Skip to content

Commit

Permalink
Per-plugin AssemblyLoadContext
Browse files Browse the repository at this point in the history
Augment MSBuild's handling of assembly loading for plugins (currently tasks,
SDK resolvers, and loggers) to use an AssemblyLoadContext, allowing
plugins to use different versions of dependencies.

Creates a new ALC per assembly path (of initially-loaded assembly, not
indirect dependencies).

Closes #1754. Closes #4635.
  • Loading branch information
rainersigwald committed Nov 14, 2019
1 parent c3fc53d commit 90ac0c3
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 12 deletions.
3 changes: 3 additions & 0 deletions src/Build/Microsoft.Build.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,9 @@
<Compile Include="..\Shared\TypeLoader.cs">
<Link>SharedUtilities\TypeLoader.cs</Link>
</Compile>
<Compile Include="..\Shared\MSBuildLoadContext.cs" Condition="'$(TargetFrameworkIdentifier)'!='.NETFramework'">
<Link>SharedUtilities\MSBuildLoadContext.cs</Link>
</Compile>
<Compile Include="..\Shared\VisualStudioConstants.cs">
<Link>VisualStudioConstants.cs</Link>
</Compile>
Expand Down
1 change: 1 addition & 0 deletions src/MSBuild/MSBuild.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@
<Compile Include="..\Shared\OutOfProcTaskHostTaskResult.cs" />
<Compile Include="..\Shared\TaskHostTaskCancelled.cs" />
<Compile Include="..\Shared\TaskLoader.cs" />
<Compile Include="..\Shared\MSBuildLoadContext.cs" Condition="'$(TargetFrameworkIdentifier)'!='.NETFramework'" />
<Compile Include="..\Shared\TypeLoader.cs" />
<Compile Include="..\Shared\LoadedType.cs">
<ExcludeFromStyleCop>true</ExcludeFromStyleCop>
Expand Down
16 changes: 4 additions & 12 deletions src/Shared/CoreCLRAssemblyLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.Loader;
using Microsoft.Build.Shared.FileSystem;

namespace Microsoft.Build.Shared
{
Expand All @@ -20,8 +18,6 @@ internal sealed class CoreClrAssemblyLoader
private readonly HashSet<string> _dependencyPaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private readonly object _guard = new object();

private bool _resolvingHandlerHookedUp = false;

private static readonly string[] _extensions = new[] { "ni.dll", "ni.exe", "dll", "exe" };
private static readonly Version _currentAssemblyVersion = new Version(Microsoft.Build.Shared.MSBuildConstants.CurrentAssemblyVersion);
private static readonly HashSet<string> _wellKnownAssemblyNames = new HashSet<string>(
Expand Down Expand Up @@ -57,12 +53,6 @@ public Assembly LoadFromPath(string fullPath)

lock (_guard)
{
if (!_resolvingHandlerHookedUp)
{
AssemblyLoadContext.Default.Resolving += TryResolveAssembly;
_resolvingHandlerHookedUp = true;
}

Assembly assembly;
if (_pathsToAssemblies.TryGetValue(fullPath, out assembly))
{
Expand Down Expand Up @@ -150,9 +140,11 @@ private Assembly TryResolveAssemblyFromPaths(AssemblyLoadContext context, Assemb
/// <remarks>
/// Assumes we have a lock on _guard
/// </remarks>
private Assembly LoadAndCache(AssemblyLoadContext context, string fullPath)
private Assembly LoadAndCache(AssemblyLoadContext _, string fullPath)
{
var assembly = context.LoadFromAssemblyPath(fullPath);
var cxt = new MSBuildLoadContext(fullPath);

var assembly = cxt.LoadFromAssemblyPath(fullPath);
var name = assembly.FullName;

_pathsToAssemblies[fullPath] = assembly;
Expand Down
68 changes: 68 additions & 0 deletions src/Shared/MSBuildLoadContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.


using System;
using System.Collections.Immutable;
using System.Reflection;
using System.Runtime.Loader;

#nullable enable
namespace Microsoft.Build.Shared
{
/// <summary>
/// This class is used to isolate the types used by an MSBuild plugin
/// (SDK resolver, logger, or task).
/// </summary>
internal class MSBuildLoadContext : AssemblyLoadContext
{
private readonly AssemblyDependencyResolver _resolver;
private readonly string _directory;

private static readonly ImmutableHashSet<string> _wellKnownAssemblyNames =
new[]
{
"MSBuild",
"Microsoft.Build",
"Microsoft.Build.Framework",
"Microsoft.Build.Tasks.Core",
"Microsoft.Build.Utilities.Core",
}.ToImmutableHashSet();


public MSBuildLoadContext(string assemblyPath) :
base(name: assemblyPath, isCollectible: false) // TODO: make this collectible?
{
_directory = assemblyPath;
_resolver = new AssemblyDependencyResolver(_directory);
}

protected override Assembly? Load(AssemblyName assemblyName)
{
if (_wellKnownAssemblyNames.Contains(assemblyName.Name!))
{
return null;
}

string? assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
if (assemblyPath != null)
{
return LoadFromAssemblyPath(assemblyPath);
}

return null;
}

protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
{
string? libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
if (libraryPath != null)
{
return LoadUnmanagedDllFromPath(libraryPath);
}

return IntPtr.Zero;
}

}
}

0 comments on commit 90ac0c3

Please sign in to comment.