Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support loading repositories from files embedded as resources in GirTool #1091

Merged
merged 1 commit into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions src/Generation/GirLoader/Helper/ChainedRepositoryResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Collections.Generic;
using System.Linq;

namespace GirLoader;

/// <summary>
/// Tries multiple repository resolvers, returning the first result found
/// </summary>
public class ChainedRepositoryResolver : IRepositoryResolver
{
private readonly IReadOnlyCollection<IRepositoryResolver> _resolvers;

public ChainedRepositoryResolver(IReadOnlyCollection<IRepositoryResolver> resolvers)
{
_resolvers = resolvers;
}

public Input.Repository? ResolveRepository(string fileName)
{
foreach (var resolver in _resolvers)
{
var repository = resolver.ResolveRepository(fileName);
if (repository != null)
{
return repository;
}
}

return null;
}
}
31 changes: 31 additions & 0 deletions src/Generation/GirLoader/Helper/DirectoryRepositoryResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System;
using System.IO;

namespace GirLoader;

/// <summary>
/// Resolves repository files from a local directory
/// </summary>
public class DirectoryRepositoryResolver : IRepositoryResolver
{
private readonly string _inputDirectory;

public DirectoryRepositoryResolver(string inputDirectory)
{
_inputDirectory = inputDirectory;
}

public Input.Repository? ResolveRepository(string fileName)
{
var path = Path.Combine(_inputDirectory, fileName);
if (File.Exists(path))
{
using var fileStream = new FileInfo(path).OpenRead();
return fileStream.DeserializeGirInputModel();
}
else
{
return null;
}
}
}
33 changes: 33 additions & 0 deletions src/Generation/GirLoader/Helper/EmbeddedRepositoryResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using System.IO;
using System.Reflection;

namespace GirLoader;

public class EmbeddedRepositoryResolver : IRepositoryResolver
{
private readonly Assembly _assembly;
private readonly string _platformName;
private readonly string _assemblyName;

public EmbeddedRepositoryResolver(Assembly assembly, string platformName)
{
_assembly = assembly;
_platformName = platformName;
_assemblyName = _assembly.GetName().Name ?? throw new Exception("Could not get assembly name");
}

public Input.Repository? ResolveRepository(string fileName)
{
var resourceName = $"{_assemblyName}.{_platformName}.{fileName}";
try
{
using var stream = _assembly.GetManifestResourceStream(resourceName);
return stream?.DeserializeGirInputModel();
}
catch (FileNotFoundException)
{
return null;
}
}
}
9 changes: 9 additions & 0 deletions src/Generation/GirLoader/Helper/IRepositoryResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace GirLoader;

/// <summary>
/// Resolves input repository definitions from GIR file names
/// </summary>
public interface IRepositoryResolver
{
Input.Repository? ResolveRepository(string fileName);
}
15 changes: 5 additions & 10 deletions src/Generation/GirLoader/Helper/IncludeResolver.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
using System.IO;

namespace GirLoader;
namespace GirLoader;

public class IncludeResolver
{
private readonly string _inputDirectory;
private readonly IRepositoryResolver _repositoryResolver;

public IncludeResolver(string inputDirectory)
public IncludeResolver(IRepositoryResolver repositoryResolver)
{
_inputDirectory = inputDirectory;
_repositoryResolver = repositoryResolver;
}

public Input.Repository? ResolveInclude(Output.Include include)
{
var fileName = $"{include.Name}-{include.Version}.gir";

var path = Path.Combine(_inputDirectory, fileName);
return File.Exists(path)
? new FileInfo(path).OpenRead().DeserializeGirInputModel()
: null;
return _repositoryResolver.ResolveRepository(fileName);
}
}
9 changes: 9 additions & 0 deletions src/Generation/GirLoader/Helper/NullRepositoryResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace GirLoader;

public class NullRepositoryResolver : IRepositoryResolver
{
public Input.Repository? ResolveRepository(string fileName)
{
return null;
}
}
39 changes: 39 additions & 0 deletions src/Generation/GirLoader/Helper/RepositoryResolverFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System.Reflection;

namespace GirLoader;

public class RepositoryResolverFactory
{
private readonly string _platform;
private readonly string? _platformSearchPath;
private readonly Assembly _assembly;

/// <summary>
/// Construct a new RepositoryResolverFactory
/// </summary>
/// <param name="platform">The platform to resolve repositories for</param>
/// <param name="platformSearchPath">Directory containing repository files for this platform.
/// If null, no repositories will be resolved for this platform.</param>
/// <param name="assembly">Assembly containing embedded repository files.
/// These are used as a fallback if no repository file is found in the search path.</param>
public RepositoryResolverFactory(string platform, string? platformSearchPath, Assembly assembly)
{
_platform = platform;
_platformSearchPath = platformSearchPath;
_assembly = assembly;
}

public IRepositoryResolver Create()
{
if (_platformSearchPath == null)
{
return new NullRepositoryResolver();
}

var directoryResolver = new DirectoryRepositoryResolver(_platformSearchPath);
var embeddedResolver = new EmbeddedRepositoryResolver(_assembly, _platform);

return new ChainedRepositoryResolver(
new IRepositoryResolver[] { directoryResolver, embeddedResolver });
}
}
19 changes: 9 additions & 10 deletions src/Generation/GirTool/GenerateCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,9 @@ private static (DeserializedInput, DeserializedInput, DeserializedInput) LoadRep
DeserializedInput? macosRepositories = null;
DeserializedInput? windowsRepositories = null;

void SetLinuxRepositories() => linuxRepositories = DeserializeInput(searchPathLinux, input);
void SetMacosRepositories() => macosRepositories = DeserializeInput(searchPathMacos, input);
void SetWindowsRepositories() => windowsRepositories = DeserializeInput(searchPathWindows, input);
void SetLinuxRepositories() => linuxRepositories = DeserializeInput("linux", searchPathLinux, input);
void SetMacosRepositories() => macosRepositories = DeserializeInput("macos", searchPathMacos, input);
void SetWindowsRepositories() => windowsRepositories = DeserializeInput("windows", searchPathWindows, input);

if (disableAsync)
{
Expand All @@ -137,15 +137,14 @@ private static (DeserializedInput, DeserializedInput, DeserializedInput) LoadRep
return (linuxRepositories!, macosRepositories!, windowsRepositories!);
}

private static DeserializedInput DeserializeInput(string? searchPath, string[] input)
private static DeserializedInput DeserializeInput(string platformName, string? searchPath, string[] input)
{
if (searchPath is null)
return DeserializedInput.Empty();
var repositoryResolver = new RepositoryResolverFactory(
platformName, searchPath, typeof(GenerateCommand).Assembly).Create();

var inputRepositories = input
.Select(x => Path.Join(searchPath, x))
.Where(File.Exists)
.Select(x => new FileInfo(x).OpenRead().DeserializeGirInputModel())
.Select(fileName => repositoryResolver.ResolveRepository(fileName))
.OfType<GirLoader.Input.Repository>()
.ToList();

// Get the namespaces corresponding to the input gir files.
Expand All @@ -154,7 +153,7 @@ private static DeserializedInput DeserializeInput(string? searchPath, string[] i
.Select(repository => repository.Namespace == null ? "" : GetNamespaceName(repository.Namespace))
.ToList();

var includeResolver = new IncludeResolver(searchPath);
var includeResolver = new IncludeResolver(repositoryResolver);
var loader = new GirLoader.Loader(includeResolver.ResolveInclude);
var outputRepositories = loader.Load(inputRepositories).ToList();

Expand Down
4 changes: 4 additions & 0 deletions src/Generation/GirTool/GirTool.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@
<ProjectReference Include="../Generator/Generator.csproj" />
<ProjectReference Include="../GirLoader/GirLoader.csproj" />
</ItemGroup>

<ItemGroup>
<EmbeddedResource Include="..\..\..\ext\gir-files\*\*.gir" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using System.Collections.Generic;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace GirLoader.Test;

[TestClass, TestCategory("UnitTest")]
public class ChainedRepositoryResolverTests
{
[TestMethod]
public void ReturnsNullWhenNoRepositoryFound()
{
var resolver = new ChainedRepositoryResolver(new[]
{
new StubRepositoryResolver(new Dictionary<string, Input.Repository>())
});

var repository = resolver.ResolveRepository("GObject-2.0.gir");

repository.Should().BeNull();
}

[TestMethod]
public void ReturnsFirstResolvedRepositoryWhenMultipleMatch()
{
var resolver = new ChainedRepositoryResolver(new[]
{
new StubRepositoryResolver(new Dictionary<string, Input.Repository>
{
{"GObject-2.0.gir", InputRepositoryHelper.CreateRepository("GObject", "2.1")},
}),
new StubRepositoryResolver(new Dictionary<string, Input.Repository>
{
{"GObject-2.0.gir", InputRepositoryHelper.CreateRepository("GObject", "2.2")},
}),
});

var repository = resolver.ResolveRepository("GObject-2.0.gir");

repository!.Namespace!.Version.Should().Be("2.1");
}

[TestMethod]
public void ReturnsRepositoryFromFallbackResolver()
{
var resolver = new ChainedRepositoryResolver(new[]
{
new StubRepositoryResolver(new Dictionary<string, Input.Repository>
{
{"TestLibrary-1.0.gir", InputRepositoryHelper.CreateRepository("TestLibrary", "1.0")},
}),
new StubRepositoryResolver(new Dictionary<string, Input.Repository>
{
{"GObject-2.0.gir", InputRepositoryHelper.CreateRepository("GObject", "2.0")},
}),
});

var repository = resolver.ResolveRepository("GObject-2.0.gir");

repository!.Namespace!.Name.Should().Be("GObject");
}

private class StubRepositoryResolver : IRepositoryResolver
{
private readonly IReadOnlyDictionary<string, Input.Repository> _repositories;

public StubRepositoryResolver(IReadOnlyDictionary<string, Input.Repository> repositories)
{
_repositories = repositories;
}

public Input.Repository? ResolveRepository(string fileName)
{
return _repositories.GetValueOrDefault(fileName);
}
}
}
2 changes: 1 addition & 1 deletion src/Tests/Generation/GirLoader.Tests/ClassPropertyTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public class ClassPropertyTest
{
private static Input.Repository GetRepositoryWithStubClass()
{
var repository = Helper.GetInputRepository("ns", "1.0");
var repository = InputRepositoryHelper.CreateRepository("ns", "1.0");
repository.Namespace!.Classes.Add(new()
{
Name = "ClassName",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System.IO;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace GirLoader.Test;

[TestClass, TestCategory("UnitTest")]
public class DirectoryRepositoryResolverTests
{
[TestMethod]
public void CanLoadRepositoryFromDirectory()
{
using var directory = new DisposableTempDirectory();
var filePath = Path.Join(directory.Path, "GObject-2.0.gir");
File.WriteAllText(filePath, InputRepositoryHelper.CreateXml("GObject", "2.0"));

var resolver = new DirectoryRepositoryResolver(directory.Path);

var repository = resolver.ResolveRepository("GObject-2.0.gir");

repository!.Namespace!.Name.Should().Be("GObject");
}

[TestMethod]
public void ReturnsNullWhenFileNotFound()
{
using var directory = new DisposableTempDirectory();
var resolver = new DirectoryRepositoryResolver(directory.Path);

var repository = resolver.ResolveRepository("GObject-2.0.gir");

repository.Should().BeNull();
}
}
21 changes: 21 additions & 0 deletions src/Tests/Generation/GirLoader.Tests/DisposableTempDirectory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.IO;

namespace GirLoader.Test;

public class DisposableTempDirectory : IDisposable
{
public DisposableTempDirectory()
{
Path = System.IO.Path.Combine(
System.IO.Path.GetTempPath(), System.IO.Path.GetRandomFileName());
Directory.CreateDirectory(Path);
}

public string Path { get; }

public void Dispose()
{
Directory.Delete(Path, recursive: true);
}
}
Loading
Loading