diff --git a/tools/Tools.Common/Loaders/CmdletLoader.cs b/tools/Tools.Common/Loaders/CmdletLoader.cs index e731c27bff44..6578c6b70abd 100644 --- a/tools/Tools.Common/Loaders/CmdletLoader.cs +++ b/tools/Tools.Common/Loaders/CmdletLoader.cs @@ -20,47 +20,32 @@ using Tools.Common.Models; using Tools.Common.Extensions; using System.IO; +using System.Runtime.Loader; namespace Tools.Common.Loaders { -// TODO: Remove IfDef -#if NETSTANDARD public class CmdletLoader -#else - public class CmdletLoader : MarshalByRefObject -#endif { public static ModuleMetadata ModuleMetadata; - public ModuleMetadata GetModuleMetadata(string assemblyPath, List commonOutputFolders) - { - AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => - { - foreach (var commonOutputFolder in commonOutputFolders) - { - var assemblyName = args.Name.Substring(0, args.Name.IndexOf(",")); - var dll = Directory.GetFiles(commonOutputFolder, "*.dll").FirstOrDefault(f => Path.GetFileNameWithoutExtension(f) == assemblyName); - if (dll == null) - { - continue; - } - - return Assembly.LoadFrom(dll); - } - - return null; - }; - - return GetModuleMetadata(assemblyPath); - } + /// + /// Use a custom assembly loader context to load assemblies of each module, + /// so they do not conflict with each other or build tools. + /// See https://docs.microsoft.com/en-us/dotnet/standard/assembly/unloadability + /// and https://docs.microsoft.com/en-us/powershell/scripting/dev-cross-plat/resolving-dependency-conflicts?view=powershell-7#more-robust-solutions + /// + private CmdletLoaderAlc _alc; /// /// Get the ModuleMetadata from a cmdlet assembly. /// /// Path to the cmdlet assembly. /// ModuleMetadata containing information about the cmdlets found in the given assembly. - public ModuleMetadata GetModuleMetadata(string assemblyPath) + public ModuleMetadata GetModuleMetadata(string assemblyPath, List commonOutputFolders = null) { + _alc = new CmdletLoaderAlc(commonOutputFolders); + AssemblyLoadContext.Default.Resolving += CustomResolving; + var results = new List(); ModuleMetadata = new ModuleMetadata(); @@ -208,9 +193,40 @@ public ModuleMetadata GetModuleMetadata(string assemblyPath) { throw ex; } + finally + { + AssemblyLoadContext.Default.Resolving -= CustomResolving; + } ModuleMetadata.Cmdlets = results; return ModuleMetadata; } + + private Assembly CustomResolving(AssemblyLoadContext assemblyLoadContext, AssemblyName name) => _alc.LoadFromAssemblyName(name); + } + + internal class CmdletLoaderAlc : AssemblyLoadContext + { + private readonly List _assemblyDirectories; + + public CmdletLoaderAlc(List assemblyDirectories = null) + { + _assemblyDirectories = assemblyDirectories ?? new List(); + } + + protected override Assembly Load(AssemblyName assemblyName) + { + foreach (var assemblyPath in _assemblyDirectories) + { + var dll = Directory.GetFiles(assemblyPath, "*.dll").FirstOrDefault(f => Path.GetFileNameWithoutExtension(f) == assemblyName.Name); + if (dll == null) + { + continue; + } + + return LoadFromAssemblyPath(dll); + } + return null; + } } }