diff --git a/src/Generation/GirLoader/Helper/ChainedRepositoryResolver.cs b/src/Generation/GirLoader/Helper/ChainedRepositoryResolver.cs new file mode 100644 index 000000000..2b863fff5 --- /dev/null +++ b/src/Generation/GirLoader/Helper/ChainedRepositoryResolver.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Linq; + +namespace GirLoader; + +/// +/// Tries multiple repository resolvers, returning the first result found +/// +public class ChainedRepositoryResolver : IRepositoryResolver +{ + private readonly IRepositoryResolver[] _resolvers; + + public ChainedRepositoryResolver(IEnumerable resolvers) + { + _resolvers = resolvers.ToArray(); + } + + public Input.Repository? ResolveRepository(string fileName) + { + foreach (var resolver in _resolvers) + { + var repository = resolver.ResolveRepository(fileName); + if (repository != null) + { + return repository; + } + } + + return null; + } +} diff --git a/src/Generation/GirLoader/Helper/DirectoryRepositoryResolver.cs b/src/Generation/GirLoader/Helper/DirectoryRepositoryResolver.cs new file mode 100644 index 000000000..e44a8354b --- /dev/null +++ b/src/Generation/GirLoader/Helper/DirectoryRepositoryResolver.cs @@ -0,0 +1,25 @@ +using System; +using System.IO; + +namespace GirLoader; + +/// +/// Resolves repository files from a local directory +/// +public class DirectoryRepositoryResolver : IRepositoryResolver +{ + private readonly string _inputDirectory; + + public DirectoryRepositoryResolver(string inputDirectory) + { + _inputDirectory = inputDirectory ?? throw new ArgumentNullException(nameof(inputDirectory)); + } + + public Input.Repository? ResolveRepository(string fileName) + { + var path = Path.Combine(_inputDirectory, fileName); + return File.Exists(path) + ? new FileInfo(path).OpenRead().DeserializeGirInputModel() + : null; + } +} diff --git a/src/Generation/GirLoader/Helper/EmbeddedRepositoryResolver.cs b/src/Generation/GirLoader/Helper/EmbeddedRepositoryResolver.cs new file mode 100644 index 000000000..ebf07f0d6 --- /dev/null +++ b/src/Generation/GirLoader/Helper/EmbeddedRepositoryResolver.cs @@ -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 ?? throw new ArgumentNullException(nameof(assembly)); + _platformName = platformName ?? throw new ArgumentNullException(nameof(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; + } + } +} diff --git a/src/Generation/GirLoader/Helper/IRepositoryResolver.cs b/src/Generation/GirLoader/Helper/IRepositoryResolver.cs new file mode 100644 index 000000000..43d5c8fc5 --- /dev/null +++ b/src/Generation/GirLoader/Helper/IRepositoryResolver.cs @@ -0,0 +1,9 @@ +namespace GirLoader; + +/// +/// Resolves input repository definitions from GIR file names +/// +public interface IRepositoryResolver +{ + Input.Repository? ResolveRepository(string fileName); +} diff --git a/src/Generation/GirLoader/Helper/IncludeResolver.cs b/src/Generation/GirLoader/Helper/IncludeResolver.cs index 2dd99d529..d6fb3864e 100644 --- a/src/Generation/GirLoader/Helper/IncludeResolver.cs +++ b/src/Generation/GirLoader/Helper/IncludeResolver.cs @@ -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); } } diff --git a/src/Generation/GirTool/GenerateCommand.cs b/src/Generation/GirTool/GenerateCommand.cs index 7c78a8f34..92c098478 100644 --- a/src/Generation/GirTool/GenerateCommand.cs +++ b/src/Generation/GirTool/GenerateCommand.cs @@ -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) { @@ -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 = GetRepositoryResolver(platformName, searchPath); var inputRepositories = input - .Select(x => Path.Join(searchPath, x)) - .Where(File.Exists) - .Select(x => new FileInfo(x).OpenRead().DeserializeGirInputModel()) + .Select(fileName => repositoryResolver.ResolveRepository(fileName)) + .Where(inputRepository => inputRepository != null) + .Select(inputRepository => inputRepository!) .ToList(); // Get the namespaces corresponding to the input gir files. @@ -154,13 +153,30 @@ 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(); return new DeserializedInput(outputRepositories, inputNamespaces); } + private static IRepositoryResolver GetRepositoryResolver(string platformName, string? searchPath) + { + var assembly = typeof(GenerateCommand).Assembly; + var embeddedResolver = new EmbeddedRepositoryResolver(assembly, platformName); + + if (searchPath == null) + { + return embeddedResolver; + } + else + { + var directoryResolver = new DirectoryRepositoryResolver(searchPath); + return new ChainedRepositoryResolver( + new IRepositoryResolver[] {directoryResolver, embeddedResolver}); + } + } + private static string GetNamespaceName(GirModel.Namespace ns) { return $"{ns.Name}-{ns.Version}"; diff --git a/src/Generation/GirTool/GirTool.csproj b/src/Generation/GirTool/GirTool.csproj index 9200795bf..bcf922429 100644 --- a/src/Generation/GirTool/GirTool.csproj +++ b/src/Generation/GirTool/GirTool.csproj @@ -9,4 +9,8 @@ + + + + diff --git a/src/Tests/Generation/GirLoader.Tests/ChainedRepositoryResolverTests.cs b/src/Tests/Generation/GirLoader.Tests/ChainedRepositoryResolverTests.cs new file mode 100644 index 000000000..0fd585bcd --- /dev/null +++ b/src/Tests/Generation/GirLoader.Tests/ChainedRepositoryResolverTests.cs @@ -0,0 +1,81 @@ +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()) + }); + + var repository = resolver.ResolveRepository("GObject-2.0.gir"); + + repository.Should().BeNull(); + } + + [TestMethod] + public void ReturnsFirstResolvedRepositoryWhenMultipleMatch() + { + var resolver = new ChainedRepositoryResolver(new[] + { + new StubRepositoryResolver(new Dictionary + { + {"GObject-2.0.gir", Helper.GetInputRepository("GObject", "2.1")}, + }), + new StubRepositoryResolver(new Dictionary + { + {"GObject-2.0.gir", Helper.GetInputRepository("GObject", "2.2")}, + }), + }); + + var repository = resolver.ResolveRepository("GObject-2.0.gir"); + + repository.Should().NotBeNull(); + repository!.Namespace.Should().NotBeNull(); + repository.Namespace!.Version.Should().Be("2.1"); + } + + [TestMethod] + public void ReturnsRepositoryFromFallbackResolver() + { + var resolver = new ChainedRepositoryResolver(new[] + { + new StubRepositoryResolver(new Dictionary + { + {"TestLibrary-1.0.gir", Helper.GetInputRepository("TestLibrary", "1.0")}, + }), + new StubRepositoryResolver(new Dictionary + { + {"GObject-2.0.gir", Helper.GetInputRepository("GObject", "2.0")}, + }), + }); + + var repository = resolver.ResolveRepository("GObject-2.0.gir"); + + repository.Should().NotBeNull(); + repository!.Namespace.Should().NotBeNull(); + repository.Namespace!.Name.Should().Be("GObject"); + } + + private class StubRepositoryResolver : IRepositoryResolver + { + private readonly IReadOnlyDictionary _repositories; + + public StubRepositoryResolver(IReadOnlyDictionary repositories) + { + _repositories = repositories; + } + + public Input.Repository? ResolveRepository(string fileName) + { + return _repositories.GetValueOrDefault(fileName); + } + } +} diff --git a/src/Tests/Generation/GirLoader.Tests/DirectoryRepositoryResolverTests.cs b/src/Tests/Generation/GirLoader.Tests/DirectoryRepositoryResolverTests.cs new file mode 100644 index 000000000..96fdae24f --- /dev/null +++ b/src/Tests/Generation/GirLoader.Tests/DirectoryRepositoryResolverTests.cs @@ -0,0 +1,36 @@ +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.DirectoryPath, "GObject-2.0.gir"); + File.WriteAllText(filePath, Helper.GetInputRepositoryXml("GObject", "2.0")); + + var resolver = new DirectoryRepositoryResolver(directory.DirectoryPath); + + var repository = resolver.ResolveRepository("GObject-2.0.gir"); + + repository.Should().NotBeNull(); + repository!.Namespace.Should().NotBeNull(); + repository.Namespace!.Name.Should().Be("GObject"); + } + + [TestMethod] + public void ReturnsNullWhenFileNotFound() + { + using var directory = new DisposableTempDirectory(); + var resolver = new DirectoryRepositoryResolver(directory.DirectoryPath); + + var repository = resolver.ResolveRepository("GObject-2.0.gir"); + + repository.Should().BeNull(); + } +} diff --git a/src/Tests/Generation/GirLoader.Tests/DisposableTempDirectory.cs b/src/Tests/Generation/GirLoader.Tests/DisposableTempDirectory.cs new file mode 100644 index 000000000..f7c83e371 --- /dev/null +++ b/src/Tests/Generation/GirLoader.Tests/DisposableTempDirectory.cs @@ -0,0 +1,22 @@ +using System; +using System.IO; + +namespace GirLoader.Test; + +public class DisposableTempDirectory : IDisposable +{ + private readonly string _directoryPath; + + public DisposableTempDirectory() + { + _directoryPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + Directory.CreateDirectory(_directoryPath); + } + + public string DirectoryPath => _directoryPath; + + public void Dispose() + { + Directory.Delete(_directoryPath, recursive: true); + } +} diff --git a/src/Tests/Generation/GirLoader.Tests/EmbeddedRepositoryResolverTests.cs b/src/Tests/Generation/GirLoader.Tests/EmbeddedRepositoryResolverTests.cs new file mode 100644 index 000000000..171be3825 --- /dev/null +++ b/src/Tests/Generation/GirLoader.Tests/EmbeddedRepositoryResolverTests.cs @@ -0,0 +1,32 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace GirLoader.Test; + +[TestClass, TestCategory("UnitTest")] +public class EmbeddedRepositoryResolverTests +{ + [TestMethod] + public void CanLoadRepositoryFromEmbeddedResource() + { + var resolver = new EmbeddedRepositoryResolver( + typeof(EmbeddedRepositoryResolverTests).Assembly, "linux"); + + var repository = resolver.ResolveRepository("GObject-2.0.gir"); + + repository.Should().NotBeNull(); + repository!.Namespace.Should().NotBeNull(); + repository.Namespace!.Name.Should().Be("GObject"); + } + + [TestMethod] + public void ReturnsNullWhenEmbeddedResourceNotFound() + { + var resolver = new EmbeddedRepositoryResolver( + typeof(EmbeddedRepositoryResolverTests).Assembly, "linux"); + + var repository = resolver.ResolveRepository("TestLibrary-1.0.gir"); + + repository.Should().BeNull(); + } +} diff --git a/src/Tests/Generation/GirLoader.Tests/GirLoader.Tests.csproj b/src/Tests/Generation/GirLoader.Tests/GirLoader.Tests.csproj index 6a1eca193..60a04d5cd 100644 --- a/src/Tests/Generation/GirLoader.Tests/GirLoader.Tests.csproj +++ b/src/Tests/Generation/GirLoader.Tests/GirLoader.Tests.csproj @@ -5,4 +5,8 @@ + + + + diff --git a/src/Tests/Generation/GirLoader.Tests/Helper.cs b/src/Tests/Generation/GirLoader.Tests/Helper.cs index 68a6ba0eb..87a73ce7a 100644 --- a/src/Tests/Generation/GirLoader.Tests/Helper.cs +++ b/src/Tests/Generation/GirLoader.Tests/Helper.cs @@ -13,4 +13,15 @@ internal static Input.Repository GetInputRepository(string namespaceName, string } }; } + + internal static string GetInputRepositoryXml(string namespaceName, string version) + { + return $""" + + + + + + """; + } }