Skip to content

Commit 67cdc9a

Browse files
authored
Merge pull request #1128 from JoeRobich/use-dependency-resolver
Use AssemblyDependencyResolver when loading analyzers
2 parents 3070d45 + bea77b3 commit 67cdc9a

File tree

2 files changed

+59
-37
lines changed

2 files changed

+59
-37
lines changed

src/Analyzers/AnalyzerReferenceInformationProvider.cs

Lines changed: 56 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ namespace Microsoft.CodeAnalysis.Tools.Analyzers
1515
internal class AnalyzerReferenceInformationProvider : IAnalyzerInformationProvider
1616
{
1717
private static readonly Dictionary<string, Assembly> s_pathsToAssemblies = new(StringComparer.OrdinalIgnoreCase);
18+
private static readonly Dictionary<string, Assembly> s_namesToAssemblies = new(StringComparer.OrdinalIgnoreCase);
19+
1820
private static readonly object s_guard = new();
1921

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

5456
try
5557
{
56-
var analyzerDirectory = new DirectoryInfo(Path.GetDirectoryName(path));
57-
58-
// Analyzer packages will put language specific assemblies in subfolders.
59-
if (analyzerDirectory.Name == "cs" || analyzerDirectory.Name == "vb")
60-
{
61-
// Get the root analyzer folder.
62-
analyzerDirectory = analyzerDirectory.Parent;
63-
}
64-
65-
var context = new AnalyzerLoadContext(analyzerDirectory.FullName);
66-
67-
// First try loading the assembly from disk.
58+
var context = new AnalyzerLoadContext(path);
6859
var assembly = context.LoadFromAssemblyPath(path);
6960

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

77-
// Give up.
7868
return null;
7969
}
8070

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

87-
public AnalyzerLoadContext(string assemblyFolderPath)
78+
public AnalyzerLoadContext(string assemblyPath)
8879
{
89-
AssemblyFolderPath = assemblyFolderPath;
80+
var analyzerDirectory = new DirectoryInfo(Path.GetDirectoryName(assemblyPath));
81+
82+
// Analyzer packages will put language specific assemblies in subfolders.
83+
if (analyzerDirectory.Name == "cs" || analyzerDirectory.Name == "vb")
84+
{
85+
// Get the root analyzer folder.
86+
analyzerDirectory = analyzerDirectory.Parent;
87+
}
88+
89+
AssemblyFolderPath = analyzerDirectory.FullName;
90+
DependencyResolver = new AssemblyDependencyResolver(assemblyPath);
9091
}
9192

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

100+
assembly = TryLoad(assemblyName);
101+
if (assembly is not null)
102+
{
103+
s_namesToAssemblies[assemblyName.FullName] = assembly;
104+
}
105+
106+
return assembly;
107+
}
108+
109+
private Assembly? TryLoad(AssemblyName assemblyName)
110+
{
111+
// If the analyzer was packaged with a .deps.json file which described
112+
// its dependencies, then the DependencyResolver should locate them for us.
113+
var resolvedPath = DependencyResolver.ResolveAssemblyToPath(assemblyName);
114+
if (resolvedPath is not null)
115+
{
116+
return LoadFromAssemblyPath(resolvedPath);
117+
}
118+
119+
// The dependency resolver failed to locate the dependency so fall back to inspecting
120+
// the analyzer package folder.
98121
foreach (var searchPath in
99122
new[]
100123
{
@@ -116,8 +139,20 @@ protected override Assembly Load(AssemblyName assemblyName)
116139
catch { }
117140
}
118141

119-
// Try to load the requested assembly from the default load context.
120-
return AssemblyLoadContext.Default.LoadFromAssemblyName(assemblyName);
142+
return null;
143+
}
144+
145+
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
146+
{
147+
// If the analyzer was packaged with a .deps.json file which described
148+
// its dependencies, then the DependencyResolver should locate them for us.
149+
var resolvedPath = DependencyResolver.ResolveUnmanagedDllToPath(unmanagedDllName);
150+
if (resolvedPath is not null)
151+
{
152+
return LoadUnmanagedDllFromPath(resolvedPath);
153+
}
154+
155+
return base.LoadUnmanagedDll(unmanagedDllName);
121156
}
122157
}
123158
}

src/Utilities/AssemblyResolver.cs

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -42,24 +42,11 @@ internal static class AssemblyResolver
4242
continue;
4343
}
4444

45-
try
46-
{
47-
var assembly = context.LoadFromAssemblyPath(candidatePath);
45+
var assembly = context.LoadFromAssemblyPath(candidatePath);
4846

49-
logger?.LogTrace($"Loaded assembly from {candidatePath}.");
47+
logger?.LogTrace($"Loaded assembly from {candidatePath}.");
5048

51-
return assembly;
52-
}
53-
catch
54-
{
55-
if (assemblyName.Name != null)
56-
{
57-
// We were unable to load the assembly from the file path. It is likely that
58-
// a different version of the assembly has already been loaded into the context.
59-
// Be forgiving and attempt to load assembly by name without specifying a version.
60-
return context.LoadFromAssemblyName(new AssemblyName(assemblyName.Name));
61-
}
62-
}
49+
return assembly;
6350
}
6451
}
6552

0 commit comments

Comments
 (0)