From 6784f45b7f04bb71f68323664b5fa8c6f4fdeee6 Mon Sep 17 00:00:00 2001 From: Viktor Hofer Date: Wed, 21 Aug 2024 15:18:20 +0200 Subject: [PATCH 1/2] Make PackageSpecDependencyProvider consider the version being requested Fixes: https://github.com/NuGet/Home/issues/10368 Re-submit of https://github.com/NuGet/NuGet.Client/pull/5452 NuGet gets confused with transitive project and package dependencies with the same identity (name) in a multi-targeting project and selects the wrong type (project instead of package). Projects are expected to be preferred over packages, but only when the version matches. If the version doesn't match, projects shouldn't be selected in frameworks which don't declare a ProjectReference to those projects. --- .../PackageSpecReferenceDependencyProvider.cs | 10 ++- .../RestoreCommandTests.cs | 80 +++++++++++++++++++ .../Commands/TestRestoreRequest.cs | 1 + 3 files changed, 90 insertions(+), 1 deletion(-) diff --git a/src/NuGet.Core/NuGet.ProjectModel/PackageSpecReferenceDependencyProvider.cs b/src/NuGet.Core/NuGet.ProjectModel/PackageSpecReferenceDependencyProvider.cs index 69899bcfe8a..68c59bfb93e 100644 --- a/src/NuGet.Core/NuGet.ProjectModel/PackageSpecReferenceDependencyProvider.cs +++ b/src/NuGet.Core/NuGet.ProjectModel/PackageSpecReferenceDependencyProvider.cs @@ -101,7 +101,15 @@ public Library GetLibrary(LibraryRange libraryRange, NuGetFramework targetFramew // This must exist in the external references if (_externalProjectsByUniqueName.TryGetValue(name, out ExternalProjectReference externalReference)) { - packageSpec = externalReference.PackageSpec; + if (externalReference.PackageSpec == null || + libraryRange.VersionRange.FindBestMatch(new NuGetVersion[] { externalReference.PackageSpec.Version }) != null) + { + packageSpec = externalReference.PackageSpec; + } + else + { + externalReference = null; + } } if (externalReference == null && packageSpec == null) diff --git a/test/NuGet.Core.Tests/NuGet.Commands.Test/RestoreCommandTests/RestoreCommandTests.cs b/test/NuGet.Core.Tests/NuGet.Commands.Test/RestoreCommandTests/RestoreCommandTests.cs index de0bbb96405..f5900762478 100644 --- a/test/NuGet.Core.Tests/NuGet.Commands.Test/RestoreCommandTests/RestoreCommandTests.cs +++ b/test/NuGet.Core.Tests/NuGet.Commands.Test/RestoreCommandTests/RestoreCommandTests.cs @@ -3337,6 +3337,86 @@ public async Task ExecuteAsync_WithLegacyAlgorithmOptIn_ExecutesLegacyAlgorithm( result.LockFile.PackageSpec.RestoreMetadata.UseLegacyDependencyResolver.Should().BeTrue(); } + [Fact] + public async Task ExecuteAsync_WithConditionalProjectAndPackageReferences_SelectsPackageWhereProjectIsNotAppropriate() + { + // Arrange + using var context = new SourceCacheContext(); + using var pathContext = new SimpleTestPathContext(); + + var mainProject = "main"; + var mainProjectPath = Path.Combine(pathContext.SolutionRoot, mainProject); + + var systemNumericsVectorName = "System.Numerics.Vectors"; + var childProjectPath = Path.Combine(pathContext.SolutionRoot, systemNumericsVectorName); + + var mainProjectJson = @" + { + ""version"": ""1.0.0"", + ""frameworks"": { + ""netstandard2.0"": { + ""dependencies"": { + ""System.Memory"": { + ""target"": ""Package"", + ""version"": ""[4.5.5, )"" + }, + ""NETStandard.Library"": { + ""suppressParent"": ""All"", + ""target"": ""Package"", + ""version"": ""[2.0.3, )"", + ""autoReferenced"": true + } + } + }, + ""net8.0"": { + ""dependencies"": { + } + } + } + }"; + PackageSpec systemNumericsVectorPackageSpec = ProjectTestHelpers.GetPackageSpec(systemNumericsVectorName, pathContext.SolutionRoot, "net8.0"); + PackageSpec mainPackageSpec = ProjectTestHelpers.GetPackageSpecWithProjectNameAndSpec(mainProject, pathContext.SolutionRoot, mainProjectJson); + var settings = Settings.LoadDefaultSettings(pathContext.SolutionRoot); + mainPackageSpec.RestoreMetadata.ConfigFilePaths = settings.GetConfigFilePaths(); + mainPackageSpec.RestoreMetadata.Sources = SettingsUtility.GetEnabledSources(settings).ToList(); + mainPackageSpec.RestoreMetadata.FallbackFolders = SettingsUtility.GetFallbackPackageFolders(settings).ToList(); + mainPackageSpec.RestoreMetadata.PackagesPath = SettingsUtility.GetGlobalPackagesFolder(settings); + + mainPackageSpec.RestoreMetadata.TargetFrameworks.Single(e => e.FrameworkName.Equals(NuGetFramework.Parse("net8.0"))).ProjectReferences.Add(new ProjectRestoreReference() + { + ProjectUniqueName = systemNumericsVectorPackageSpec.RestoreMetadata.ProjectUniqueName, + ProjectPath = systemNumericsVectorPackageSpec.RestoreMetadata.ProjectPath, + }); + + // create packages + var ns203 = new SimpleTestPackageContext("NETStandard.Library", "2.0.3"); + var systemMemory = new SimpleTestPackageContext("System.Memory", "4.5.5"); + var systemNumericsVector = new SimpleTestPackageContext(systemNumericsVectorName, "4.4.0"); + systemMemory.Dependencies.Add(systemNumericsVector); + + await SimpleTestPackageUtility.CreateFolderFeedV3Async(pathContext.PackageSource, + ns203, + systemMemory, + systemNumericsVector); + + var logger = new TestLogger(); + var request = ProjectTestHelpers.CreateRestoreRequest(pathContext, logger, mainPackageSpec, systemNumericsVectorPackageSpec); + + var restoreCommand = new RestoreCommand(request); + RestoreResult result = await restoreCommand.ExecuteAsync(); + + result.Success.Should().BeTrue(because: logger.ShowMessages()); + result.LockFile.LogMessages.Should().BeEmpty(); + var net80Target = result.LockFile.Targets.Single(e => e.TargetFramework.Equals(NuGetFramework.Parse("net8.0"))); + var netstandard20 = result.LockFile.Targets.Single(e => e.TargetFramework.Equals(NuGetFramework.Parse("netstandard2.0"))); + net80Target.Libraries.Should().HaveCount(1); + var net80Vectors = net80Target.Libraries.Single(e => e.Name.Equals(systemNumericsVectorName)); + net80Vectors.Version.Should().Be(new NuGetVersion(1, 0, 0)); + netstandard20.Libraries.Should().HaveCount(3); + var ns20Target = netstandard20.Libraries.Single(e => e.Name.Equals(systemNumericsVectorName)); + ns20Target.Version.Should().Be(new NuGetVersion(4, 4, 0)); + } + private static TargetFrameworkInformation CreateTargetFrameworkInformation(List dependencies, List centralVersionsDependencies, NuGetFramework framework = null) { NuGetFramework nugetFramework = framework ?? new NuGetFramework("net40"); diff --git a/test/TestUtilities/Test.Utility/Commands/TestRestoreRequest.cs b/test/TestUtilities/Test.Utility/Commands/TestRestoreRequest.cs index 94310d9d4c2..92ef2e8157e 100644 --- a/test/TestUtilities/Test.Utility/Commands/TestRestoreRequest.cs +++ b/test/TestUtilities/Test.Utility/Commands/TestRestoreRequest.cs @@ -203,6 +203,7 @@ public TestRestoreRequest( DependencyGraphSpec.AddProject(project); DependencyGraphSpec.AddRestore(project.RestoreMetadata.ProjectUniqueName); AllowNoOp = true; + ProjectStyle = project.RestoreMetadata.ProjectStyle; } } } From 59e82ddc8cdf0e2d11d0dc482661282bf24c4a77 Mon Sep 17 00:00:00 2001 From: Viktor Hofer Date: Mon, 2 Sep 2024 13:17:07 +0200 Subject: [PATCH 2/2] Update PackageSpecReferenceDependencyProvider.cs --- .../PackageSpecReferenceDependencyProvider.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/NuGet.Core/NuGet.ProjectModel/PackageSpecReferenceDependencyProvider.cs b/src/NuGet.Core/NuGet.ProjectModel/PackageSpecReferenceDependencyProvider.cs index 68c59bfb93e..4115b67c21b 100644 --- a/src/NuGet.Core/NuGet.ProjectModel/PackageSpecReferenceDependencyProvider.cs +++ b/src/NuGet.Core/NuGet.ProjectModel/PackageSpecReferenceDependencyProvider.cs @@ -101,7 +101,9 @@ public Library GetLibrary(LibraryRange libraryRange, NuGetFramework targetFramew // This must exist in the external references if (_externalProjectsByUniqueName.TryGetValue(name, out ExternalProjectReference externalReference)) { + // A library with a null version range means that all versions should match. if (externalReference.PackageSpec == null || + libraryRange.VersionRange == null || libraryRange.VersionRange.FindBestMatch(new NuGetVersion[] { externalReference.PackageSpec.Version }) != null) { packageSpec = externalReference.PackageSpec;