Skip to content

Commit

Permalink
Correctly identify transitive package references
Browse files Browse the repository at this point in the history
Fixes NuGet/Home#10368

When a transitive package dependency has the same identity as a direct
project dependency that is differenced by a different inner build (TFM),
the dependency is incorrectly resolved as a project.

The resolver logic invoked by the dependency walker only knows about which
projects are referenced but not by which TFM. To correctly isolate inner
builds from each other and avoid incorrectly promoting a project
dependency, pass the nearest project dependencies to the dependency
resolving logic.
  • Loading branch information
ViktorHofer committed Sep 3, 2023
1 parent ff6f3fd commit ea0ba57
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,8 @@ static NuGet.DependencyResolver.LibraryRangeCacheKey.operator ==(NuGet.Dependenc
~static NuGet.DependencyResolver.PackagingUtility.GetLibraryDependencyFromNuspec(NuGet.Packaging.Core.PackageDependency dependency) -> NuGet.LibraryModel.LibraryDependency
~static NuGet.DependencyResolver.RemoteDependencyWalker.IsGreaterThanOrEqualTo(NuGet.Versioning.VersionRange nearVersion, NuGet.Versioning.VersionRange farVersion) -> bool
~static NuGet.DependencyResolver.ResolverUtility.FindLibraryByVersionAsync(NuGet.LibraryModel.LibraryRange libraryRange, NuGet.Frameworks.NuGetFramework framework, System.Collections.Generic.IEnumerable<NuGet.DependencyResolver.IRemoteDependencyProvider> providers, NuGet.Protocol.Core.Types.SourceCacheContext cacheContext, NuGet.Common.ILogger logger, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task<NuGet.DependencyResolver.RemoteMatch>
~static NuGet.DependencyResolver.ResolverUtility.FindLibraryCachedAsync(System.Collections.Concurrent.ConcurrentDictionary<NuGet.DependencyResolver.LibraryRangeCacheKey, System.Threading.Tasks.Task<NuGet.DependencyResolver.GraphItem<NuGet.DependencyResolver.RemoteResolveResult>>> cache, NuGet.LibraryModel.LibraryRange libraryRange, NuGet.Frameworks.NuGetFramework framework, string runtimeIdentifier, NuGet.DependencyResolver.RemoteWalkContext context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<NuGet.DependencyResolver.GraphItem<NuGet.DependencyResolver.RemoteResolveResult>>
~static NuGet.DependencyResolver.ResolverUtility.FindLibraryEntryAsync(NuGet.LibraryModel.LibraryRange libraryRange, NuGet.Frameworks.NuGetFramework framework, string runtimeIdentifier, NuGet.DependencyResolver.RemoteWalkContext context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<NuGet.DependencyResolver.GraphItem<NuGet.DependencyResolver.RemoteResolveResult>>
~static NuGet.DependencyResolver.ResolverUtility.FindLibraryMatchAsync(NuGet.LibraryModel.LibraryRange libraryRange, NuGet.Frameworks.NuGetFramework framework, string runtimeIdentifier, System.Collections.Generic.IEnumerable<NuGet.DependencyResolver.IRemoteDependencyProvider> remoteProviders, System.Collections.Generic.IEnumerable<NuGet.DependencyResolver.IRemoteDependencyProvider> localProviders, System.Collections.Generic.IEnumerable<NuGet.DependencyResolver.IDependencyProvider> projectProviders, System.Collections.Generic.IDictionary<NuGet.DependencyResolver.LockFileCacheKey, System.Collections.Generic.IList<NuGet.LibraryModel.LibraryIdentity>> lockFileLibraries, NuGet.Protocol.Core.Types.SourceCacheContext cacheContext, NuGet.Common.ILogger logger, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<NuGet.DependencyResolver.RemoteMatch>
~static NuGet.DependencyResolver.ResolverUtility.FindLibraryCachedAsync(System.Collections.Concurrent.ConcurrentDictionary<NuGet.DependencyResolver.LibraryRangeCacheKey, System.Threading.Tasks.Task<NuGet.DependencyResolver.GraphItem<NuGet.DependencyResolver.RemoteResolveResult>>> cache, NuGet.LibraryModel.LibraryRange libraryRange, NuGet.Frameworks.NuGetFramework framework, string runtimeIdentifier, NuGet.DependencyResolver.RemoteWalkContext context, System.Threading.CancellationToken cancellationToken, NuGet.LibraryModel.LibraryDependency[] projectDependencies = null) -> System.Threading.Tasks.Task<NuGet.DependencyResolver.GraphItem<NuGet.DependencyResolver.RemoteResolveResult>>
~static NuGet.DependencyResolver.ResolverUtility.FindLibraryEntryAsync(NuGet.LibraryModel.LibraryRange libraryRange, NuGet.Frameworks.NuGetFramework framework, string runtimeIdentifier, NuGet.DependencyResolver.RemoteWalkContext context, System.Threading.CancellationToken cancellationToken, NuGet.LibraryModel.LibraryDependency[] projectDependencies = null) -> System.Threading.Tasks.Task<NuGet.DependencyResolver.GraphItem<NuGet.DependencyResolver.RemoteResolveResult>>
~static NuGet.DependencyResolver.ResolverUtility.FindLibraryMatchAsync(NuGet.LibraryModel.LibraryRange libraryRange, NuGet.Frameworks.NuGetFramework framework, string runtimeIdentifier, System.Collections.Generic.IEnumerable<NuGet.DependencyResolver.IRemoteDependencyProvider> remoteProviders, System.Collections.Generic.IEnumerable<NuGet.DependencyResolver.IRemoteDependencyProvider> localProviders, System.Collections.Generic.IEnumerable<NuGet.DependencyResolver.IDependencyProvider> projectProviders, System.Collections.Generic.IDictionary<NuGet.DependencyResolver.LockFileCacheKey, System.Collections.Generic.IList<NuGet.LibraryModel.LibraryIdentity>> lockFileLibraries, NuGet.Protocol.Core.Types.SourceCacheContext cacheContext, NuGet.Common.ILogger logger, System.Threading.CancellationToken cancellationToken, NuGet.LibraryModel.LibraryDependency[] projectDependencies = null) -> System.Threading.Tasks.Task<NuGet.DependencyResolver.RemoteMatch>
~static NuGet.DependencyResolver.ResolverUtility.FindPackageLibraryMatchCachedAsync(System.Collections.Concurrent.ConcurrentDictionary<NuGet.LibraryModel.LibraryRange, System.Threading.Tasks.Task<System.Tuple<NuGet.LibraryModel.LibraryRange, NuGet.DependencyResolver.RemoteMatch>>> cache, NuGet.LibraryModel.LibraryRange libraryRange, NuGet.DependencyResolver.RemoteWalkContext remoteWalkContext, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<System.Tuple<NuGet.LibraryModel.LibraryRange, NuGet.DependencyResolver.RemoteMatch>>
~static NuGet.DependencyResolver.ResolverUtility.FindProjectMatchAsync(NuGet.LibraryModel.LibraryRange libraryRange, NuGet.Frameworks.NuGetFramework framework, System.Collections.Generic.IEnumerable<NuGet.DependencyResolver.IDependencyProvider> projectProviders, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<NuGet.DependencyResolver.RemoteMatch>
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ public async Task<GraphNode<RemoteResolveResult>> WalkAsync(LibraryRange library
predicate: _ => (recursive ? DependencyResult.Acceptable : DependencyResult.Eclipsed, null),
outerEdge: null,
transitiveCentralPackageVersions: transitiveCentralPackageVersions,
hasParentNodes: false);
hasParentNodes: false,
projectDependencies: null);

// do not calculate the hashset of the direct dependencies for cases when there are not any elements in the transitiveCentralPackageVersions queue
var indexedDirectDependenciesKeyNames = new Lazy<HashSet<string>>(
Expand Down Expand Up @@ -72,7 +73,8 @@ private async Task<GraphNode<RemoteResolveResult>> CreateGraphNode(
Func<LibraryRange, (DependencyResult dependencyResult, LibraryDependency conflictingDependency)> predicate,
GraphEdge<RemoteResolveResult> outerEdge,
TransitiveCentralPackageVersions transitiveCentralPackageVersions,
bool hasParentNodes)
bool hasParentNodes,
LibraryDependency[] projectDependencies)
{
HashSet<LibraryDependency> runtimeDependencies = null;
List<Task<GraphNode<RemoteResolveResult>>> tasks = null;
Expand Down Expand Up @@ -119,7 +121,8 @@ private async Task<GraphNode<RemoteResolveResult>> CreateGraphNode(
framework,
runtimeName,
_context,
CancellationToken.None);
CancellationToken.None,
projectDependencies);

bool hasInnerNodes = (item.Data.Dependencies.Count + (runtimeDependencies == null ? 0 : runtimeDependencies.Count)) > 0;
GraphNode<RemoteResolveResult> node = new GraphNode<RemoteResolveResult>(libraryRange, hasInnerNodes, hasParentNodes)
Expand All @@ -129,6 +132,14 @@ private async Task<GraphNode<RemoteResolveResult>> CreateGraphNode(

Debug.Assert(node.Item != null, "FindLibraryCached should return an unresolved item instead of null");

// Store the nearest project dependencies
if (libraryRange.TypeConstraintAllowsAnyOf(LibraryDependencyTarget.Project | LibraryDependencyTarget.ExternalProject))
{
projectDependencies = item.Data.Dependencies
.Where(dep => dep.LibraryRange.TypeConstraintAllowsAnyOf(LibraryDependencyTarget.Project | LibraryDependencyTarget.ExternalProject))
.ToArray();
}

// Merge in runtime dependencies
if (runtimeDependencies?.Count > 0)
{
Expand Down Expand Up @@ -188,7 +199,8 @@ private async Task<GraphNode<RemoteResolveResult>> CreateGraphNode(
predicate,
innerEdge,
transitiveCentralPackageVersions,
hasParentNodes: false));
hasParentNodes: false,
projectDependencies));
}
else
{
Expand Down Expand Up @@ -473,7 +485,8 @@ private async Task<GraphNode<RemoteResolveResult>> AddTransitiveCentralPackageVe
predicate: ChainPredicate(_ => (DependencyResult.Acceptable, null), rootNode, centralPackageVersionDependency),
outerEdge: null,
transitiveCentralPackageVersions: transitiveCentralPackageVersions,
hasParentNodes: true);
hasParentNodes: true,
projectDependencies: null);

node.OuterNode = rootNode;
node.Item.IsCentralTransitive = true;
Expand Down
37 changes: 29 additions & 8 deletions src/NuGet.Core/NuGet.DependencyResolver.Core/ResolverUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,15 @@ public static Task<GraphItem<RemoteResolveResult>> FindLibraryCachedAsync(
NuGetFramework framework,
string runtimeIdentifier,
RemoteWalkContext context,
CancellationToken cancellationToken)
CancellationToken cancellationToken,
LibraryDependency[] projectDependencies = null)
{
var key = new LibraryRangeCacheKey(libraryRange, framework);

if (cache.TryGetValue(key, out var graphItem))
return graphItem;

graphItem = cache.GetOrAdd(key, FindLibraryEntryAsync(key.LibraryRange, framework, runtimeIdentifier, context, cancellationToken));
graphItem = cache.GetOrAdd(key, FindLibraryEntryAsync(key.LibraryRange, framework, runtimeIdentifier, context, cancellationToken, projectDependencies));

return graphItem;
}
Expand All @@ -41,7 +42,8 @@ public static async Task<GraphItem<RemoteResolveResult>> FindLibraryEntryAsync(
NuGetFramework framework,
string runtimeIdentifier,
RemoteWalkContext context,
CancellationToken cancellationToken)
CancellationToken cancellationToken,
LibraryDependency[] projectDependencies = null)
{
GraphItem<RemoteResolveResult> graphItem = null;
var currentCacheContext = context.CacheContext;
Expand Down Expand Up @@ -69,7 +71,8 @@ public static async Task<GraphItem<RemoteResolveResult>> FindLibraryEntryAsync(
context.LockFileLibraries,
currentCacheContext,
context.Logger,
cancellationToken);
cancellationToken,
projectDependencies);

if (match == null)
{
Expand Down Expand Up @@ -156,13 +159,31 @@ public static async Task<RemoteMatch> FindLibraryMatchAsync(
IDictionary<LockFileCacheKey, IList<LibraryIdentity>> lockFileLibraries,
SourceCacheContext cacheContext,
ILogger logger,
CancellationToken cancellationToken)
CancellationToken cancellationToken,
LibraryDependency[] projectDependencies = null)
{
var projectMatch = await FindProjectMatchAsync(libraryRange, framework, projectProviders, cancellationToken);
bool hasProjectReference = true;
if (projectDependencies is not null)
{
hasProjectReference = false;
foreach (LibraryDependency projectDependency in projectDependencies)
{
if (projectDependency.Name == libraryRange.Name)
{
hasProjectReference = true;
break;
}
}
}

if (projectMatch != null)
if (hasProjectReference)
{
return projectMatch;
var projectMatch = await FindProjectMatchAsync(libraryRange, framework, projectProviders, cancellationToken);

if (projectMatch != null)
{
return projectMatch;
}
}

if (libraryRange.VersionRange == null)
Expand Down

0 comments on commit ea0ba57

Please sign in to comment.