Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve loading of Roslyn assemblies on validate package task #22277

Merged
merged 2 commits into from
Oct 23, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,23 @@

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using Microsoft.Build.Framework;
using Microsoft.DotNet.PackageValidation;
using Microsoft.NET.Build.Tasks;
using NuGet.RuntimeModel;
#if NETCOREAPP
using System.Runtime.Loader;
#endif

namespace Microsoft.DotNet.Compatibility
{
public class ValidatePackage : TaskBase
{
private const string CODE_ANALYSIS_ASSEMBLY_NAME = "Microsoft.CodeAnalysis";
safern marked this conversation as resolved.
Show resolved Hide resolved
private const string CODE_ANALYSIS_CSHARP_ASSEMBLY_NAME = "Microsoft.CodeAnalysis.CSharp";

[Required]
public string PackageTargetPath { get; set; }

Expand Down Expand Up @@ -43,14 +48,23 @@ public class ValidatePackage : TaskBase

public override bool Execute()
{
#if NETCOREAPP
AssemblyLoadContext currentContext = AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly());
currentContext.Resolving += ResolverForRoslyn;
#else
AppDomain.CurrentDomain.AssemblyResolve += ResolverForRoslyn;
#endif
try
{
return base.Execute();
}
finally
{
#if NETCOREAPP
currentContext.Resolving -= ResolverForRoslyn;
#else
AppDomain.CurrentDomain.AssemblyResolve -= ResolverForRoslyn;
#endif
}
}

Expand Down Expand Up @@ -103,20 +117,60 @@ protected override void ExecuteCore()
}
}

#if NETCOREAPP
private Assembly ResolverForRoslyn(AssemblyLoadContext context, AssemblyName assemblyName)
{
if (assemblyName.Name.StartsWith(CODE_ANALYSIS_ASSEMBLY_NAME))
safern marked this conversation as resolved.
Show resolved Hide resolved
{
(string requested, string extra) = GetRoslynPathsToLoad(assemblyName.Name);
safern marked this conversation as resolved.
Show resolved Hide resolved
Assembly asm = context.LoadFromAssemblyPath(requested);
ThrowIfVersionIsLower(assemblyName.Version, asm.GetName().Version);

// Being extra defensive but we want to avoid that we accidentally load two different versions of either
// of the roslyn assemblies from a different location, so let's load them both on the first request.
Assembly _ = context.LoadFromAssemblyPath(extra);

return asm;
}

return null;
}
#else
private Assembly ResolverForRoslyn(object sender, ResolveEventArgs args)
{
AssemblyName name = new(args.Name);
if (name.Name == "Microsoft.CodeAnalysis" || name.Name == "Microsoft.CodeAnalysis.CSharp")
if (name.Name.StartsWith(CODE_ANALYSIS_ASSEMBLY_NAME))
{
Assembly asm = Assembly.LoadFrom(Path.Combine(RoslynAssembliesPath, $"{name.Name}.dll"));
Version version = asm.GetName().Version;
if (version < name.Version)
{
throw new Exception(string.Format(Resources.UpdateSdkVersion, version, name.Version));
}
(string requested, string extra) = GetRoslynPathsToLoad(name.Name);
Assembly asm = Assembly.LoadFrom(requested);
ThrowIfVersionIsLower(name.Version, asm.GetName().Version);

// Being extra defensive but we want to avoid that we accidentally load two different versions of either
// of the roslyn assemblies from a different location, so let's load them both on the first request.
Assembly _ = Assembly.LoadFrom(extra);

return asm;
}
return null;
}
#endif

private (string requested, string extra) GetRoslynPathsToLoad(string name)
{
string requested = Path.Combine(RoslynAssembliesPath, $"{name}.dll");
string extra = name == CODE_ANALYSIS_ASSEMBLY_NAME ?
Path.Combine(RoslynAssembliesPath, $"{CODE_ANALYSIS_CSHARP_ASSEMBLY_NAME}.dll") :
Path.Combine(RoslynAssembliesPath, $"{CODE_ANALYSIS_ASSEMBLY_NAME}.dll");

return (requested, extra);
}

private static void ThrowIfVersionIsLower(Version expected, Version actual)
{
if (actual < expected)
{
throw new Exception(string.Format(Resources.UpdateSdkVersion, actual, expected));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@
<PropertyGroup Condition="'$(RoslynAssembliesPath)' == ''">
<RoslynAssembliesPath>$(RoslynTargetsPath)</RoslynAssembliesPath>
<_packageReferenceList>@(PackageReference)</_packageReferenceList>
<RoslynAssembliesPath Condition="'$(MSBuildProjectExtension)' == '.csproj' and $(_packageReferenceList.Contains('Microsoft.Net.Compilers.Toolset'))">$([System.IO.Path]::GetDirectoryName($(CSharpCoreTargetsPath)))</RoslynAssembliesPath>
<RoslynAssembliesPath Condition="'$(MSBuildProjectExtension)' == '.vbproj' and $(_packageReferenceList.Contains('Microsoft.Net.Compilers.Toolset'))">$([System.IO.Path]::GetDirectoryName($(VisualBasicCoreTargetsPath)))</RoslynAssembliesPath>
<!-- CSharpCoreTargetsPath and VisualBasicCoreTargetsPath point to the same location, Microsoft.CodeAnalysis.CSharp and Microsoft.CodeAnalysis.VisualBasic
safern marked this conversation as resolved.
Show resolved Hide resolved
are on the same directory as Microsoft.CodeAnalysis. So there is no need to distinguish between csproj or vbproj. -->
<RoslynAssembliesPath Condition="$(_packageReferenceList.Contains('Microsoft.Net.Compilers.Toolset'))">$([System.IO.Path]::GetDirectoryName($(CSharpCoreTargetsPath)))</RoslynAssembliesPath>
<RoslynAssembliesPath Condition="'$(MSBuildRuntimeType)' == 'Core'">$([System.IO.Path]::Combine('$(RoslynAssembliesPath)', bincore))</RoslynAssembliesPath>
</PropertyGroup>

Expand Down