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

Annotate RAR SearchPaths added because they're "next to a reference" #9700

Merged
merged 15 commits into from
Feb 7, 2024
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
27 changes: 25 additions & 2 deletions src/Tasks.UnitTests/AssemblyDependency/Miscellaneous.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3237,7 +3237,7 @@ public void ResolveToGACSpecificVersion()
[Fact]
public void ParentAssemblyResolvedFromAForGac()
{
var parentReferenceFolders = new List<string>();
var parentReferenceFolders = new List<DirectoryWithParentAssembly>();
var referenceList = new List<Reference>();

var taskItem = new TaskItem("Microsoft.VisualStudio.Interopt, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
Expand Down Expand Up @@ -3266,7 +3266,7 @@ public void ParentAssemblyResolvedFromAForGac()
}

Assert.Single(parentReferenceFolders);
Assert.Equal(reference2.ResolvedSearchPath, parentReferenceFolders[0]);
Assert.Equal(reference2.ResolvedSearchPath, parentReferenceFolders[0].Directory);
}

/// <summary>
Expand Down Expand Up @@ -8623,6 +8623,29 @@ public void SDKReferencesAreResolvedWithoutIO()
rar._cache.IsDirty.ShouldBeFalse();
}

[Fact]
public void LogsParentAssemblyForEveryConsideredAndRejectedSearchPath()
{
InitializeRARwithMockEngine(_output, out MockEngine mockEngine, out ResolveAssemblyReference rar);

rar.Assemblies = new ITaskItem[]
{
new TaskItem(@"C:\DirectoryTest\A.dll"),
new TaskItem("B"),
};

rar.SearchPaths = new string[]
{
"{RawFileName}",
};

Execute(rar).ShouldBeTrue();

mockEngine.AssertLogContains(rar.Log.FormatResourceString("ResolveAssemblyReference.SearchPathAddedByParentAssembly",
@"C:\DirectoryTest",
@"C:\DirectoryTest\A.dll"));
}

[Fact]
public void ManagedRuntimeVersionReaderSupportsWindowsRuntime()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,9 @@ internal void StopIOMonitoring()
s_netstandardDllPath,
@"C:\SystemRuntime\Regular.dll",
s_dependsOnNuGet_ADllPath,
s_nugetCache_N_Lib_NDllPath
s_nugetCache_N_Lib_NDllPath,
@"C:\DirectoryTest\A.dll",
@"C:\DirectoryTest\B.dll",
};

/// <summary>
Expand Down Expand Up @@ -2482,6 +2484,19 @@ internal static AssemblyNameExtension[] GetDependencies(string path)
};
}

if (String.Equals(path, @"C:\DirectoryTest\A.dll", StringComparison.OrdinalIgnoreCase))
{
return new AssemblyNameExtension[]
{
GetAssemblyName(@"C:\DirectoryTest\B.dll")
};
}

if (String.Equals(path, @"C:\DirectoryTest\B.dll", StringComparison.OrdinalIgnoreCase))
{
return Array.Empty<AssemblyNameExtension>();
}

// Use a default list.
return new AssemblyNameExtension[]
{
Expand Down
12 changes: 7 additions & 5 deletions src/Tasks/AssemblyDependency/AssemblyResolution.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

namespace Microsoft.Build.Tasks
{
internal readonly record struct DirectoryWithParentAssembly(string Directory, string ParentAssembly);

/// <summary>
/// Utility class encapsulates steps to resolve assembly references.
/// For example, this class has the code that will take:
Expand Down Expand Up @@ -203,7 +205,7 @@ public static Resolver[] CompileSearchPaths(
}
else
{
resolvers[p] = new DirectoryResolver(searchPaths[p], getAssemblyName, fileExists, getRuntimeVersion, targetedRuntimeVersion);
resolvers[p] = new DirectoryResolver(searchPaths[p], getAssemblyName, fileExists, getRuntimeVersion, targetedRuntimeVersion, null);
}
}
return resolvers;
Expand All @@ -213,16 +215,16 @@ public static Resolver[] CompileSearchPaths(
/// Build a resolver array from a set of directories to resolve directly from.
/// </summary>
internal static Resolver[] CompileDirectories(
List<string> directories,
List<DirectoryWithParentAssembly> parentReferenceDirectories,
FileExists fileExists,
GetAssemblyName getAssemblyName,
GetAssemblyRuntimeVersion getRuntimeVersion,
Version targetedRuntimeVersion)
{
var resolvers = new Resolver[directories.Count];
for (int i = 0; i < directories.Count; i++)
var resolvers = new Resolver[parentReferenceDirectories.Count];
for (int i = 0; i < parentReferenceDirectories.Count; i++)
{
resolvers[i] = new DirectoryResolver(directories[i], getAssemblyName, fileExists, getRuntimeVersion, targetedRuntimeVersion);
resolvers[i] = new DirectoryResolver(parentReferenceDirectories[i].Directory, getAssemblyName, fileExists, getRuntimeVersion, targetedRuntimeVersion, parentReferenceDirectories[i].ParentAssembly);
}

return resolvers;
Expand Down
31 changes: 28 additions & 3 deletions src/Tasks/AssemblyDependency/DirectoryResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,18 @@ namespace Microsoft.Build.Tasks
/// </summary>
internal class DirectoryResolver : Resolver
{
/// <summary>
/// The parent assembly that was used for the SearchPath.
/// </summary>
public readonly string parentAssembly;

/// <summary>
/// Construct.
/// </summary>
public DirectoryResolver(string searchPathElement, GetAssemblyName getAssemblyName, FileExists fileExists, GetAssemblyRuntimeVersion getRuntimeVersion, Version targetedRuntimeVesion)
public DirectoryResolver(string searchPathElement, GetAssemblyName getAssemblyName, FileExists fileExists, GetAssemblyRuntimeVersion getRuntimeVersion, Version targetedRuntimeVesion, string parentAssembly)
: base(searchPathElement, getAssemblyName, fileExists, getRuntimeVersion, targetedRuntimeVesion, System.Reflection.ProcessorArchitecture.None, false)
{
this.parentAssembly = parentAssembly;
}

/// <inheritdoc/>
Expand All @@ -40,8 +46,27 @@ public override bool Resolve(
foundPath = null;
userRequestedSpecificFile = false;

// Resolve to the given path.
string resolvedPath = ResolveFromDirectory(assemblyName, isPrimaryProjectReference, wantSpecificVersion, executableExtensions, searchPathElement, assembliesConsideredAndRejected);
string resolvedPath;

if (parentAssembly != null)
{
var searchLocationsWithParentAssembly = new List<ResolutionSearchLocation>();

// Resolve to the given path.
resolvedPath = ResolveFromDirectory(assemblyName, isPrimaryProjectReference, wantSpecificVersion, executableExtensions, searchPathElement, searchLocationsWithParentAssembly);

foreach (var searchLocation in searchLocationsWithParentAssembly)
{
searchLocation.ParentAssembly = parentAssembly;
}

assembliesConsideredAndRejected.AddRange(searchLocationsWithParentAssembly);
}
else
{
resolvedPath = ResolveFromDirectory(assemblyName, isPrimaryProjectReference, wantSpecificVersion, executableExtensions, searchPathElement, assembliesConsideredAndRejected);
}

if (resolvedPath != null)
{
foundPath = resolvedPath;
Expand Down
8 changes: 4 additions & 4 deletions src/Tasks/AssemblyDependency/ReferenceTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1220,7 +1220,7 @@ private static bool IsPseudoAssembly(string name)
/// The only time we do not want to do this is if the parent assembly came from the GAC or AssemblyFoldersEx then we want the assembly
/// to be found using those resolvers so that our GAC and AssemblyFolders checks later on will work on those assemblies.
/// </summary>
internal static void CalculateParentAssemblyDirectories(List<string> parentReferenceFolders, Reference parentReference)
internal static void CalculateParentAssemblyDirectories(List<DirectoryWithParentAssembly> parentReferenceFolders, Reference parentReference)
{
string parentReferenceFolder = parentReference.DirectoryName;
string parentReferenceResolvedSearchPath = parentReference.ResolvedSearchPath;
Expand All @@ -1240,7 +1240,7 @@ internal static void CalculateParentAssemblyDirectories(List<string> parentRefer
if (!parentReferencesAdded.Contains(parentReferenceFolder) && !parentReferenceResolvedFromGAC && !parentReferenceResolvedFromAssemblyFolders)
{
parentReferencesAdded.Add(parentReferenceFolder);
parentReferenceFolders.Add(parentReferenceFolder);
parentReferenceFolders.Add(new (Directory: parentReferenceFolder, ParentAssembly: parentReference.FullPath));
}
}

Expand Down Expand Up @@ -1279,7 +1279,7 @@ private void ResolveReference(
// First, look for the dependency in the parents' directories. Unless they are resolved from the GAC or assemblyFoldersEx then
// we should make sure we use the GAC and assemblyFolders resolvers themserves rather than a directory resolver to find the reference.
// This way we dont get assemblies pulled from the GAC or AssemblyFolders but dont have the marking that they were pulled form there.
var parentReferenceFolders = new List<string>();
var parentReferenceFolders = new List<DirectoryWithParentAssembly>();
foreach (Reference parentReference in reference.GetDependees())
{
CalculateParentAssemblyDirectories(parentReferenceFolders, parentReference);
Expand All @@ -1298,7 +1298,7 @@ private void ResolveReference(
else
{
// Do not probe near dependees if the reference is primary and resolved externally. If resolved externally, the search paths should have been specified in such a way to point to the assembly file.
if (assemblyName == null || !_externallyResolvedPrimaryReferences.Contains(assemblyName.Name))
if (parentReferenceFolders.Count > 0 && (assemblyName == null || !_externallyResolvedPrimaryReferences.Contains(assemblyName.Name)))
{
jaggedResolvers.Add(AssemblyResolution.CompileDirectories(parentReferenceFolders, _fileExists, _getAssemblyName, _getRuntimeVersion, _targetedRuntimeVersion));
}
Expand Down
5 changes: 5 additions & 0 deletions src/Tasks/AssemblyDependency/ResolutionSearchLocation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ internal class ResolutionSearchLocation
/// </summary>
internal string SearchPath { get; set; }

/// <summary>
/// The parent assembly that was used for the SearchPath.
/// </summary>
internal string ParentAssembly { get; set; }

/// <summary>
/// The name of the assembly found at that location. Will be null if there was no assembly there.
/// </summary>
Expand Down
13 changes: 11 additions & 2 deletions src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ private static class Strings
public static string ResolvedFrom;
public static string SearchedAssemblyFoldersEx;
public static string SearchPath;
public static string SearchPathAddedByParentAssembly;
public static string TargetedProcessorArchitectureDoesNotMatch;
public static string UnificationByAppConfig;
public static string UnificationByAutoUnify;
Expand Down Expand Up @@ -152,7 +153,8 @@ internal static void Initialize(TaskLoggingHelper log)
Resolved = GetResourceFourSpaces("ResolveAssemblyReference.Resolved");
ResolvedFrom = GetResourceFourSpaces("ResolveAssemblyReference.ResolvedFrom");
SearchedAssemblyFoldersEx = GetResourceEightSpaces("ResolveAssemblyReference.SearchedAssemblyFoldersEx");
SearchPath = EightSpaces + GetResource("ResolveAssemblyReference.SearchPath");
SearchPath = GetResourceEightSpaces("ResolveAssemblyReference.SearchPath");
SearchPathAddedByParentAssembly = GetResourceEightSpaces("ResolveAssemblyReference.SearchPathAddedByParentAssembly");
TargetedProcessorArchitectureDoesNotMatch = GetResourceEightSpaces("ResolveAssemblyReference.TargetedProcessorArchitectureDoesNotMatch");
UnificationByAppConfig = GetResourceFourSpaces("ResolveAssemblyReference.UnificationByAppConfig");
UnificationByAutoUnify = GetResourceFourSpaces("ResolveAssemblyReference.UnificationByAutoUnify");
Expand Down Expand Up @@ -1791,7 +1793,14 @@ private void LogAssembliesConsideredAndRejected(Reference reference, string fusi
if (lastSearchPath != location.SearchPath)
{
lastSearchPath = location.SearchPath;
Log.LogMessage(importance, Strings.SearchPath, lastSearchPath);
if (location.ParentAssembly != null)
{
Log.LogMessage(importance, Strings.SearchPathAddedByParentAssembly, lastSearchPath, location.ParentAssembly);
}
else
{
Log.LogMessage(importance, Strings.SearchPath, lastSearchPath);
}
if (logAssemblyFoldersMinimal)
{
Log.LogMessage(importance, Strings.SearchedAssemblyFoldersEx);
Expand Down
4 changes: 4 additions & 0 deletions src/Tasks/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1713,6 +1713,10 @@
<data name="ResolveAssemblyReference.SearchPath">
<value>For SearchPath "{0}".</value>
</data>
<data name="ResolveAssemblyReference.SearchPathAddedByParentAssembly">
<value>For SearchPath "{0}" (added by referencing assembly "{1}").</value>
<comment> {1} is the name of the parent assembly for which SearchPath was used.</comment>
</data>
<data name="ResolveAssemblyReference.UnificationByAppConfig">
<value>Using this version instead of original version "{0}" in "{2}" because of a binding redirect entry in the file "{1}".</value>
</data>
Expand Down
5 changes: 5 additions & 0 deletions src/Tasks/Resources/xlf/Strings.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Tasks/Resources/xlf/Strings.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Tasks/Resources/xlf/Strings.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Tasks/Resources/xlf/Strings.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Tasks/Resources/xlf/Strings.it.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Tasks/Resources/xlf/Strings.ja.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Tasks/Resources/xlf/Strings.ko.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Tasks/Resources/xlf/Strings.pl.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading