From a91d4b70383d6abf0f1082c62a6f429795420807 Mon Sep 17 00:00:00 2001 From: PascalSenn Date: Tue, 7 May 2024 12:04:04 +0200 Subject: [PATCH 1/2] Adds component root --- .gitignore | 4 +- Directory.Packages.props | 1 + .../WriteConfigurationFileMiddleware.cs | 1 - .../src/Confix.Library/Confix.Library.csproj | 1 + .../Configuration/ComponentConfiguration.cs | 1 - .../DotNet/DotnetPackageComponentProvider.cs | 206 +++++++++++------- .../Utilities/Dotnet/DotnetHelpers.cs | 28 +-- .../src/Confix.Tool/Confix.Tool.csproj | 1 + 8 files changed, 144 insertions(+), 99 deletions(-) diff --git a/.gitignore b/.gitignore index 0ffe88ee..73479c51 100644 --- a/.gitignore +++ b/.gitignore @@ -484,4 +484,6 @@ $RECYCLE.BIN/ #nextjs .next/ -out/ \ No newline at end of file +out/ + +.mono diff --git a/Directory.Packages.props b/Directory.Packages.props index a4d4b443..55cac6b4 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -25,6 +25,7 @@ + diff --git a/src/Confix.Tool/src/Confix.Library/ConfigurationFile/WriteConfigurationFileMiddleware.cs b/src/Confix.Tool/src/Confix.Library/ConfigurationFile/WriteConfigurationFileMiddleware.cs index 8d3dabab..12ea36af 100644 --- a/src/Confix.Tool/src/Confix.Library/ConfigurationFile/WriteConfigurationFileMiddleware.cs +++ b/src/Confix.Tool/src/Confix.Library/ConfigurationFile/WriteConfigurationFileMiddleware.cs @@ -1,4 +1,3 @@ -using System.Text.Json; using Confix.Tool.Commands.Logging; using Confix.Tool.Common.Pipelines; using Confix.Tool.Middlewares.Encryption; diff --git a/src/Confix.Tool/src/Confix.Library/Confix.Library.csproj b/src/Confix.Tool/src/Confix.Library/Confix.Library.csproj index d7134838..64b2db3a 100644 --- a/src/Confix.Tool/src/Confix.Library/Confix.Library.csproj +++ b/src/Confix.Tool/src/Confix.Library/Confix.Library.csproj @@ -35,6 +35,7 @@ + diff --git a/src/Confix.Tool/src/Confix.Library/Entities/Component/Configuration/ComponentConfiguration.cs b/src/Confix.Tool/src/Confix.Library/Entities/Component/Configuration/ComponentConfiguration.cs index e4a821ff..9d139fae 100644 --- a/src/Confix.Tool/src/Confix.Library/Entities/Component/Configuration/ComponentConfiguration.cs +++ b/src/Confix.Tool/src/Confix.Library/Entities/Component/Configuration/ComponentConfiguration.cs @@ -1,4 +1,3 @@ -using System.Text.Json; using System.Text.Json.Nodes; using Confix.Tool.Middlewares; using Confix.Tool.Schema; diff --git a/src/Confix.Tool/src/Confix.Library/Entities/Component/Providers/DotNet/DotnetPackageComponentProvider.cs b/src/Confix.Tool/src/Confix.Library/Entities/Component/Providers/DotNet/DotnetPackageComponentProvider.cs index 4764b7bb..5ac6ab38 100644 --- a/src/Confix.Tool/src/Confix.Library/Entities/Component/Providers/DotNet/DotnetPackageComponentProvider.cs +++ b/src/Confix.Tool/src/Confix.Library/Entities/Component/Providers/DotNet/DotnetPackageComponentProvider.cs @@ -40,22 +40,79 @@ public async Task ExecuteAsync(IComponentProviderContext context) throw new ExitException($"Failed to build project:\n{output}"); } - var projectAssembly = DotnetHelpers.GetAssemblyFileFromCsproj(csproj); - - if (projectAssembly is not { Exists: true }) + var projectAssembly = DotnetHelpers.GetAssemblyNameFromCsproj(csproj); + var components = await DiscoverResources(context.Logger, projectAssembly, projectDirectory); + foreach (var component in components) { - context.Logger.ProjectNotFoundInDirectory(projectDirectory); - context.Logger.DotnetProjectWasNotDetected(); - return; + context.Components.Add(component); } + } - var resources = - DiscoverResources(context.Logger, projectAssembly, projectDirectory); - var components = await LoadComponents(resources); - foreach (var component in components) + private static async Task> DiscoverResources( + IConsoleLogger logger, + string rootAssemblyName, + DirectoryInfo directory) + { + var discoveredResources = new List(); + + logger.FoundAssembly(rootAssemblyName); + + var assembliesToScan = new Queue(); + var processedAssemblies = new HashSet(); + + assembliesToScan.Enqueue(rootAssemblyName); + + var assemblyResolver = DotnetHelpers.CreateAssemblyResolver(directory); + using var metadataLoadContext = new MetadataLoadContext(assemblyResolver); + + while (assembliesToScan.TryDequeue(out var assemblyName)) { - context.Components.Add(component); + if (!processedAssemblies.Add(assemblyName)) + { + continue; + } + + logger.ScanningAssembly(assemblyName); + + try + { + var assembly = metadataLoadContext.TryLoadAssembly(assemblyName); + if (assembly is null) + { + logger.AssemblyFileNotFound(assemblyName); + continue; + } + + var isComponentRoot = assembly.IsComponentRoot(); + if (!isComponentRoot) + { + var referencedAssemblies = assembly + .GetReferencedAssemblies() + .Where(x => !string.IsNullOrWhiteSpace(x.Name) && + !x.Name.StartsWith("System", StringComparison.InvariantCulture) && + !x.Name.StartsWith("Microsoft", StringComparison.InvariantCulture)) + .ToArray(); + + referencedAssemblies.ForEach(x => assembliesToScan.Enqueue(x.Name!)); + } + else if (assembly.IsComponentRoot()) + { + logger.DetectedComponentRoot(assemblyName); + } + + foreach (var resourceName in assembly.GetManifestResourceNames()) + { + logger.FoundManifestResourceInAssembly(resourceName, assemblyName); + discoveredResources.Add(new DiscoveredResource(assembly, resourceName)); + } + } + catch (BadImageFormatException ex) + { + logger.CouldNotLoadAssembly(assemblyName, ex); + } } + + return await LoadComponents(discoveredResources); } private static async Task> LoadComponents( @@ -142,65 +199,6 @@ private static async ValueTask LoadComponentConfiguratio } } - private static IReadOnlyList DiscoverResources( - IConsoleLogger logger, - FileSystemInfo assemblyFile, - DirectoryInfo directory) - { - var discoveredResources = new List(); - - logger.FoundAssembly(assemblyFile); - - var assembliesToScan = new Queue(); - var processedAssemblies = new HashSet(); - - assembliesToScan.Enqueue(assemblyFile.Name[..^assemblyFile.Extension.Length]); - - while (assembliesToScan.TryDequeue(out var assemblyName)) - { - if (!processedAssemblies.Add(assemblyName)) - { - continue; - } - - logger.ScanningAssembly(assemblyName); - - var assemblyFilePath = DotnetHelpers - .GetAssemblyInPathByName(directory, assemblyName); - - if (assemblyFilePath is not { Exists: true }) - { - logger.AssemblyFileNotFound(assemblyName); - continue; - } - - try - { - logger.FoundAssemblyFile(assemblyFilePath); - var assembly = Assembly.LoadFile(assemblyFilePath.FullName); - - assembly - .GetReferencedAssemblies() - .Where(x => !string.IsNullOrWhiteSpace(x.Name) && - !x.Name.StartsWith("System", StringComparison.InvariantCulture) && - !x.Name.StartsWith("Microsoft", StringComparison.InvariantCulture)) - .ForEach(x => assembliesToScan.Enqueue(x.Name!)); - - foreach (var resourceName in assembly.GetManifestResourceNames()) - { - logger.FoundManifestResourceInAssembly(resourceName, assemblyName); - discoveredResources.Add(new DiscoveredResource(assembly, resourceName)); - } - } - catch (BadImageFormatException ex) - { - logger.CouldNotLoadAssembly(assemblyFile, ex); - } - } - - return discoveredResources; - } - private record DiscoveredResource(Assembly Assembly, string ResourceName) { public Stream GetStream() => Assembly.GetManifestResourceStream(ResourceName) ?? @@ -211,6 +209,45 @@ public Stream GetStream() => Assembly.GetManifestResourceStream(ResourceName) ?? file static class Extensions { + public static Assembly? TryLoadAssembly(this MetadataLoadContext context, string assemblyName) + { + try + { + return context + .GetAssemblies() + .FirstOrDefault(x => x.FullName == assemblyName) ?? + context.LoadFromAssemblyName(assemblyName); + } + catch + { + return null; + } + } + + public static bool IsComponentRoot(this Assembly assembly) + { + return assembly + .GetCustomAttributesData() + .Any(x => + { + try + { + // even though both are assembly metadata attributes, they are not of the equal + // type, so we need to compare the full name + return x.AttributeType.FullName == + typeof(AssemblyMetadataAttribute).FullName && + x.ConstructorArguments is + [ + { Value: "IsConfixComponentRoot" }, { Value: "true" } + ]; + } + catch + { + return false; + } + }); + } + public static void EnsureSolution(this IComponentProviderContext context) { if (context.Solution.Directory is not { Exists: true }) @@ -237,32 +274,27 @@ public static void StartLoadingComponents(this IConsoleLogger logger, string nam logger.Debug($"Start loading components from project '{name}'"); } - public static void FoundAssembly(this IConsoleLogger logger, FileSystemInfo assembly) + public static void AssemblyFileNotFound(this IConsoleLogger logger, string assembly) { - logger.Debug($"Found assembly: {assembly.Name}"); + logger.Debug($"Assembly file not found for assembly: {assembly}"); } - public static void ScanningAssembly(this IConsoleLogger logger, string assembly) + public static void FoundAssembly(this IConsoleLogger logger, string assemblyName) { - logger.Debug($"Scanning assembly: {assembly}"); + logger.Debug($"Found assembly: {assemblyName}"); } - public static void FoundAssemblyFile(this IConsoleLogger logger, FileSystemInfo assembly) + public static void ScanningAssembly(this IConsoleLogger logger, string assembly) { - logger.Debug($"Found assembly file: {assembly.FullName}"); + logger.Debug($"Scanning assembly: {assembly}"); } public static void CouldNotLoadAssembly( this IConsoleLogger logger, - FileSystemInfo assembly, + string assembly, Exception ex) { - logger.Debug($"Could not load assembly: {assembly.FullName}. {ex.Message}"); - } - - public static void AssemblyFileNotFound(this IConsoleLogger logger, string assembly) - { - logger.Debug($"Assembly file not found for assembly: {assembly}"); + logger.Debug($"Could not load assembly: {assembly}. {ex.Message}"); } public static void FoundDotnetProject(this IConsoleLogger logger, FileSystemInfo csproj) @@ -298,4 +330,12 @@ public static void ParsingComponent( logger.Debug( $"Parsing component from resource '{resourceName}' in assembly '{assembly.FullName}'"); } + + public static void DetectedComponentRoot( + this IConsoleLogger logger, + string assembly) + { + logger.Inform( + $"Detected component root in assembly '{assembly}'. Skipping scanning referenced assemblies."); + } } diff --git a/src/Confix.Tool/src/Confix.Library/Utilities/Dotnet/DotnetHelpers.cs b/src/Confix.Tool/src/Confix.Library/Utilities/Dotnet/DotnetHelpers.cs index c59a7962..a0f00f22 100644 --- a/src/Confix.Tool/src/Confix.Library/Utilities/Dotnet/DotnetHelpers.cs +++ b/src/Confix.Tool/src/Confix.Library/Utilities/Dotnet/DotnetHelpers.cs @@ -1,4 +1,6 @@ using System.Diagnostics; +using System.Reflection; +using System.Runtime.InteropServices; using System.Text; using System.Xml; using System.Xml.Linq; @@ -32,7 +34,7 @@ public static async Task BuildProjectAsync( return new ProcessExecutionResult(process.ExitCode == 0, output); } - public static FileInfo? GetAssemblyFileFromCsproj(FileInfo projectFile) + public static string GetAssemblyNameFromCsproj(FileInfo projectFile) { // Load the .csproj file as an XDocument var csprojDoc = XDocument.Load(projectFile.FullName, LoadOptions.PreserveWhitespace); @@ -45,8 +47,7 @@ public static async Task BuildProjectAsync( ?.Value ?? Path.GetFileNameWithoutExtension(projectFile.FullName); - // Construct the path to where the assembly should be built - return GetAssemblyInPathByName(projectFile.Directory!, propertyGroup); + return propertyGroup; } public static async Task EnsureUserSecretsIdAsync( @@ -88,7 +89,7 @@ public static async Task EnsureUserSecretsIdAsync( propertyGroup.Add(new XElement(Xml.UserSecretsId, userSecretsId)); App.Log.AddedUserSecretsIdToTheCsprojFile(userSecretsId); - + await csprojDoc.PrettifyAndSaveAsync(csprojFile.FullName, ct); } else @@ -100,7 +101,7 @@ public static async Task EnsureUserSecretsIdAsync( } public static async Task EnsureEmbeddedResourceAsync( - FileInfo csprojFile, + FileInfo csprojFile, string path, CancellationToken ct) { @@ -144,9 +145,7 @@ public static async Task EnsureEmbeddedResourceAsync( } } - public static FileInfo? GetAssemblyInPathByName( - DirectoryInfo projectDirectory, - string assemblyName) + public static PathAssemblyResolver CreateAssemblyResolver(DirectoryInfo projectDirectory) { var binDirectory = Path.Join(projectDirectory.FullName, "bin"); if (!Directory.Exists(binDirectory)) @@ -155,11 +154,14 @@ public static async Task EnsureEmbeddedResourceAsync( $"The directory '{binDirectory}' was not found. Make sure to build the project first."); } - var firstMatch = Directory - .EnumerateFiles(binDirectory, assemblyName + ".dll", SearchOption.AllDirectories) - .FirstOrDefault(); + var appAssembly = Directory + .EnumerateFiles(binDirectory, "*.dll", SearchOption.AllDirectories) + .DistinctBy(Path.GetFileName); - return firstMatch is null ? null : new FileInfo(firstMatch); + var runtimeAssemblies = Directory + .GetFiles(RuntimeEnvironment.GetRuntimeDirectory(), "*.dll"); + + return new PathAssemblyResolver(appAssembly.Concat(runtimeAssemblies)); } public static FileInfo? FindProjectFileInPath(DirectoryInfo directory) @@ -177,7 +179,7 @@ private static async Task PrettifyAndSaveAsync( settings.Indent = true; settings.Async = true; settings.OmitXmlDeclaration = true; - + var formattedCsproj = new StringBuilder(); await using var writer = XmlWriter.Create(formattedCsproj, settings); await xDocument.WriteToAsync(writer, ct); diff --git a/src/Confix.Tool/src/Confix.Tool/Confix.Tool.csproj b/src/Confix.Tool/src/Confix.Tool/Confix.Tool.csproj index eb208829..9ec48939 100644 --- a/src/Confix.Tool/src/Confix.Tool/Confix.Tool.csproj +++ b/src/Confix.Tool/src/Confix.Tool/Confix.Tool.csproj @@ -37,6 +37,7 @@ + From 1f094f07d058681229508da0c7824316c1559d62 Mon Sep 17 00:00:00 2001 From: PascalSenn Date: Tue, 7 May 2024 12:30:28 +0200 Subject: [PATCH 2/2] Fixed --- .../Providers/DotNet/DotnetPackageComponentProvider.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Confix.Tool/src/Confix.Library/Entities/Component/Providers/DotNet/DotnetPackageComponentProvider.cs b/src/Confix.Tool/src/Confix.Library/Entities/Component/Providers/DotNet/DotnetPackageComponentProvider.cs index 5ac6ab38..935de497 100644 --- a/src/Confix.Tool/src/Confix.Library/Entities/Component/Providers/DotNet/DotnetPackageComponentProvider.cs +++ b/src/Confix.Tool/src/Confix.Library/Entities/Component/Providers/DotNet/DotnetPackageComponentProvider.cs @@ -84,7 +84,11 @@ private static async Task> DiscoverResources( } var isComponentRoot = assembly.IsComponentRoot(); - if (!isComponentRoot) + if (isComponentRoot) + { + logger.DetectedComponentRoot(assemblyName); + } + else { var referencedAssemblies = assembly .GetReferencedAssemblies() @@ -95,10 +99,6 @@ private static async Task> DiscoverResources( referencedAssemblies.ForEach(x => assembliesToScan.Enqueue(x.Name!)); } - else if (assembly.IsComponentRoot()) - { - logger.DetectedComponentRoot(assemblyName); - } foreach (var resourceName in assembly.GetManifestResourceNames()) {