Skip to content

Use AssemblyDependencyResolver when loading analyzers #1128

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

Merged
merged 1 commit into from
Jun 17, 2021
Merged
Show file tree
Hide file tree
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
77 changes: 56 additions & 21 deletions src/Analyzers/AnalyzerReferenceInformationProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ namespace Microsoft.CodeAnalysis.Tools.Analyzers
internal class AnalyzerReferenceInformationProvider : IAnalyzerInformationProvider
{
private static readonly Dictionary<string, Assembly> s_pathsToAssemblies = new(StringComparer.OrdinalIgnoreCase);
private static readonly Dictionary<string, Assembly> s_namesToAssemblies = new(StringComparer.OrdinalIgnoreCase);

private static readonly object s_guard = new();

public ImmutableDictionary<ProjectId, AnalyzersAndFixers> GetAnalyzersAndFixers(
Expand Down Expand Up @@ -53,18 +55,7 @@ private AnalyzersAndFixers GetAnalyzersAndFixers(Project project)

try
{
var analyzerDirectory = new DirectoryInfo(Path.GetDirectoryName(path));

// Analyzer packages will put language specific assemblies in subfolders.
if (analyzerDirectory.Name == "cs" || analyzerDirectory.Name == "vb")
{
// Get the root analyzer folder.
analyzerDirectory = analyzerDirectory.Parent;
}

var context = new AnalyzerLoadContext(analyzerDirectory.FullName);

// First try loading the assembly from disk.
var context = new AnalyzerLoadContext(path);
var assembly = context.LoadFromAssemblyPath(path);

s_pathsToAssemblies.Add(path, assembly);
Expand All @@ -74,7 +65,6 @@ private AnalyzersAndFixers GetAnalyzersAndFixers(Project project)
catch { }
}

// Give up.
return null;
}

Expand All @@ -83,18 +73,51 @@ private AnalyzersAndFixers GetAnalyzersAndFixers(Project project)
internal sealed class AnalyzerLoadContext : AssemblyLoadContext
{
internal string AssemblyFolderPath { get; }
internal AssemblyDependencyResolver DependencyResolver { get; }

public AnalyzerLoadContext(string assemblyFolderPath)
public AnalyzerLoadContext(string assemblyPath)
{
AssemblyFolderPath = assemblyFolderPath;
var analyzerDirectory = new DirectoryInfo(Path.GetDirectoryName(assemblyPath));

// Analyzer packages will put language specific assemblies in subfolders.
if (analyzerDirectory.Name == "cs" || analyzerDirectory.Name == "vb")
{
// Get the root analyzer folder.
analyzerDirectory = analyzerDirectory.Parent;
}

AssemblyFolderPath = analyzerDirectory.FullName;
DependencyResolver = new AssemblyDependencyResolver(assemblyPath);
}

protected override Assembly Load(AssemblyName assemblyName)
protected override Assembly? Load(AssemblyName assemblyName)
{
// Since we build against .NET Core 2.1 we do not have access to the
// AssemblyDependencyResolver which resolves depenendency assembly paths
// from AssemblyName by using the .deps.json.
if (s_namesToAssemblies.TryGetValue(assemblyName.FullName, out var assembly))
{
return assembly;
}

assembly = TryLoad(assemblyName);
if (assembly is not null)
{
s_namesToAssemblies[assemblyName.FullName] = assembly;
}

return assembly;
}

private Assembly? TryLoad(AssemblyName assemblyName)
{
// If the analyzer was packaged with a .deps.json file which described
// its dependencies, then the DependencyResolver should locate them for us.
var resolvedPath = DependencyResolver.ResolveAssemblyToPath(assemblyName);
if (resolvedPath is not null)
{
return LoadFromAssemblyPath(resolvedPath);
}

// The dependency resolver failed to locate the dependency so fall back to inspecting
// the analyzer package folder.
foreach (var searchPath in
new[]
{
Expand All @@ -116,8 +139,20 @@ protected override Assembly Load(AssemblyName assemblyName)
catch { }
}

// Try to load the requested assembly from the default load context.
return AssemblyLoadContext.Default.LoadFromAssemblyName(assemblyName);
return null;
}

protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
{
// If the analyzer was packaged with a .deps.json file which described
// its dependencies, then the DependencyResolver should locate them for us.
var resolvedPath = DependencyResolver.ResolveUnmanagedDllToPath(unmanagedDllName);
if (resolvedPath is not null)
{
return LoadUnmanagedDllFromPath(resolvedPath);
}

return base.LoadUnmanagedDll(unmanagedDllName);
}
}
}
Expand Down
19 changes: 3 additions & 16 deletions src/Utilities/AssemblyResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,24 +42,11 @@ internal static class AssemblyResolver
continue;
}

try
{
var assembly = context.LoadFromAssemblyPath(candidatePath);
var assembly = context.LoadFromAssemblyPath(candidatePath);

logger?.LogTrace($"Loaded assembly from {candidatePath}.");
logger?.LogTrace($"Loaded assembly from {candidatePath}.");

return assembly;
}
catch
{
if (assemblyName.Name != null)
{
// We were unable to load the assembly from the file path. It is likely that
// a different version of the assembly has already been loaded into the context.
// Be forgiving and attempt to load assembly by name without specifying a version.
return context.LoadFromAssemblyName(new AssemblyName(assemblyName.Name));
}
}
return assembly;
}
}

Expand Down