-
Notifications
You must be signed in to change notification settings - Fork 736
Full restore support for aliasing - make the assets file aliasing aware #6972
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Conversation
4827827 to
e7cded1
Compare
7cbe95e to
863e076
Compare
Fixes # ### Context Design: NuGet/Home#12124 To allow duplicate frameworks in aliasing, the project reference protocol nearest framework selection needs to be updated to support matching by alias as well. Relevant part: https://github.com/NuGet/Home/blob/dev-nkolev92-tfmaliases/accepted/2025/Multiple-Equivalent-Framework-Support-TFM-As-Aliases.md#project-to-project-references NuGet/NuGet.Client#7011 NuGet.Client side adding the parameter. NuGet/NuGet.Client#6972 will add the full implementation at a later point. ### Changes Made - Pass CurrentProjectTargetFrameworkProperty if GetReferenceNearestTargetFrameworkTaskSupportsTargetFrameworKPropertyParameter is set. - If GetReferenceNearestTargetFrameworkTaskSupportsTargetFrameworKPropertyParameter is not set, but GetReferenceNearestTargetFrameworkTaskSupportsTargetPlatformParameter is set, call the old variation. - Otherwise calls the last variation. ### Testing - Manual testing. - I'd be happy to add tests if someone can point me in the right direction. ### Notes The idea here is to get ahead of things. Currently aliasing work can't be end to end tested because it requires an msbuild change. It makes it really hard to validate the NuGet changes are enough and good, but this is the only change needed on the msbuild side. --------- Co-authored-by: Copilot <[email protected]>
2c2a38f to
2d62c8b
Compare
91829c0 to
1d505fc
Compare
1d505fc to
0db929a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR implements the restore component of framework aliasing support (from NuGet/Home#12124), enabling projects to restore duplicate target frameworks differentiated by aliases. The implementation includes a new assets file format (version 4) that pivots on aliases instead of framework names, with automatic downgrade to version 3 for legacy projects or older .NET SDK versions.
Changes:
- Introduces assets file format v4 with alias-based pivoting
- Adds framework alias disambiguation logic in restore and MSBuild tasks
- Implements NU1018 error for duplicate frameworks when using legacy resolver or older SDKs
Reviewed changes
Copilot reviewed 70 out of 71 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/NuGet.Core/NuGet.ProjectModel/PackageSpecExtensions.cs | Adds GetNearestTargetFramework method for alias-based framework matching |
| src/NuGet.Core/NuGet.ProjectModel/PackageSpecWriter.cs | Updates writer to support both legacy and alias-based format output |
| src/NuGet.Core/NuGet.ProjectModel/LockFile/LockFileFormat.cs | Changes default version to 4 with conditional legacy writer usage |
| src/NuGet.Core/NuGet.Commands/RestoreCommand/RestoreCommand.cs | Adds duplicate framework validation and version determination logic |
| src/NuGet.Core/NuGet.DependencyResolver.Core/LibraryRangeCacheKey.cs | Extends cache key to include alias parameter |
| src/NuGet.Core/NuGet.Build.Tasks/GetReferenceNearestTargetFrameworkTask.cs | Adds CurrentProjectTargetFrameworkProperty for disambiguation |
| test files | Comprehensive test coverage for aliasing scenarios |
Files not reviewed (1)
- src/NuGet.Core/NuGet.Commands/Strings.Designer.cs: Language not supported
Comments suppressed due to low confidence (3)
test/NuGet.Core.Tests/NuGet.Commands.Test/Project2ProjectInLockFileTests.cs:1
- Duplicate comment "// Act" on consecutive lines. Remove the duplicate on line 278.
test/NuGet.Clients.Tests/NuGet.PackageManagement.VisualStudio.Test/Services/NuGetProjectManagerServiceTests.cs:1 - Incomplete or corrupted comment text. "ock" should likely be removed or the complete intended comment should be provided.
test/NuGet.Core.FuncTests/Dotnet.Integration.Test/DotnetRestoreTests.cs:1 - Duplicate assertions on lines 3651-3653 and 3655-3657. The second set of assertions is redundant and should be removed.
src/NuGet.Core/NuGet.Commands/RestoreCommand/LockFileBuilder.cs
Outdated
Show resolved
Hide resolved
Co-authored-by: Copilot <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 70 out of 71 changed files in this pull request and generated 3 comments.
Files not reviewed (1)
- src/NuGet.Core/NuGet.Commands/Strings.Designer.cs: Language not supported
....Tests/NuGet.PackageManagement.VisualStudio.Test/Services/NuGetProjectManagerServiceTests.cs
Outdated
Show resolved
Hide resolved
test/NuGet.Core.FuncTests/NuGet.Commands.FuncTest/RestoreCommand_AlgorithmEquivalencyTests.cs
Outdated
Show resolved
Hide resolved
test/NuGet.Core.Tests/NuGet.Commands.Test/Project2ProjectInLockFileTests.cs
Outdated
Show resolved
Hide resolved
| request.FallbackRuntimes.UnionWith(FallbackRuntimes); | ||
| request.LockFileVersion = LockFileFormat.Version; | ||
| // Use v3 for classic csproj. | ||
| request.LockFileVersion = request.Project.RestoreMetadata?.UsingMicrosoftNETSdk == true ? LockFileFormat.Version : 3; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we have a constant for the value of 3 here?
| if (spec.RestoreMetadata.TargetFrameworks.Count > 1) | ||
| { | ||
| var aliases = spec.TargetFrameworks.Select(e => e.TargetAlias); | ||
| var aliases = spec.TargetFrameworks.Select(e => e.TargetAlias).ToList(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this just be moved out of the if now that its needed in two places? Is it possible for this call to be happening twice sometimes?
| RuntimeIdentifier = targetGraph.RuntimeIdentifier, | ||
| TargetAlias = targetGraph.TargetAlias, | ||
| }; | ||
| var target = lockFile.Version >= 4 ? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should also be a constant IMO
| var target = lockFile.Version >= 4 ? | |
| var target = lockFile.Version >= LockFileFormat.Version4 ? |
| { | ||
| // For NETCore put everything under a TFM section | ||
| // Projects are included for NETCore | ||
| bool isV4LockFile = lockFile.Version >= 4; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| bool isV4LockFile = lockFile.Version >= 4; | |
| bool isV4OrGreatorLockFile = lockFile.Version >= 4; |
| bool success = true; | ||
|
|
||
| // Check for duplicate frameworks. The new dependency resolver supports aliasing, but the legacy one does not. | ||
| if (_request.Project.TargetFrameworks.Count != new HashSet<NuGetFramework>(_request.Project.TargetFrameworks.Select(e => e.FrameworkName)).Count) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would GroupBy() be better and more obvious what this code is doing?
| } | ||
|
|
||
| var library = _dependencyProvider.GetLibrary(libraryIdentity, targetFramework); | ||
| var library = _dependencyProvider.GetLibrary(libraryIdentity, targetFramework, alias: null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think in some places we're passing null as alias for a default and other places we're passing string.Empty. Since we declare the parameter as nullable, we should probably just pass null but still treat string.Empty as nothing? Or does string.Empty mean something else and its okay that we're not consistently passing one or the other?
| return LibraryRange.Equals(other.LibraryRange) | ||
| && Framework.Equals(other.Framework); | ||
| && Framework.Equals(other.Framework) | ||
| && StringComparer.Ordinal.Equals(Alias, other.Alias); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is alias case sensitive?
| && StringComparer.Ordinal.Equals(Alias, other.Alias); | |
| && StringComparer.OrdinalIgnoreCase.Equals(Alias, other.Alias); |
| { | ||
| foreach (var target in lockFile.Targets) | ||
| { | ||
| target.TargetFramework = lockFile.PackageSpec.GetTargetFramework(target.TargetAlias).FrameworkName; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to guard against GetTargetFramework() returning null?
| // Legacy is really supposed to be 3. | ||
|
|
||
| // PackageSpec probably doesn't need to be versioned to be honest. | ||
| // But the lock file does. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clean up this comment, especially the next line...
Bug
Fixes: NuGet/Home#14534
Description
This PR implements the restore part of NuGet/Home#12124.
The short summary is:
Some more helpers for the review:
Assets file and dg spec (packagespec) read/write changes pointers:
The PackageSpecWriter, which writes the package spec, has a legacy and "new" writer. This writer is used in 3 different places. The assets file file, the dg spec writer (dgspec.json in the obj folder) and the no-op hash. In order to avoid having to version the package spec, we only use the legacy writer in the assets file, when the version is 3. This ensures all of our tests are actually covering the new codepath and we don't accidentally break the legacy projects. The way the package spec writer is implemented probably allows the legacy projects to have v4 of the assets file, but I'd rather do that at a later point to minimize the risk of this current change.
Every time we write the package spec now, we write a "framework", which is basically the effective framework. For simplicity, we write this every time.
LockFileTarget.Name is what's being written as the key, so in LockFileBuilder, we set the Name based on the version. Similarly, the LockFileTarget reader, v3 and v4, will set the name based on the version. If V3, we fill out the TargetAlias, if V4, we fill out the Framework (which is not longer written).
In LockFileFormat, the default version is 4. Whenever we use V4 of the LockFileFormat (or assets file), we use the default package spec writer, with version 3, we use the legacy writer.
I cleaned up any global suppressions whenever I saw it fit. The fewer of these we have, the better.
PR Checklist