diff --git a/build/Build.cs b/build/Build.cs index f1720b20..500fb5a5 100644 --- a/build/Build.cs +++ b/build/Build.cs @@ -118,12 +118,15 @@ public partial class Build : NukeBuild .Produces(UnitTestsResultsDirectory) .Executes(() => { - DotNetTest(c => c - .SetConfiguration(Configuration) - .EnableNoBuild() - .SetDataCollector("XPlat Code Coverage;Format=opencover") - .CombineWith(SourceDirectory.GlobFiles("**/*.UTests.csproj"), (settings, path) => - settings.SetProjectFile(path)), degreeOfParallelism: 4, completeOnFailure: true); + foreach (var dotNetVersion in DotNetVersions) { + DotNetTest(c => c + .SetConfiguration(Configuration) + .SetFramework(dotNetVersion) + .EnableNoBuild() + .SetDataCollector("XPlat Code Coverage;Format=opencover") + .CombineWith(SourceDirectory.GlobFiles("**/*.UTests.csproj"), (settings, path) => + settings.SetProjectFile(path)), degreeOfParallelism: 4, completeOnFailure: true); + } }); internal Target Pack => _ => _ @@ -200,16 +203,19 @@ public partial class Build : NukeBuild .SetConfigFile(Path.Combine(Path.Combine(workingDirectory, "Properties"), "NuGet.Config")) ); // Run the ITests in "dotnet tool" mode - DotNetTest(c => c - .SetConfiguration(Configuration) - .EnableNoBuild() - .SetDataCollector("XPlat Code Coverage;Format=opencover") - .CombineWith(SourceDirectory.GlobFiles("**/*.ITests.csproj"), (settings, path) => - settings - .SetProjectFile(path) - .SetProcessEnvironmentVariable("DryGen.ITests.ToolInvocationSteps.RunAsTool", "some value") - .SetProcessEnvironmentVariable("DryGen.ITests.ToolInvocationSteps.WorkingDirectory", workingDirectory)) - , degreeOfParallelism: 4, completeOnFailure: true); + foreach (var dotNetVersion in DotNetVersions) { + DotNetTest(c => c + .SetConfiguration(Configuration) + .SetFramework(dotNetVersion) + .EnableNoBuild() + .SetDataCollector("XPlat Code Coverage;Format=opencover") + .CombineWith(SourceDirectory.GlobFiles("**/*.ITests.csproj"), (settings, path) => + settings + .SetProjectFile(path) + .SetProcessEnvironmentVariable("DryGen.ITests.ToolInvocationSteps.RunAsTool", "some value") + .SetProcessEnvironmentVariable("DryGen.ITests.ToolInvocationSteps.WorkingDirectory", workingDirectory)) + , degreeOfParallelism: 4, completeOnFailure: true); + } }); internal Target GenerateDocs => _ => _ @@ -445,4 +451,5 @@ private static void LogChangesAndFailIfGitWorkingCopyIsNotClean() private const string PwshVersion = "7.4.5"; private const string DocfxPackageName = "docfx"; private const string DocfxVersion = "2.77.0"; + private readonly string[] DotNetVersions = ["net8.0", "net9.0"]; } \ No newline at end of file diff --git a/docs/templates/releasenotes/2024-11-03-v-2.0.0-prerelease0002.md b/docs/templates/releasenotes/2024-11-10-v-2.0.0-prerelease0003.md similarity index 69% rename from docs/templates/releasenotes/2024-11-03-v-2.0.0-prerelease0002.md rename to docs/templates/releasenotes/2024-11-10-v-2.0.0-prerelease0003.md index cae5d3e8..79eb3b54 100644 --- a/docs/templates/releasenotes/2024-11-03-v-2.0.0-prerelease0002.md +++ b/docs/templates/releasenotes/2024-11-10-v-2.0.0-prerelease0003.md @@ -2,15 +2,18 @@ > [!IMPORTANT] > In v 2.x .Net 6 and .Net 7 will no longer be supported, since they're no longer supported by Microsoft. -### Improvements in this version -## Prerelease 2 +## Improvements in this version +### Prerelease 3 +- Fixed an issue where the lookup of the Asp.Net Core shared folder failed if the version number for .Net and Asp.Net Core was not exactly the same. + - E.g. for .Net 9 RC 2 where the Asp.Net Core version number was 9.0.0-rc.2.24474.3, but the .Net version number was 9.0.0-rc.2.24473.5 +### Prerelease 2 - Added support for .Net 9 - Added support for generating many to many associations in `mermaid-class-diagram-from-csharp`. - Added support for generating many to many relations in `mermaid-er-diagram-from-csharp`. - Added support for `include-exception-stacktrace` in `verbs-from-options-file`. -## Prerelease 1 (2024-10-29) +### Prerelease 1 (2024-10-29) - Fixed an issue where `mermaid-er-diagram-from-efcore` would fail when an input assebly referenced Asp.Net Core. - - Ie the .csproj file contained `` or `` or referenced such an assembly. + - Ie the .csproj file contained `` or `` or referenced such an assembly. - Added support for generating many to many relations in `mermaid-er-diagram-from-efcore`. - Switched to [docfx](https://dotnet.github.io/docfx/) for generating the documentation. - docfx uses .Net, so Ruby is no longer needed in the development toolchain diff --git a/src/DryGen.CSharpFromJsonSchema/CSharpFromJsonSchemaGenerator.cs b/src/DryGen.CSharpFromJsonSchema/CSharpFromJsonSchemaGenerator.cs index 5def47c9..803f44a3 100644 --- a/src/DryGen.CSharpFromJsonSchema/CSharpFromJsonSchemaGenerator.cs +++ b/src/DryGen.CSharpFromJsonSchema/CSharpFromJsonSchemaGenerator.cs @@ -57,10 +57,7 @@ private static void RemoveSynteticSchemaProperty(JsonSchema jsonSchema) { // Hack to get rid of the syntetic $schema property we must use if we want additionalProperties = false in the topmost object const string schemaPropertyName = "$schema"; - if (jsonSchema.Properties.ContainsKey(schemaPropertyName)) - { - jsonSchema.Properties.Remove(schemaPropertyName); - } + jsonSchema.Properties.Remove(schemaPropertyName); } private static async Task LoadJsonSchemaFromFile(string? jsonSchemaFileName, JsonSchemaFileFormat jsonSchemaFileFormat) diff --git a/src/DryGen.CodeCompiler/Extensions.cs b/src/DryGen.CodeCompiler/Extensions.cs index 5b5f2b32..b378ed3e 100644 --- a/src/DryGen.CodeCompiler/Extensions.cs +++ b/src/DryGen.CodeCompiler/Extensions.cs @@ -17,7 +17,7 @@ public static Assembly CompileCodeToMemory(this string csharpCode, params Assemb using var ms = new MemoryStream(); csharpCode.CompileCodeToStream(assemblyName, ms, referencedAssemblies); ms.Seek(0, SeekOrigin.Begin); - Assembly assembly = Assembly.Load(ms.ToArray()); + var assembly = Assembly.Load(ms.ToArray()); return assembly; } @@ -55,12 +55,12 @@ public static void CompileCodeToStream(this string csharpCode, string assemblyNa syntaxTrees: new[] { syntaxTree }, references: references, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); - EmitResult compilationResult = compilation.Emit(stream); - ThrowExceptionIfCompilationFailed(compilationResult); + var compilationResult = compilation.Emit(stream); + compilationResult.ThrowExceptionIfCompilationFailed(); } [ExcludeFromCodeCoverage] //This should in theory never happen in a normal test run, only when we develop new tests that compiles C# code - private static void ThrowExceptionIfCompilationFailed(EmitResult compilationResult) + private static void ThrowExceptionIfCompilationFailed(this EmitResult compilationResult) { if (!compilationResult.Success) { diff --git a/src/DryGen.Core/Extensions.cs b/src/DryGen.Core/Extensions.cs index 9a91a691..0a116dc2 100644 --- a/src/DryGen.Core/Extensions.cs +++ b/src/DryGen.Core/Extensions.cs @@ -78,4 +78,10 @@ public static T AsNonNull(this T? value) where T : class ArgumentNullException.ThrowIfNull(value); return value; } + + public static string AsLinuxPath(this string path) + { + return path.Replace("\\", "/"); + } + } \ No newline at end of file diff --git a/src/DryGen.MermaidFromCSharp/ClassDiagram/ClassDiagramGenerator.cs b/src/DryGen.MermaidFromCSharp/ClassDiagram/ClassDiagramGenerator.cs index 788d294b..34aee9ce 100644 --- a/src/DryGen.MermaidFromCSharp/ClassDiagram/ClassDiagramGenerator.cs +++ b/src/DryGen.MermaidFromCSharp/ClassDiagram/ClassDiagramGenerator.cs @@ -94,7 +94,7 @@ private static ClassDiagramClass[] ConvertExtensionMethodsToInstanceMethodsOnKno return classDiagramClasses.Except(removedExtensionClasses).ToArray(); } - private static void GenerateClassAssociationsCompositionsAndAggregations(IDictionary classLookup, ClassDiagramClass classDiagramClass) + private static void GenerateClassAssociationsCompositionsAndAggregations(Dictionary classLookup, ClassDiagramClass classDiagramClass) { foreach (var property in classDiagramClass.Type.GetProperties( BindingFlags.Instance | @@ -179,7 +179,7 @@ private static void GenerateClassInheritanceOrRealizationForInterfaces(Dictionar } } - private static void GenerateClassInheritanceForBaseType(IDictionary classLookup, ClassDiagramClass classDiagramClass) + private static void GenerateClassInheritanceForBaseType(Dictionary classLookup, ClassDiagramClass classDiagramClass) { if (classDiagramClass.Type.IsInterface) { diff --git a/src/DryGen/AspNetCoreSharedFolderResolver.cs b/src/DryGen/AspNetCoreSharedFolderResolver.cs new file mode 100644 index 00000000..43f8da60 --- /dev/null +++ b/src/DryGen/AspNetCoreSharedFolderResolver.cs @@ -0,0 +1,43 @@ +using System.IO; +using System.Linq; +using DryGen.Core; + +namespace DryGen; + +public class AspNetCoreSharedFolderResolver(string dotNetRuntimeDirectory) +{ + public string DotNetRuntimeDirectory => dotNetRuntimeDirectory.AsLinuxPath().TrimEnd('/'); + + public AspNetCoreSharedFolderResolver() : this(System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory()) + { + } + + public string? Resolve() + { + var result = DotNetRuntimeDirectory.Replace("Microsoft.NETCore.App", "Microsoft.AspNetCore.App"); + if (Directory.Exists(result)) + { + return result.AsLinuxPath(); + } + var dotNetRuntimeVersion = new DirectoryInfo(result).Name; + var baseAspNetDirectory = new DirectoryInfo(result[..result.LastIndexOf('/')]).AsNonNull(); + var bestCandidate = baseAspNetDirectory.GetDirectories().Where(x => IsCandidate(x.Name, dotNetRuntimeVersion)).OrderByDescending(x => x.Name).FirstOrDefault(); + return bestCandidate?.FullName.AsLinuxPath(); + } + + private static bool IsCandidate(string aspNetCoreVersion, string dotNetRuntimeVersion) + { + var dotNetRuntimeVersionParts = dotNetRuntimeVersion.Split(".").AsNonNull(); + var aspNetCoreVersionParts = aspNetCoreVersion.Split(".").AsNonNull(); + for (var i = 0; i < dotNetRuntimeVersionParts.Length; i++) + { + if (i >= aspNetCoreVersionParts.Length) { + continue; + } + if (aspNetCoreVersionParts[i] == dotNetRuntimeVersionParts[i]) { + return true; + } + } + return false; + } +} diff --git a/src/DryGen/Generator.cs b/src/DryGen/Generator.cs index e71d215b..0d42c775 100644 --- a/src/DryGen/Generator.cs +++ b/src/DryGen/Generator.cs @@ -421,7 +421,7 @@ private Assembly LoadAsseblyFromFile(string? inputFile) { throw new OptionsException("Input file must be specified as the option -i/--input-file on the command line, or as input-file in the option file."); } - return new InternalAssemblyLoadContext(inputFile, useAssemblyLoadContextDefault).Load(); + return new InternalAssemblyLoadContext(inputFile, useAssemblyLoadContextDefault, new AspNetCoreSharedFolderResolver()).Load(); } private static TreeShakingDiagramFilter GetMermaidDiagramTreeShakingFilter(IEnumerable? treeShakingRoots) diff --git a/src/DryGen/InternalAssemblyLoadContext.cs b/src/DryGen/InternalAssemblyLoadContext.cs index 6eb41be7..36e24602 100644 --- a/src/DryGen/InternalAssemblyLoadContext.cs +++ b/src/DryGen/InternalAssemblyLoadContext.cs @@ -12,15 +12,15 @@ internal class InternalAssemblyLoadContext : AssemblyLoadContext private AssemblyLoadContext AssemblyLoadContext => useAssemblyLoadContextDefault ? Default : this; - internal InternalAssemblyLoadContext(string inputFile, bool useAssemblyLoadContextDefault) + internal InternalAssemblyLoadContext(string inputFile, bool useAssemblyLoadContextDefault, AspNetCoreSharedFolderResolver aspNetCoreSharedFolderResolver) { this.inputFile = inputFile; this.useAssemblyLoadContextDefault = useAssemblyLoadContextDefault; var inputDirectory = Path.GetDirectoryName(inputFile) ?? throw new OptionsException($"Could not determine directory from inputFile '{inputFile}'"); - var runtimeDirectory = System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory(); - var aspNetCoreDirectoryApp = runtimeDirectory.Replace("Microsoft.NETCore.App", "Microsoft.AspNetCore.App"); - var aspNetCoreDirectoryAll = runtimeDirectory.Replace("Microsoft.NETCore.App", "Microsoft.AspNetCore.All"); - searchDirectories = new[] { inputDirectory, aspNetCoreDirectoryApp, aspNetCoreDirectoryAll, runtimeDirectory }; + var aspNetCoreDirectoryApp = aspNetCoreSharedFolderResolver.Resolve(); + searchDirectories = string.IsNullOrEmpty(aspNetCoreDirectoryApp) + ? [inputDirectory, aspNetCoreSharedFolderResolver.DotNetRuntimeDirectory] + : [inputDirectory, aspNetCoreDirectoryApp, aspNetCoreSharedFolderResolver.DotNetRuntimeDirectory]; } internal Assembly Load() @@ -35,7 +35,7 @@ internal Assembly Load() { foreach (var extension in new[] { ".dll", ".exe" }) { - foreach(var loadDirectory in searchDirectories) + foreach (var loadDirectory in searchDirectories) { var assemblyFileName = $"{loadDirectory}{Path.DirectorySeparatorChar}{assemblyName.Name}{extension}"; if (File.Exists(assemblyFileName)) diff --git a/src/develop/DryGen.Docs/ExamplesFileGenerator.cs b/src/develop/DryGen.Docs/ExamplesFileGenerator.cs index 4e93938f..daf2fa05 100644 --- a/src/develop/DryGen.Docs/ExamplesFileGenerator.cs +++ b/src/develop/DryGen.Docs/ExamplesFileGenerator.cs @@ -1,4 +1,5 @@ using System.IO; +using DryGen.Core; namespace DryGen.Docs; diff --git a/src/develop/DryGen.Docs/Extensions.cs b/src/develop/DryGen.Docs/Extensions.cs index 60a82790..d0e04b2e 100644 --- a/src/develop/DryGen.Docs/Extensions.cs +++ b/src/develop/DryGen.Docs/Extensions.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.IO; using System.Linq; +using DryGen.Core; namespace DryGen.Docs; @@ -68,11 +69,6 @@ public static string AsGeneratedVerbsDirectoryCreated(this string rootDirectory) return Path.Combine(rootDirectory.AsGeneratedDirectory(), "verbs").AsLinuxPath().CreateDirectories(); } - public static string AsLinuxPath(this string path) - { - return path.Replace("\\", "/"); - } - public static string AsRelativePathOf(this string directory, string rootDirectory) { return Path.GetRelativePath(rootDirectory, directory).AsLinuxPath(); diff --git a/src/develop/DryGen.Docs/Program.cs b/src/develop/DryGen.Docs/Program.cs index c68e5427..a7ae9439 100644 --- a/src/develop/DryGen.Docs/Program.cs +++ b/src/develop/DryGen.Docs/Program.cs @@ -1,4 +1,5 @@ using CommandLine; +using DryGen.Core; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; diff --git a/src/develop/DryGen.Docs/ReleaseNotesFileGenerator.cs b/src/develop/DryGen.Docs/ReleaseNotesFileGenerator.cs index 18ca9013..9b72f3f3 100644 --- a/src/develop/DryGen.Docs/ReleaseNotesFileGenerator.cs +++ b/src/develop/DryGen.Docs/ReleaseNotesFileGenerator.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using DryGen.Core; namespace DryGen.Docs; diff --git a/src/develop/DryGen.ITests/DryGen.ITests.csproj b/src/develop/DryGen.ITests/DryGen.ITests.csproj index be641e75..de57feda 100644 --- a/src/develop/DryGen.ITests/DryGen.ITests.csproj +++ b/src/develop/DryGen.ITests/DryGen.ITests.csproj @@ -1,7 +1,7 @@  - net8.0 + net8.0;net9.0 false diff --git a/src/develop/DryGen.UTests/DryGen.UTests.csproj b/src/develop/DryGen.UTests/DryGen.UTests.csproj index 86f7e904..42432e14 100644 --- a/src/develop/DryGen.UTests/DryGen.UTests.csproj +++ b/src/develop/DryGen.UTests/DryGen.UTests.csproj @@ -1,7 +1,7 @@  - net8.0 + net8.0;net9.0 false diff --git a/src/develop/DryGen.UTests/Features/DotNetSharedFolders.feature b/src/develop/DryGen.UTests/Features/DotNetSharedFolders.feature new file mode 100644 index 00000000..a26c36d6 --- /dev/null +++ b/src/develop/DryGen.UTests/Features/DotNetSharedFolders.feature @@ -0,0 +1,20 @@ +Feature: .Net shared folders +To be able to use dry-gen with assemblies located in the .Net shared folders +As a dry-gen user +I should get the Asp.Net Core shared folder name resolved by the .Net shared folder + + Scenario: Resolve Asp.Net Core shared folders from .Net version + Given the base .Net shared folder is 'Microsoft.NETCore.App' + Given these .Net shared folder paths + | Base folder | Sub folder | + | Microsoft.NETCore.App | <.Net version> | + | Microsoft.AspNetCore.App | | + | Microsoft.AspNetCore.App | | + When I resolve the Asp.Net Core shared folder with .Net version '<.Net version>' + Then I should get the folder Asp.Net Core shared folder 'Microsoft.AspNetCore.App/' + + Examples: + | .Net version | Asp.Net Core version 1 | Asp.Net Core version 2 | Asp.Net Core version found | + | 8.0.10 | 6.0.30 | 8.0.10 | 8.0.10 | + | 9.0.0-rc.2.24473.5 | 8.0.10 | 9.0.0-rc.2.24474.3 | 9.0.0-rc.2.24474.3 | + | 9.0.0-rc.2.24473.5/ | 8.0.10 | 9.0.0-rc.2.24474.3 | 9.0.0-rc.2.24474.3 | diff --git a/src/develop/DryGen.UTests/Steps/DotNetSharedFoldersSteps.cs b/src/develop/DryGen.UTests/Steps/DotNetSharedFoldersSteps.cs new file mode 100644 index 00000000..a6e2ec9f --- /dev/null +++ b/src/develop/DryGen.UTests/Steps/DotNetSharedFoldersSteps.cs @@ -0,0 +1,49 @@ +using System.IO; +using DryGen.Core; +using DryGen.DevUtils.Helpers; +using FluentAssertions; +using Reqnroll; + +namespace DryGen.UTests.Steps; + +[Binding] +public sealed class DotNetSharedFoldersSteps +{ + private readonly RootDirectoryContext rootDirectoryContext; + private string? aspNetSharedFolder; + private string? dotNetSharedFolder; + + public DotNetSharedFoldersSteps(RootDirectoryContext rootDirectoryContext) + { + this.rootDirectoryContext = rootDirectoryContext; + } + + [Given(@"the base .Net shared folder is '([^']*)'")] + public void GivenTheBaseDotNetShareFolderIs(string dotNetSharedFolder) + { + this.dotNetSharedFolder = dotNetSharedFolder; + } + + [Given(@"these .Net shared folder paths")] + public void GivenTheseDotNetSharedFolders(Table table) + { + table.Header.Count.Should().Be(2); + foreach(var row in table.Rows) { + rootDirectoryContext.BuldSubDirectory(rootDirectory => Path.Combine(rootDirectory, Path.Combine(row[0], row[1]))); + } + } + + [When(@"I resolve the Asp.Net Core shared folder with .Net version '([^']*)'")] + public void WhenIResolveTheAspDotNetCoreSharedFolderWithDotNetVersion(string dotNetVersion) + { + var dotNetRuntimeDirectory = Path.Combine(rootDirectoryContext.RootDirectory, Path.Combine(dotNetSharedFolder.AsNonNull(), dotNetVersion)); + aspNetSharedFolder = new AspNetCoreSharedFolderResolver(dotNetRuntimeDirectory).Resolve(); + } + + [Then(@"I should get the folder Asp.Net Core shared folder '([^']*)'")] + public void ThenIShouldGetTheFolderAspDotNetCoreSharedFolder(string expected) + { + expected = expected.AsLinuxPath(); + aspNetSharedFolder.Should().EndWith(expected); + } +} diff --git a/src/develop/DryGen.UTests/Steps/ExamplesSteps.cs b/src/develop/DryGen.UTests/Steps/ExamplesSteps.cs index 92418794..72d81126 100644 --- a/src/develop/DryGen.UTests/Steps/ExamplesSteps.cs +++ b/src/develop/DryGen.UTests/Steps/ExamplesSteps.cs @@ -4,6 +4,7 @@ using System.IO; using Reqnroll; using DryGen.DevUtils.Helpers; +using DryGen.Core; namespace DryGen.UTests.Steps; diff --git a/src/develop/DryGen.UTests/Steps/ReleaseNotesSteps.cs b/src/develop/DryGen.UTests/Steps/ReleaseNotesSteps.cs index b5517757..a1956d9f 100644 --- a/src/develop/DryGen.UTests/Steps/ReleaseNotesSteps.cs +++ b/src/develop/DryGen.UTests/Steps/ReleaseNotesSteps.cs @@ -4,6 +4,7 @@ using Reqnroll; using DryGen.DevUtils.Helpers; using FluentAssertions; +using DryGen.Core; namespace DryGen.UTests.Steps;