Skip to content

Commit 7a524a6

Browse files
committed
GetReferenceNearest disambiguation
1 parent de3b92c commit 7a524a6

2 files changed

Lines changed: 201 additions & 4 deletions

File tree

src/NuGet.Core/NuGet.ProjectModel/ProjectLockFile/PackagesLockFileUtilities.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -229,14 +229,20 @@ public static LockFileValidationResult IsLockFileValid(DependencyGraphSpec dgSpe
229229
else
230230
{
231231
// This does not consider ATF.
232-
p2pSpecTargetFrameworkInformation = NuGetFrameworkUtility.GetNearest(p2pSpec.TargetFrameworks, restoreMetadataFramework.FrameworkName, e => e.FrameworkName);
232+
p2pSpecTargetFrameworkInformation = p2pSpec.GetNearestTargetFramework(targetFrameworkInformation.FrameworkName, targetFrameworkInformation.TargetAlias);
233+
if (p2pSpecTargetFrameworkInformation.FrameworkName == null)
234+
{
235+
if (targetFrameworkInformation.FrameworkName is AssetTargetFallbackFramework atfFramework)
236+
{
237+
p2pSpecTargetFrameworkInformation = p2pSpec.GetNearestTargetFramework(atfFramework.AsFallbackFramework(), targetFrameworkInformation.TargetAlias);
238+
}
239+
}
233240
}
234241
// No compatible framework found
235-
if (p2pSpecTargetFrameworkInformation != null)
242+
if (p2pSpecTargetFrameworkInformation != null && p2pSpecTargetFrameworkInformation.FrameworkName != null)
236243
{
237244
// We need to compare the main framework only. Ignoring fallbacks.
238-
var p2pSpecProjectRestoreMetadataFrameworkInfo = p2pSpec.RestoreMetadata.TargetFrameworks.FirstOrDefault(
239-
t => NuGetFramework.Comparer.Equals(p2pSpecTargetFrameworkInformation.FrameworkName, t.FrameworkName));
245+
var p2pSpecProjectRestoreMetadataFrameworkInfo = p2pSpec.RestoreMetadata.TargetFrameworks.FirstOrDefault(e => e.TargetAlias == p2pSpecTargetFrameworkInformation.TargetAlias);
240246

241247
if (p2pSpecProjectRestoreMetadataFrameworkInfo != null)
242248
{

test/NuGet.Core.FuncTests/NuGet.Commands.FuncTest/RestoreCommand_PackagesLockFileTests.cs

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#nullable disable
55

6+
using System;
67
using System.Collections.Generic;
78
using System.IO;
89
using System.Linq;
@@ -1678,5 +1679,195 @@ public async Task RestoreCommand_PackagesLockFile_InLockedMode_WithAliasedFramew
16781679
logger.ErrorMessages.Single().Should().Contain("NU1004");
16791680
logger.ErrorMessages.Single().Should().Contain("The project reference project2 has changed");
16801681
}
1682+
1683+
// Verifies project reference through ATF, lock file creation, and locked mode
1684+
[Fact]
1685+
public async Task RestoreCommand_PackagesLockFile_ProjectReferenceWithATF_LockedModeSucceeds()
1686+
{
1687+
using var pathContext = new SimpleTestPathContext();
1688+
var logger = new TestLogger();
1689+
1690+
// Create packages
1691+
var pkgA = new SimpleTestPackageContext("PackageA", "1.0.0");
1692+
await SimpleTestPackageUtility.CreatePackagesAsync(pathContext.PackageSource, pkgA);
1693+
1694+
var project2Spec = @"
1695+
{
1696+
""frameworks"": {
1697+
""net472"": {
1698+
""dependencies"": {
1699+
""PackageA"": {
1700+
""version"": ""[1.0.0,)"",
1701+
""target"": ""Package"",
1702+
}
1703+
}
1704+
}
1705+
}
1706+
}";
1707+
1708+
var project2 = ProjectTestHelpers.GetPackageSpecWithProjectNameAndSpec("Project2", pathContext.SolutionRoot, project2Spec);
1709+
1710+
var project1Spec = @"
1711+
{
1712+
""frameworks"": {
1713+
""net10.0"": {
1714+
""assetTargetFallback"": true,
1715+
""imports"": [ ""net472"" ],
1716+
""warn"": true,
1717+
""dependencies"": {
1718+
}
1719+
}
1720+
}
1721+
}";
1722+
1723+
var project1 = ProjectTestHelpers.GetPackageSpecWithProjectNameAndSpec("Project1", pathContext.SolutionRoot, project1Spec);
1724+
project1 = project1.WithTestProjectReference(project2);
1725+
1726+
var lockFilePath = Path.Combine(Path.GetDirectoryName(project1.RestoreMetadata.ProjectPath)!, PackagesLockFileFormat.LockFileName);
1727+
project1.RestoreMetadata.RestoreLockProperties = new RestoreLockProperties(
1728+
restorePackagesWithLockFile: "true",
1729+
lockFilePath,
1730+
restoreLockedMode: false);
1731+
1732+
var result = await new RestoreCommand(ProjectTestHelpers.CreateRestoreRequest(pathContext, logger, project1, project2)).ExecuteAsync();
1733+
result.Success.Should().BeTrue(because: string.Join(Environment.NewLine, result.LockFile.LogMessages.Select(e => e.Message)));
1734+
await result.CommitAsync(logger, CancellationToken.None);
1735+
1736+
// Verify lock file was created and has correct alias-aware structure
1737+
File.Exists(lockFilePath).Should().BeTrue();
1738+
var packagesLockFile = PackagesLockFileFormat.Read(lockFilePath);
1739+
packagesLockFile.Targets.Should().HaveCount(1);
1740+
packagesLockFile.Targets[0].Dependencies.Should().HaveCount(2);
1741+
1742+
// Enable locked mode
1743+
project1.RestoreMetadata.RestoreLockProperties = new RestoreLockProperties(
1744+
restorePackagesWithLockFile: "true",
1745+
lockFilePath,
1746+
restoreLockedMode: true);
1747+
logger.Clear();
1748+
1749+
// Second restore in locked mode
1750+
// Lock file validation does not consider ATF for project references (PackagesLockFileUtilities.cs),
1751+
// so locked mode fails with NU1004 when the project reference requires ATF.
1752+
result = await new RestoreCommand(ProjectTestHelpers.CreateRestoreRequest(pathContext, logger, project1, project2)).ExecuteAsync();
1753+
result.Success.Should().BeTrue(logger.ShowErrors());
1754+
}
1755+
1756+
// P1 (apple;banana net10.0 with ATF net472) -> Project2 (apple;banana net472) -> PackageA
1757+
// Verifies project reference through alias disambiguation with ATF, lock file creation, and locked mode
1758+
[Fact]
1759+
public async Task RestoreCommand_PackagesLockFile_WithAliasesOfSameFramework_ProjectReferenceWithATF_LockedModeSucceeds()
1760+
{
1761+
using var pathContext = new SimpleTestPathContext();
1762+
var logger = new TestLogger();
1763+
1764+
// Create packages
1765+
var pkgA = new SimpleTestPackageContext("PackageA", "1.0.0");
1766+
var pkgB = new SimpleTestPackageContext("PackageB", "1.0.0");
1767+
await SimpleTestPackageUtility.CreatePackagesAsync(pathContext.PackageSource, pkgA, pkgB);
1768+
1769+
string apple = nameof(apple);
1770+
string banana = nameof(banana);
1771+
1772+
// Create Project2 spec with apple;banana aliases both targeting net472
1773+
var project2Spec = @"
1774+
{
1775+
""frameworks"": {
1776+
""apple"": {
1777+
""framework"": ""net472"",
1778+
""targetAlias"": ""apple"",
1779+
""dependencies"": {
1780+
""PackageA"": {
1781+
""version"": ""[1.0.0,)"",
1782+
""target"": ""Package"",
1783+
}
1784+
}
1785+
},
1786+
""banana"": {
1787+
""framework"": ""net472"",
1788+
""targetAlias"": ""banana"",
1789+
""dependencies"": {
1790+
""PackageB"": {
1791+
""version"": ""[1.0.0,)"",
1792+
""target"": ""Package"",
1793+
}
1794+
}
1795+
}
1796+
}
1797+
}";
1798+
1799+
var project2 = ProjectTestHelpers.GetPackageSpecWithProjectNameAndSpec("Project2", pathContext.SolutionRoot, project2Spec);
1800+
1801+
// Create Project1 spec with apple;banana aliases, both net10.0 with ATF for net472
1802+
var project1Spec = @"
1803+
{
1804+
""frameworks"": {
1805+
""apple"": {
1806+
""framework"": ""net10.0"",
1807+
""targetAlias"": ""apple"",
1808+
""assetTargetFallback"": true,
1809+
""imports"": [ ""net472"" ],
1810+
""warn"": true,
1811+
""dependencies"": {
1812+
}
1813+
},
1814+
""banana"": {
1815+
""framework"": ""net10.0"",
1816+
""targetAlias"": ""banana"",
1817+
""assetTargetFallback"": true,
1818+
""imports"": [ ""net472"" ],
1819+
""warn"": true,
1820+
""dependencies"": {
1821+
}
1822+
}
1823+
}
1824+
}";
1825+
1826+
var project1 = ProjectTestHelpers.GetPackageSpecWithProjectNameAndSpec("Project1", pathContext.SolutionRoot, project1Spec);
1827+
project1 = project1.WithTestProjectReference(project2);
1828+
1829+
// Enable lock file on Project1
1830+
var lockFilePath = Path.Combine(Path.GetDirectoryName(project1.RestoreMetadata.ProjectPath)!, PackagesLockFileFormat.LockFileName);
1831+
project1.RestoreMetadata.RestoreLockProperties = new RestoreLockProperties(
1832+
restorePackagesWithLockFile: "true",
1833+
lockFilePath,
1834+
restoreLockedMode: false);
1835+
1836+
// First restore - generates lock file
1837+
var result = await new RestoreCommand(ProjectTestHelpers.CreateRestoreRequest(pathContext, logger, project1, project2)).ExecuteAsync();
1838+
result.Success.Should().BeTrue(because: string.Join(Environment.NewLine, result.LockFile.LogMessages.Select(e => e.Message)));
1839+
await result.CommitAsync(logger, CancellationToken.None);
1840+
1841+
// Verify lock file was created and has correct alias-aware structure
1842+
File.Exists(lockFilePath).Should().BeTrue();
1843+
var packagesLockFile = PackagesLockFileFormat.Read(lockFilePath);
1844+
packagesLockFile.Targets.Should().HaveCount(2);
1845+
packagesLockFile.Targets.Should().Contain(t => t.TargetAlias == apple);
1846+
packagesLockFile.Targets.Should().Contain(t => t.TargetAlias == banana);
1847+
packagesLockFile.Targets[0].Dependencies.Should().HaveCount(2);
1848+
packagesLockFile.Targets[1].Dependencies.Should().HaveCount(2);
1849+
1850+
// Both aliases should resolve Project2 through alias disambiguation with ATF
1851+
var appleTarget = result.LockFile.GetTarget(apple, null);
1852+
appleTarget.Should().NotBeNull();
1853+
appleTarget.Libraries.Should().Contain(e => e.Name!.Equals("Project2"));
1854+
1855+
var bananaTarget = result.LockFile.GetTarget(banana, null);
1856+
bananaTarget.Should().NotBeNull();
1857+
bananaTarget.Libraries.Should().Contain(e => e.Name!.Equals("Project2"));
1858+
1859+
// Enable locked mode
1860+
project1.RestoreMetadata.RestoreLockProperties = new RestoreLockProperties(
1861+
restorePackagesWithLockFile: "true",
1862+
lockFilePath,
1863+
restoreLockedMode: true);
1864+
logger.Clear();
1865+
1866+
// Second restore in locked mode
1867+
// Lock file validation does not consider ATF for project references (PackagesLockFileUtilities.cs),
1868+
// so locked mode fails with NU1004 when the project reference requires ATF.
1869+
result = await new RestoreCommand(ProjectTestHelpers.CreateRestoreRequest(pathContext, logger, project1, project2)).ExecuteAsync();
1870+
result.Success.Should().BeTrue(logger.ShowErrors());
1871+
}
16811872
}
16821873
}

0 commit comments

Comments
 (0)