From 19535a7bb36e9ff9cf3fad279c040e4b92057ae6 Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Wed, 15 Jan 2025 18:33:30 -0800 Subject: [PATCH] Use AssemblyLoadContext to load EFC.Design Fixes #35265 --- .../Tasks/Internal/OperationTaskBase.cs | 17 ++- ...icrosoft.EntityFrameworkCore.Tasks.targets | 7 +- .../tools/EntityFrameworkCore.psm1 | 9 ++ src/dotnet-ef/Project.cs | 101 ++++++++++-------- src/dotnet-ef/RootCommand.cs | 7 ++ src/ef/Commands/ProjectCommandBase.cs | 62 ++++++++++- src/ef/Properties/Resources.Designer.cs | 6 ++ src/ef/Properties/Resources.resx | 3 + 8 files changed, 159 insertions(+), 53 deletions(-) diff --git a/src/EFCore.Tasks/Tasks/Internal/OperationTaskBase.cs b/src/EFCore.Tasks/Tasks/Internal/OperationTaskBase.cs index 6849955bdc2..224230ca924 100644 --- a/src/EFCore.Tasks/Tasks/Internal/OperationTaskBase.cs +++ b/src/EFCore.Tasks/Tasks/Internal/OperationTaskBase.cs @@ -30,6 +30,11 @@ public abstract class OperationTaskBase : ToolTask /// public ITaskItem? StartupAssembly { get; set; } + /// + /// The location of Microsoft.EntityFrameworkCore.Design.dll + /// + public ITaskItem? DesignAssembly { get; set; } + /// /// The target framework moniker. /// @@ -188,13 +193,17 @@ protected override string GenerateCommandLineCommands() args.Add(runtimeFrameworkVersion); } +#if !NET10_0 +#elif NET472 +#error Target framework needs to be updated here +#endif args.Add( Path.Combine( Path.GetDirectoryName(typeof(OperationTaskBase).Assembly.Location)!, "..", "..", "tools", - "netcoreapp2.0", + "net10.0", "ef.dll")); args.AddRange(AdditionalArguments); @@ -207,6 +216,12 @@ protected override string GenerateCommandLineCommands() args.Add(Path.ChangeExtension(StartupAssembly.ItemSpec, ".dll")); } + if (DesignAssembly != null) + { + args.Add("--design-assembly"); + args.Add(DesignAssembly.ItemSpec); + } + if (Project != null) { args.Add("--project"); diff --git a/src/EFCore.Tasks/buildTransitive/Microsoft.EntityFrameworkCore.Tasks.targets b/src/EFCore.Tasks/buildTransitive/Microsoft.EntityFrameworkCore.Tasks.targets index 25463696b4c..759688a46c7 100644 --- a/src/EFCore.Tasks/buildTransitive/Microsoft.EntityFrameworkCore.Tasks.targets +++ b/src/EFCore.Tasks/buildTransitive/Microsoft.EntityFrameworkCore.Tasks.targets @@ -76,7 +76,7 @@ For Publish: Properties="Configuration=$(Configuration);Platform=$(Platform);_EFGenerationStage=$(_EFGenerationStage)" /> - + $(RootNamespace) $(AssemblyName) @@ -90,9 +90,14 @@ For Publish: false + + + + metadata; - var metadataFile = Path.GetTempFileName(); - try - { - var args = new List + var args = new List { "msbuild", }; - if (framework != null) - { - args.Add($"/property:TargetFramework={framework}"); - } + if (framework != null) + { + args.Add($"/property:TargetFramework={framework}"); + } - if (configuration != null) - { - args.Add($"/property:Configuration={configuration}"); - } + if (configuration != null) + { + args.Add($"/property:Configuration={configuration}"); + } - if (runtime != null) - { - args.Add($"/property:RuntimeIdentifier={runtime}"); - } + if (runtime != null) + { + args.Add($"/property:RuntimeIdentifier={runtime}"); + } - foreach (var property in typeof(Project).GetProperties()) - { - args.Add($"/getProperty:{property.Name}"); - } + foreach (var property in typeof(Project).GetProperties()) + { + args.Add($"/getProperty:{property.Name}"); + } - args.Add("/getProperty:Platform"); + args.Add("/getProperty:Platform"); - args.Add(file); + args.Add("/t:ResolvePackageAssets"); + args.Add("/getItem:RuntimeCopyLocalItems"); - var output = new StringBuilder(); + args.Add(file); - var exitCode = Exe.Run("dotnet", args, handleOutput: line => output.AppendLine(line)); - if (exitCode != 0) - { - throw new CommandException(Resources.GetMetadataFailed); - } + var output = new StringBuilder(); - metadata = JsonSerializer.Deserialize>>(output.ToString())!["Properties"]; - } - finally + var exitCode = Exe.Run("dotnet", args, handleOutput: line => output.AppendLine(line)); + if (exitCode != 0) { - File.Delete(metadataFile); + throw new CommandException(Resources.GetMetadataFailed); } - var platformTarget = metadata[nameof(PlatformTarget)]; + var metadata = JsonSerializer.Deserialize(output.ToString())!; + + var designAssembly = metadata.Items["RuntimeCopyLocalItems"] + .Select(i => i["FullPath"]) + .FirstOrDefault(i => i.Contains("Microsoft.EntityFrameworkCore.Design", StringComparison.InvariantCulture)); + var properties = metadata.Properties; + + var platformTarget = properties[nameof(PlatformTarget)]; if (platformTarget.Length == 0) { - platformTarget = metadata["Platform"]; + platformTarget = properties["Platform"]; } return new Project(file, framework, configuration, runtime) { - AssemblyName = metadata[nameof(AssemblyName)], - Language = metadata[nameof(Language)], - OutputPath = metadata[nameof(OutputPath)], + AssemblyName = properties[nameof(AssemblyName)], + DesignAssembly = designAssembly, + Language = properties[nameof(Language)], + OutputPath = properties[nameof(OutputPath)], PlatformTarget = platformTarget, - ProjectAssetsFile = metadata[nameof(ProjectAssetsFile)], - ProjectDir = metadata[nameof(ProjectDir)], - RootNamespace = metadata[nameof(RootNamespace)], - RuntimeFrameworkVersion = metadata[nameof(RuntimeFrameworkVersion)], - TargetFileName = metadata[nameof(TargetFileName)], - TargetFrameworkMoniker = metadata[nameof(TargetFrameworkMoniker)], - Nullable = metadata[nameof(Nullable)], - TargetFramework = metadata[nameof(TargetFramework)], - TargetPlatformIdentifier = metadata[nameof(TargetPlatformIdentifier)] + ProjectAssetsFile = properties[nameof(ProjectAssetsFile)], + ProjectDir = properties[nameof(ProjectDir)], + RootNamespace = properties[nameof(RootNamespace)], + RuntimeFrameworkVersion = properties[nameof(RuntimeFrameworkVersion)], + TargetFileName = properties[nameof(TargetFileName)], + TargetFrameworkMoniker = properties[nameof(TargetFrameworkMoniker)], + Nullable = properties[nameof(Nullable)], + TargetFramework = properties[nameof(TargetFramework)], + TargetPlatformIdentifier = properties[nameof(TargetPlatformIdentifier)] }; } + private record class ProjectMetadata + { + public Dictionary Properties { get; set; } = null!; + public Dictionary[]> Items { get; set; } = null!; + } + public void Build(IEnumerable? additionalArgs) { var args = new List { "build" }; diff --git a/src/dotnet-ef/RootCommand.cs b/src/dotnet-ef/RootCommand.cs index 9bb6ed5f469..412a4d37be7 100644 --- a/src/dotnet-ef/RootCommand.cs +++ b/src/dotnet-ef/RootCommand.cs @@ -200,6 +200,13 @@ protected override int Execute(string[] _) args.Add("--framework"); args.Add(startupProject.TargetFramework!); + var designAssembly = startupProject.DesignAssembly; + if (!string.IsNullOrEmpty(designAssembly)) + { + args.Add("--design-assembly"); + args.Add(designAssembly); + } + if (_configuration.HasValue()) { args.Add("--configuration"); diff --git a/src/ef/Commands/ProjectCommandBase.cs b/src/ef/Commands/ProjectCommandBase.cs index 02fc113eef7..11b25d2183b 100644 --- a/src/ef/Commands/ProjectCommandBase.cs +++ b/src/ef/Commands/ProjectCommandBase.cs @@ -6,8 +6,9 @@ using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Tools.Properties; #if NET472 -using System; using System.Configuration; +#else +using System.Runtime.Loader; #endif namespace Microsoft.EntityFrameworkCore.Tools.Commands @@ -20,6 +21,7 @@ internal abstract class ProjectCommandBase : EFCommandBase private CommandOption? _language; private CommandOption? _nullable; private string? _efcoreVersion; + private CommandOption? _designAssembly; protected CommandOption? Assembly { get; private set; } protected CommandOption? Project { get; private set; } @@ -29,10 +31,61 @@ internal abstract class ProjectCommandBase : EFCommandBase protected CommandOption? Framework { get; private set; } protected CommandOption? Configuration { get; private set; } +#if !NET472 + private AssemblyLoadContext? _assemblyLoadContext; + protected AssemblyLoadContext AssemblyLoadContext + { + get + { + if (_assemblyLoadContext != null) + { + return _assemblyLoadContext; + } + + if (_designAssembly!.Value() != null) + { + AssemblyLoadContext.Default.Resolving += (context, name) => + { + var assemblyPath = Path.GetDirectoryName(_designAssembly!.Value())!; + assemblyPath = Path.Combine(assemblyPath, name.Name + ".dll"); + return File.Exists(assemblyPath) ? context.LoadFromAssemblyPath(assemblyPath) : null; + }; + _assemblyLoadContext = AssemblyLoadContext.Default; + } + + return AssemblyLoadContext.Default; + } + } +#endif + protected string? EFCoreVersion - => _efcoreVersion ??= System.Reflection.Assembly.Load("Microsoft.EntityFrameworkCore.Design") - .GetCustomAttribute() - ?.InformationalVersion; + { + get + { + if (_efcoreVersion != null) + { + return _efcoreVersion; + } + + Assembly? assembly = null; +#if !NET472 + assembly = AssemblyLoadContext.LoadFromAssemblyName(new AssemblyName("Microsoft.EntityFrameworkCore.Design")); +#else + if (_designAssembly!.Value() != null) + { + var assemblyPath = Path.GetDirectoryName(_designAssembly!.Value()); + assemblyPath = Path.Combine(assemblyPath, "Microsoft.EntityFrameworkCore.Design.dll"); + assembly = System.Reflection.Assembly.LoadFrom(assemblyPath); + } else + { + assembly = System.Reflection.Assembly.Load("Microsoft.EntityFrameworkCore.Design"); + } +#endif + _efcoreVersion = assembly.GetCustomAttribute() + ?.InformationalVersion; + return _efcoreVersion; + } + } public override void Configure(CommandLineApplication command) { @@ -50,6 +103,7 @@ public override void Configure(CommandLineApplication command) WorkingDir = command.Option("--working-dir ", Resources.WorkingDirDescription); Framework = command.Option("--framework ", Resources.FrameworkDescription); Configuration = command.Option("--configuration ", Resources.ConfigurationDescription); + _designAssembly = command.Option("--design-assembly ", Resources.DesignAssemblyDescription); base.Configure(command); } diff --git a/src/ef/Properties/Resources.Designer.cs b/src/ef/Properties/Resources.Designer.cs index 31f58dd2a67..873d4635f55 100644 --- a/src/ef/Properties/Resources.Designer.cs +++ b/src/ef/Properties/Resources.Designer.cs @@ -217,6 +217,12 @@ public static string DbContextType(object? type) GetString("DbContextType", nameof(type)), type); + /// + /// The location of the referenced Microsoft.EntityFrameworkCore.Design assembly. + /// + public static string DesignAssemblyDescription + => GetString("DesignAssemblyDescription"); + /// /// Your startup project '{startupProject}' doesn't reference Microsoft.EntityFrameworkCore.Design. This package is required for the Entity Framework Core Tools to work. Ensure your startup project is correct, install the package, and try again. /// diff --git a/src/ef/Properties/Resources.resx b/src/ef/Properties/Resources.resx index 5506c7c483c..a1037275dc9 100644 --- a/src/ef/Properties/Resources.resx +++ b/src/ef/Properties/Resources.resx @@ -210,6 +210,9 @@ Type: {type} + + The location of the referenced Microsoft.EntityFrameworkCore.Design assembly. + Your startup project '{startupProject}' doesn't reference Microsoft.EntityFrameworkCore.Design. This package is required for the Entity Framework Core Tools to work. Ensure your startup project is correct, install the package, and try again.