From ea0ba578a58625bbc07c72623d93e34db399fa2a Mon Sep 17 00:00:00 2001 From: Viktor Hofer Date: Sun, 3 Sep 2023 09:46:56 +0200 Subject: [PATCH] Correctly identify transitive package references Fixes https://github.com/NuGet/Home/issues/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. --- .../PublicAPI.Shipped.txt | 6 +-- .../Remote/RemoteDependencyWalker.cs | 23 +++++++++--- .../ResolverUtility.cs | 37 +++++++++++++++---- 3 files changed, 50 insertions(+), 16 deletions(-) diff --git a/src/NuGet.Core/NuGet.DependencyResolver.Core/PublicAPI.Shipped.txt b/src/NuGet.Core/NuGet.DependencyResolver.Core/PublicAPI.Shipped.txt index 65abee085c3..10eded64d3f 100644 --- a/src/NuGet.Core/NuGet.DependencyResolver.Core/PublicAPI.Shipped.txt +++ b/src/NuGet.Core/NuGet.DependencyResolver.Core/PublicAPI.Shipped.txt @@ -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 providers, NuGet.Protocol.Core.Types.SourceCacheContext cacheContext, NuGet.Common.ILogger logger, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task -~static NuGet.DependencyResolver.ResolverUtility.FindLibraryCachedAsync(System.Collections.Concurrent.ConcurrentDictionary>> cache, NuGet.LibraryModel.LibraryRange libraryRange, NuGet.Frameworks.NuGetFramework framework, string runtimeIdentifier, NuGet.DependencyResolver.RemoteWalkContext context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> -~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> -~static NuGet.DependencyResolver.ResolverUtility.FindLibraryMatchAsync(NuGet.LibraryModel.LibraryRange libraryRange, NuGet.Frameworks.NuGetFramework framework, string runtimeIdentifier, System.Collections.Generic.IEnumerable remoteProviders, System.Collections.Generic.IEnumerable localProviders, System.Collections.Generic.IEnumerable projectProviders, System.Collections.Generic.IDictionary> lockFileLibraries, NuGet.Protocol.Core.Types.SourceCacheContext cacheContext, NuGet.Common.ILogger logger, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task +~static NuGet.DependencyResolver.ResolverUtility.FindLibraryCachedAsync(System.Collections.Concurrent.ConcurrentDictionary>> 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> +~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> +~static NuGet.DependencyResolver.ResolverUtility.FindLibraryMatchAsync(NuGet.LibraryModel.LibraryRange libraryRange, NuGet.Frameworks.NuGetFramework framework, string runtimeIdentifier, System.Collections.Generic.IEnumerable remoteProviders, System.Collections.Generic.IEnumerable localProviders, System.Collections.Generic.IEnumerable projectProviders, System.Collections.Generic.IDictionary> lockFileLibraries, NuGet.Protocol.Core.Types.SourceCacheContext cacheContext, NuGet.Common.ILogger logger, System.Threading.CancellationToken cancellationToken, NuGet.LibraryModel.LibraryDependency[] projectDependencies = null) -> System.Threading.Tasks.Task ~static NuGet.DependencyResolver.ResolverUtility.FindPackageLibraryMatchCachedAsync(System.Collections.Concurrent.ConcurrentDictionary>> cache, NuGet.LibraryModel.LibraryRange libraryRange, NuGet.DependencyResolver.RemoteWalkContext remoteWalkContext, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> ~static NuGet.DependencyResolver.ResolverUtility.FindProjectMatchAsync(NuGet.LibraryModel.LibraryRange libraryRange, NuGet.Frameworks.NuGetFramework framework, System.Collections.Generic.IEnumerable projectProviders, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task diff --git a/src/NuGet.Core/NuGet.DependencyResolver.Core/Remote/RemoteDependencyWalker.cs b/src/NuGet.Core/NuGet.DependencyResolver.Core/Remote/RemoteDependencyWalker.cs index 08c2b1681b3..cd570dd02cb 100644 --- a/src/NuGet.Core/NuGet.DependencyResolver.Core/Remote/RemoteDependencyWalker.cs +++ b/src/NuGet.Core/NuGet.DependencyResolver.Core/Remote/RemoteDependencyWalker.cs @@ -36,7 +36,8 @@ public async Task> 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>( @@ -72,7 +73,8 @@ private async Task> CreateGraphNode( Func predicate, GraphEdge outerEdge, TransitiveCentralPackageVersions transitiveCentralPackageVersions, - bool hasParentNodes) + bool hasParentNodes, + LibraryDependency[] projectDependencies) { HashSet runtimeDependencies = null; List>> tasks = null; @@ -119,7 +121,8 @@ private async Task> CreateGraphNode( framework, runtimeName, _context, - CancellationToken.None); + CancellationToken.None, + projectDependencies); bool hasInnerNodes = (item.Data.Dependencies.Count + (runtimeDependencies == null ? 0 : runtimeDependencies.Count)) > 0; GraphNode node = new GraphNode(libraryRange, hasInnerNodes, hasParentNodes) @@ -129,6 +132,14 @@ private async Task> 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) { @@ -188,7 +199,8 @@ private async Task> CreateGraphNode( predicate, innerEdge, transitiveCentralPackageVersions, - hasParentNodes: false)); + hasParentNodes: false, + projectDependencies)); } else { @@ -473,7 +485,8 @@ private async Task> 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; diff --git a/src/NuGet.Core/NuGet.DependencyResolver.Core/ResolverUtility.cs b/src/NuGet.Core/NuGet.DependencyResolver.Core/ResolverUtility.cs index 635906b98c4..5900df47d68 100644 --- a/src/NuGet.Core/NuGet.DependencyResolver.Core/ResolverUtility.cs +++ b/src/NuGet.Core/NuGet.DependencyResolver.Core/ResolverUtility.cs @@ -24,14 +24,15 @@ public static Task> 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; } @@ -41,7 +42,8 @@ public static async Task> FindLibraryEntryAsync( NuGetFramework framework, string runtimeIdentifier, RemoteWalkContext context, - CancellationToken cancellationToken) + CancellationToken cancellationToken, + LibraryDependency[] projectDependencies = null) { GraphItem graphItem = null; var currentCacheContext = context.CacheContext; @@ -69,7 +71,8 @@ public static async Task> FindLibraryEntryAsync( context.LockFileLibraries, currentCacheContext, context.Logger, - cancellationToken); + cancellationToken, + projectDependencies); if (match == null) { @@ -156,13 +159,31 @@ public static async Task FindLibraryMatchAsync( IDictionary> 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)