Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 43 additions & 27 deletions tools/Tools.Common/Loaders/CmdletLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> 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);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assembly.LoadFrom() will load the dll into the default ALC, so the dlls may conflict.

}

return null;
};

return GetModuleMetadata(assemblyPath);
}
/// <summary>
/// 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
/// </summary>
private CmdletLoaderAlc _alc;

/// <summary>
/// Get the ModuleMetadata from a cmdlet assembly.
/// </summary>
/// <param name="assmeblyPath">Path to the cmdlet assembly.</param>
/// <returns>ModuleMetadata containing information about the cmdlets found in the given assembly.</returns>
public ModuleMetadata GetModuleMetadata(string assemblyPath)
public ModuleMetadata GetModuleMetadata(string assemblyPath, List<string> commonOutputFolders = null)
{
_alc = new CmdletLoaderAlc(commonOutputFolders);
AssemblyLoadContext.Default.Resolving += CustomResolving;

var results = new List<CmdletMetadata>();

ModuleMetadata = new ModuleMetadata();
Expand Down Expand Up @@ -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<string> _assemblyDirectories;

public CmdletLoaderAlc(List<string> assemblyDirectories = null)
{
_assemblyDirectories = assemblyDirectories ?? new List<string>();
}

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);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The assembly resolving logic is the same as before. I just use AssemblyLoadContext.LoadFromAssemblyPath() to load the dll into individual ALCs

}
return null;
}
}
}