Skip to content

Commit b86a8fb

Browse files
HaploisnohwndSanan07
authored
Resolve dependencies from GAC (#951)
Cherry-picked from #950 > When there are dependencies that the project installs, but they would only resolve via a reference that is located in GAC (e.g. System.ValueTuple via netstandard) then the deployment into Out folder would ignore it. > > If user then installs a version that is newer than what is in GAC it will get into their assembly redirects, but won't get copied into Out folder, resulting into TypeLoad exception for tests with DeploymentItem attribute. > > This change fixes the resolver to look through all the dependencies including GAC dependencies and then copy over only the ones that are found in the bin folder, because ultimately Out should be just a subset (or the same) as the contents in bin folder. > > Looking through GAC assemblies does not seem to add significant overhead the whole resolve is under 300ms so hopefully we don't need an option to configure enabling this. > > (The new log messages won't go into diag log, because it probably is not correctly initialized in the new appdomain that loads the dll, but they will be written to Debug Trace and can be observed by DebugView++ or DebugView.) Co-authored-by: nohwnd <[email protected]> Co-authored-by: Sanan Yuzbashiyev <[email protected]>
1 parent afabf2c commit b86a8fb

File tree

3 files changed

+38
-17
lines changed

3 files changed

+38
-17
lines changed

src/Adapter/PlatformServices.Desktop/Deployment/AssemblyLoadWorker.cs

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -46,19 +46,24 @@ public string[] GetFullPathToDependentAssemblies(string assemblyPath, out IList<
4646
Assembly assembly = null;
4747
try
4848
{
49+
EqtTrace.Verbose($"AssemblyLoadWorker.GetFullPathToDependentAssemblies: Reflection loading {assemblyPath}.");
50+
4951
// First time we load in LoadFromContext to avoid issues.
5052
assembly = this.assemblyUtility.ReflectionOnlyLoadFrom(assemblyPath);
5153
}
5254
catch (Exception ex)
5355
{
56+
EqtTrace.Error($"AssemblyLoadWorker.GetFullPathToDependentAssemblies: Reflection loading of {assemblyPath} failed:");
57+
EqtTrace.Error(ex);
58+
5459
warnings.Add(ex.Message);
5560
return new string[0]; // Otherwise just return no dependencies.
5661
}
5762

5863
Debug.Assert(assembly != null, "assembly");
5964

6065
List<string> result = new List<string>();
61-
List<string> visitedAssemblies = new List<string>();
66+
HashSet<string> visitedAssemblies = new HashSet<string>();
6267

6368
visitedAssemblies.Add(assembly.FullName);
6469

@@ -149,9 +154,11 @@ private string GetTargetFrameworkStringFromAssembly(Assembly assembly)
149154
/// <param name="result"> The result. </param>
150155
/// <param name="visitedAssemblies"> The visited Assemblies. </param>
151156
/// <param name="warnings"> The warnings. </param>
152-
private void ProcessChildren(Assembly assembly, IList<string> result, IList<string> visitedAssemblies, IList<string> warnings)
157+
private void ProcessChildren(Assembly assembly, IList<string> result, ISet<string> visitedAssemblies, IList<string> warnings)
153158
{
154159
Debug.Assert(assembly != null, "assembly");
160+
161+
EqtTrace.Verbose($"AssemblyLoadWorker.GetFullPathToDependentAssemblies: Processing assembly {assembly.FullName}.");
155162
foreach (AssemblyName reference in assembly.GetReferencedAssemblies())
156163
{
157164
this.GetDependentAssembliesInternal(reference.FullName, result, visitedAssemblies, warnings);
@@ -161,6 +168,7 @@ private void ProcessChildren(Assembly assembly, IList<string> result, IList<stri
161168
var modules = new Module[0];
162169
try
163170
{
171+
EqtTrace.Verbose($"AssemblyLoadWorker.GetFullPathToDependentAssemblies: Getting modules of {assembly.FullName}.");
164172
modules = assembly.GetModules();
165173
}
166174
catch (FileNotFoundException e)
@@ -192,13 +200,12 @@ private void ProcessChildren(Assembly assembly, IList<string> result, IList<stri
192200
continue;
193201
}
194202

195-
if (visitedAssemblies.Contains(m.Name))
203+
if (!visitedAssemblies.Add(m.Name))
196204
{
205+
// The assembly was already in the set, meaning that we already visited it.
197206
continue;
198207
}
199208

200-
visitedAssemblies.Add(m.Name);
201-
202209
if (!File.Exists(m.FullyQualifiedName))
203210
{
204211
string warning = string.Format(CultureInfo.CurrentCulture, Resource.MissingDeploymentDependencyWithoutReason, m.FullyQualifiedName);
@@ -219,20 +226,21 @@ private void ProcessChildren(Assembly assembly, IList<string> result, IList<stri
219226
/// <param name="visitedAssemblies"> The visited Assemblies. </param>
220227
/// <param name="warnings"> The warnings. </param>
221228
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")]
222-
private void GetDependentAssembliesInternal(string assemblyString, IList<string> result, IList<string> visitedAssemblies, IList<string> warnings)
229+
private void GetDependentAssembliesInternal(string assemblyString, IList<string> result, ISet<string> visitedAssemblies, IList<string> warnings)
223230
{
224231
Debug.Assert(!string.IsNullOrEmpty(assemblyString), "assemblyString");
225232

226-
if (visitedAssemblies.Contains(assemblyString))
233+
if (!visitedAssemblies.Add(assemblyString))
227234
{
235+
// The assembly was already in the hashset, so we already visited it.
228236
return;
229237
}
230238

231-
visitedAssemblies.Add(assemblyString);
232-
233239
Assembly assembly = null;
234240
try
235241
{
242+
EqtTrace.Verbose($"AssemblyLoadWorker.GetDependentAssembliesInternal: Reflection loading {assemblyString}.");
243+
236244
string postPolicyAssembly = AppDomain.CurrentDomain.ApplyPolicy(assemblyString);
237245
Debug.Assert(!string.IsNullOrEmpty(postPolicyAssembly), "postPolicyAssembly");
238246

@@ -241,17 +249,15 @@ private void GetDependentAssembliesInternal(string assemblyString, IList<string>
241249
}
242250
catch (Exception ex)
243251
{
252+
EqtTrace.Error($"AssemblyLoadWorker.GetDependentAssembliesInternal: Reflection loading {assemblyString} failed:.");
253+
EqtTrace.Error(ex);
254+
244255
string warning = string.Format(CultureInfo.CurrentCulture, Resource.MissingDeploymentDependency, assemblyString, ex.Message);
245256
warnings.Add(warning);
246257
return;
247258
}
248259

249-
// As soon as we find GAC or internal assembly we do not look further.
250-
if (assembly.GlobalAssemblyCache)
251-
{
252-
return;
253-
}
254-
260+
EqtTrace.Verbose($"AssemblyLoadWorker.GetDependentAssembliesInternal: Assembly {assemblyString} was added as dependency.");
255261
result.Add(assembly.Location);
256262

257263
this.ProcessChildren(assembly, result, visitedAssemblies, warnings);

src/Adapter/PlatformServices.Desktop/Utilities/DesktopAssemblyUtility.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,8 @@ internal virtual string[] GetFullPathToDependentAssemblies(string assemblyPath,
190190
EqtTrace.InfoIf(EqtTrace.IsInfoEnabled, "AssemblyDependencyFinder.GetDependentAssemblies: start.");
191191

192192
AppDomainSetup setupInfo = new AppDomainSetup();
193-
setupInfo.ApplicationBase = Path.GetDirectoryName(Path.GetFullPath(assemblyPath));
193+
var dllDirectory = Path.GetDirectoryName(Path.GetFullPath(assemblyPath));
194+
setupInfo.ApplicationBase = dllDirectory;
194195

195196
Debug.Assert(string.IsNullOrEmpty(configFile) || File.Exists(configFile), "Config file is specified but does not exist: {0}", configFile);
196197

@@ -227,7 +228,18 @@ internal virtual string[] GetFullPathToDependentAssemblies(string assemblyPath,
227228

228229
EqtTrace.InfoIf(EqtTrace.IsInfoEnabled, "AssemblyDependencyFinder.GetDependentAssemblies: loaded the worker.");
229230

230-
return worker.GetFullPathToDependentAssemblies(assemblyPath, out warnings);
231+
var allDependencies = worker.GetFullPathToDependentAssemblies(assemblyPath, out warnings);
232+
var dependenciesFromDllDirectory = new List<string>();
233+
var dllDirectoryUppercase = dllDirectory.ToUpperInvariant();
234+
foreach (var dependency in allDependencies)
235+
{
236+
if (dependency.ToUpperInvariant().Contains(dllDirectoryUppercase))
237+
{
238+
dependenciesFromDllDirectory.Add(dependency);
239+
}
240+
}
241+
242+
return dependenciesFromDllDirectory.ToArray();
231243
}
232244
}
233245
finally

src/Adapter/PlatformServices.Desktop/Utilities/DesktopDeploymentUtility.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,8 @@ private void AddDependencies(string testSource, string configFile, IList<Deploym
219219
Debug.Assert(deploymentItems != null, "deploymentItems should not be null.");
220220
Debug.Assert(Path.IsPathRooted(testSource), "path should be rooted.");
221221

222+
var sw = Stopwatch.StartNew();
223+
222224
// Note: if this is not an assembly we simply return empty array, also:
223225
// we do recursive search and report missing.
224226
string[] references = this.AssemblyUtility.GetFullPathToDependentAssemblies(testSource, configFile, out var warningList);
@@ -230,6 +232,7 @@ private void AddDependencies(string testSource, string configFile, IList<Deploym
230232
if (EqtTrace.IsInfoEnabled)
231233
{
232234
EqtTrace.Info("DeploymentManager: Source:{0} has following references", testSource);
235+
EqtTrace.Info("DeploymentManager: Resolving dependencies took {0} ms", sw.ElapsedMilliseconds);
233236
}
234237

235238
foreach (string reference in references)

0 commit comments

Comments
 (0)