Skip to content

Commit 9a3254f

Browse files
authored
Fix locked-mode lock file validation for ATF project references (#7312)
1 parent df6c51a commit 9a3254f

2 files changed

Lines changed: 200 additions & 6 deletions

File tree

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

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -228,15 +228,20 @@ public static LockFileValidationResult IsLockFileValid(DependencyGraphSpec dgSpe
228228
}
229229
else
230230
{
231-
// This does not consider ATF.
232-
p2pSpecTargetFrameworkInformation = NuGetFrameworkUtility.GetNearest(p2pSpec.TargetFrameworks, restoreMetadataFramework.FrameworkName, e => e.FrameworkName);
231+
p2pSpecTargetFrameworkInformation = p2pSpec.GetNearestTargetFramework(targetFrameworkInformation.FrameworkName, targetFrameworkInformation.TargetAlias);
232+
if (p2pSpecTargetFrameworkInformation.FrameworkName == null)
233+
{
234+
if (targetFrameworkInformation.FrameworkName is AssetTargetFallbackFramework atfFramework)
235+
{
236+
p2pSpecTargetFrameworkInformation = p2pSpec.GetNearestTargetFramework(atfFramework.AsFallbackFramework(), targetFrameworkInformation.TargetAlias);
237+
}
238+
}
233239
}
234240
// No compatible framework found
235-
if (p2pSpecTargetFrameworkInformation != null)
241+
if (p2pSpecTargetFrameworkInformation != null && p2pSpecTargetFrameworkInformation.FrameworkName != null)
236242
{
237-
// 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));
243+
// Get it based on the matching alias. The appropriate target framework information has already been calculated.
244+
var p2pSpecProjectRestoreMetadataFrameworkInfo = p2pSpec.RestoreMetadata.TargetFrameworks.FirstOrDefault(e => e.TargetAlias == p2pSpecTargetFrameworkInformation.TargetAlias);
240245

241246
if (p2pSpecProjectRestoreMetadataFrameworkInfo != null)
242247
{

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

Lines changed: 189 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,193 @@ 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+
result = await new RestoreCommand(ProjectTestHelpers.CreateRestoreRequest(pathContext, logger, project1, project2)).ExecuteAsync();
1751+
result.Success.Should().BeTrue(logger.ShowErrors());
1752+
}
1753+
1754+
// P1 (apple;banana net10.0 with ATF net472) -> Project2 (apple;banana net472) -> PackageA
1755+
// Verifies project reference through alias disambiguation with ATF, lock file creation, and locked mode
1756+
[Fact]
1757+
public async Task RestoreCommand_PackagesLockFile_WithAliasesOfSameFramework_ProjectReferenceWithATF_LockedModeSucceeds()
1758+
{
1759+
using var pathContext = new SimpleTestPathContext();
1760+
var logger = new TestLogger();
1761+
1762+
// Create packages
1763+
var pkgA = new SimpleTestPackageContext("PackageA", "1.0.0");
1764+
var pkgB = new SimpleTestPackageContext("PackageB", "1.0.0");
1765+
await SimpleTestPackageUtility.CreatePackagesAsync(pathContext.PackageSource, pkgA, pkgB);
1766+
1767+
string apple = nameof(apple);
1768+
string banana = nameof(banana);
1769+
1770+
// Create Project2 spec with apple;banana aliases both targeting net472
1771+
var project2Spec = @"
1772+
{
1773+
""frameworks"": {
1774+
""apple"": {
1775+
""framework"": ""net472"",
1776+
""targetAlias"": ""apple"",
1777+
""dependencies"": {
1778+
""PackageA"": {
1779+
""version"": ""[1.0.0,)"",
1780+
""target"": ""Package"",
1781+
}
1782+
}
1783+
},
1784+
""banana"": {
1785+
""framework"": ""net472"",
1786+
""targetAlias"": ""banana"",
1787+
""dependencies"": {
1788+
""PackageB"": {
1789+
""version"": ""[1.0.0,)"",
1790+
""target"": ""Package"",
1791+
}
1792+
}
1793+
}
1794+
}
1795+
}";
1796+
1797+
var project2 = ProjectTestHelpers.GetPackageSpecWithProjectNameAndSpec("Project2", pathContext.SolutionRoot, project2Spec);
1798+
1799+
// Create Project1 spec with apple;banana aliases, both net10.0 with ATF for net472
1800+
var project1Spec = @"
1801+
{
1802+
""frameworks"": {
1803+
""apple"": {
1804+
""framework"": ""net10.0"",
1805+
""targetAlias"": ""apple"",
1806+
""assetTargetFallback"": true,
1807+
""imports"": [ ""net472"" ],
1808+
""warn"": true,
1809+
""dependencies"": {
1810+
}
1811+
},
1812+
""banana"": {
1813+
""framework"": ""net10.0"",
1814+
""targetAlias"": ""banana"",
1815+
""assetTargetFallback"": true,
1816+
""imports"": [ ""net472"" ],
1817+
""warn"": true,
1818+
""dependencies"": {
1819+
}
1820+
}
1821+
}
1822+
}";
1823+
1824+
var project1 = ProjectTestHelpers.GetPackageSpecWithProjectNameAndSpec("Project1", pathContext.SolutionRoot, project1Spec);
1825+
project1 = project1.WithTestProjectReference(project2);
1826+
1827+
// Enable lock file on Project1
1828+
var lockFilePath = Path.Combine(Path.GetDirectoryName(project1.RestoreMetadata.ProjectPath)!, PackagesLockFileFormat.LockFileName);
1829+
project1.RestoreMetadata.RestoreLockProperties = new RestoreLockProperties(
1830+
restorePackagesWithLockFile: "true",
1831+
lockFilePath,
1832+
restoreLockedMode: false);
1833+
1834+
// First restore - generates lock file
1835+
var result = await new RestoreCommand(ProjectTestHelpers.CreateRestoreRequest(pathContext, logger, project1, project2)).ExecuteAsync();
1836+
result.Success.Should().BeTrue(because: string.Join(Environment.NewLine, result.LockFile.LogMessages.Select(e => e.Message)));
1837+
await result.CommitAsync(logger, CancellationToken.None);
1838+
1839+
// Verify lock file was created and has correct alias-aware structure
1840+
File.Exists(lockFilePath).Should().BeTrue();
1841+
var packagesLockFile = PackagesLockFileFormat.Read(lockFilePath);
1842+
packagesLockFile.Targets.Should().HaveCount(2);
1843+
packagesLockFile.Targets.Should().Contain(t => t.TargetAlias == apple);
1844+
packagesLockFile.Targets.Should().Contain(t => t.TargetAlias == banana);
1845+
packagesLockFile.Targets[0].Dependencies.Should().HaveCount(2);
1846+
packagesLockFile.Targets[1].Dependencies.Should().HaveCount(2);
1847+
1848+
// Both aliases should resolve Project2 through alias disambiguation with ATF
1849+
var appleTarget = result.LockFile.GetTarget(apple, null);
1850+
appleTarget.Should().NotBeNull();
1851+
appleTarget.Libraries.Should().Contain(e => e.Name!.Equals("Project2"));
1852+
1853+
var bananaTarget = result.LockFile.GetTarget(banana, null);
1854+
bananaTarget.Should().NotBeNull();
1855+
bananaTarget.Libraries.Should().Contain(e => e.Name!.Equals("Project2"));
1856+
1857+
// Enable locked mode
1858+
project1.RestoreMetadata.RestoreLockProperties = new RestoreLockProperties(
1859+
restorePackagesWithLockFile: "true",
1860+
lockFilePath,
1861+
restoreLockedMode: true);
1862+
logger.Clear();
1863+
1864+
// Second restore in locked mode
1865+
// Lock file validation does not consider ATF for project references (PackagesLockFileUtilities.cs),
1866+
// so locked mode fails with NU1004 when the project reference requires ATF.
1867+
result = await new RestoreCommand(ProjectTestHelpers.CreateRestoreRequest(pathContext, logger, project1, project2)).ExecuteAsync();
1868+
result.Success.Should().BeTrue(logger.ShowErrors());
1869+
}
16811870
}
16821871
}

0 commit comments

Comments
 (0)