Skip to content

Conversation

@nkolev92
Copy link
Member

@nkolev92 nkolev92 commented Oct 3, 2022

Design for #5154

Rendered

cc @dsplaisted

Copy link

@Nirmal4G Nirmal4G left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some ideas and suggestions. Feel free to ignore it. 😅🙃

@nkolev92
Copy link
Member Author

nkolev92 commented Oct 4, 2022

Thanks for taking a look @Nirmal4G

For context, this is design I'm still working on, I'd say it's at about 50%.

Hoping to add more details about the scenarios and just in general provide more concrete proposals soon.

@Nirmal4G
Copy link

Nirmal4G commented Oct 4, 2022

@nkolev92 These are some of my observations and preliminary suggestions, ideas and takeaways from this draft. I'm not taking this as a concrete proposal. I'm just viewing this from a 10-foot view (or design perspective) of the proposal to see how it could impact normal as well as advanced users. In that mindset, read through my comments again. You would then see what I was talking about.

@Nirmal4G
Copy link

Nirmal4G commented Oct 4, 2022

Hoping to add more details about the scenarios and just in general provide more concrete proposals soon.

Sure, Will look though once you mark this ready for review. Sorry for the uninvited comments. I'll stop now.

@nkolev92
Copy link
Member Author

nkolev92 commented Oct 4, 2022

Hoping to add more details about the scenarios and just in general provide more concrete proposals soon.

Sure, Will look though once you mark this ready for review. Sorry for the uninvited comments. I'll stop now.

Oh no worries. I appreciate the feedback as this is a large doc so getting feedback early on is always great.

My comment was more about that fact that I'll be adding even more info to the doc, setting expectations about when something like this would even be close to happening :)

@JonDouglas
Copy link
Contributor

@ViktorHofer & @ericstj

@zivkan zivkan self-assigned this Nov 13, 2023
@zivkan zivkan marked this pull request as ready for review November 15, 2023 13:27
@zivkan zivkan requested a review from a team as a code owner November 15, 2023 13:27
@zivkan zivkan force-pushed the dev-nkolev92-tfmaliases branch from 55c6bba to 1c19625 Compare November 23, 2023 08:55
@ghost
Copy link

ghost commented Jan 11, 2024

This PR has been automatically marked as stale because it has no activity for 30 days. It will be closed if no further activity occurs within another 15 days of this comment, unless it has a "Status:Do not auto close" label. If it is closed, you may reopen it anytime when you're ready again, as long as you don't delete the branch.

@zivkan
Copy link
Member

zivkan commented Jan 26, 2024

oops, vacation and other priorities has kept this feature on the backburner, but it's still planned.

@zivkan zivkan marked this pull request as draft July 23, 2024 22:34
@nkolev92 nkolev92 force-pushed the dev-nkolev92-tfmaliases branch from 1c19625 to ddc7fae Compare September 16, 2025 18:27
@nkolev92 nkolev92 force-pushed the dev-nkolev92-tfmaliases branch from ddc7fae to 413d370 Compare November 26, 2025 17:58
@nkolev92 nkolev92 assigned nkolev92 and unassigned zivkan Dec 1, 2025
@nkolev92 nkolev92 changed the title Design for allowing restore of multiple equivalent frameworks Design for allowing restore of multiple equivalent frameworks (Target Frameworks as Aliases) Dec 12, 2025
@nkolev92 nkolev92 force-pushed the dev-nkolev92-tfmaliases branch from 650ca9a to b6c47f6 Compare December 12, 2025 18:39
@nkolev92 nkolev92 marked this pull request as ready for review December 12, 2025 18:42
@nkolev92 nkolev92 requested a review from baronfel December 12, 2025 18:43
@nkolev92
Copy link
Member Author

@ericstj @ViktorHofer @dsplaisted @baronfel @zivkan @jeffkl

This is ready for review.

It's a complicated, but I'd love to get through it async as much as possible because of the length itself :D

dsplaisted
dsplaisted previously approved these changes Dec 12, 2025

PackageReference is also supported by non-SDK style projects, which use [dotnet/NuGet.BuildTools](https://github.com/NuGet.BuildTools) which, despite the name, is owned by the non-SDK project system team, not NuGet.
The changes to the assets file that affect legacy projects will be done in a non-breaking way and as such, changes should not be required there.
NuGet will utilize the SDKAnalysisLevel property when it writes out an assets file with breaking changes, ensuring that the .NET SDK will be able to read the assets file for the build.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Customers are expected to change SDKAnalysisLevel if they have build errors when upgrading to a newer SDK. I can't remember the name of the property, but there's an SDK version that feels more appropriate for what this is aiming to achieve.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, there's an SDK version property for sure, we raise telemetry on that, but I see SDKAnalysisLevel to mean, behave like the previous SDK version, and to me that'd imply the older restore behavior in general.

I don't feel too strongly though, and can easily be convinced otherwise.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my mind, SdkAnalysisLevel is about warnings and errors (diagnostics), not features, and looking at the original spec, I feel my opinion is reinforced: https://github.com/dotnet/designs/blob/main/proposed/sdk-analysis-level.md

The spec doesn't say it, but I always thought SdkAnalysisLevel was inspired by Roslyn's AnalysisLevel, and Roslyn also has LangVersion which I consider to be the feature toggle. The SDK/NuGet doesn't have a "global feature toggle" like Roslyn does with LangVersion. Using SdkAnalysisLevel is a choice that can be made, but then customers lose the ability to enable new features while disabling new diagnostics. I guess we need to decide whether that's an important distinction to allow customers to have.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Chet has a good compass when it comes to SDKAnalysisLevel.

Curious what you think @baronfel

tldr;
NuGet will introduce a V4 of the assets file.
It requires a new .NET SDK reader, ie a specific .NET SDK version.

Should that "switch" be based off of the SDKAnalysisLevel or NETCoreSdkVersion property.
More context on the pros and cons in the above comments.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@baronfel attempted to post the below, but was facing GH problems:


The intent of SdkAnalysisLevel is to easily apply behaviors to all kinds of SDK-level analysis - not just Roslyn Analyzers, but built-in diagnostics, MSBuild BuildChecks, and similar. Any kind of validation that we do during a build was in scope.

This has evolved slightly to controlling behaviors as well - I am entirely supportive of this. The main reason for this evolution/broadening of meaning is that the user-facing view of this property is often "make the current SDK behave like the SDK band I tell you to". With this lens, applying SdkAnalysisLevel to behaviors in addition to validations is entirely appropriate.


As previously shown in [the assets file pivots changes](#target-framework-pivots), NuGet uses `tfm/rid` as the property name in the `$/targets` object.
Therefore, a `TargetFramework` that includes a `/` will cause parsing challenges.
NuGet's libraries could attempt to do the split on the last `/` character.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considering the assets file has an entry for the ridless target, in addition to the per-rid targets, I think it would be challening to tell the difference between the two.

If the parser keeps raw strings in memory and then parses the alias/rid only once all targets are parsed from json, then you can find what the max number of / are, and assume the ridless has n-1.

But I think it's easier if we fail restore if the alias has a /. It's easier to block first and later allow, than the other way around.

Since we're already incrementing the version number, I think it'd be ideal if we could make another breaking change to the format, so instead of

"targets": {
   "alias" : {  },
   "alias/rid": {  }
}

we could instead have

"targets": {
   "alias": {
     "": { },
     "rid": {  }
  }
}

But I suspect this might be a lot harder to implement.

However, as is pointed out just below, the target alias is used as the directory name for the obj and bin directories, and / is a directory separator, so I think that's another reason we could just block / from the alias

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'd be fine with blocking that yeah.
Honestly, I can't image this to be a super critical scenario that would need to be addressed.
The more changes we make, the higher the risk of the change anyways.

JanProvaznik pushed a commit to dotnet/msbuild that referenced this pull request Dec 19, 2025
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 <175728472+Copilot@users.noreply.github.com>
Copy link
Contributor

@donnie-msft donnie-msft left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a couple questions, and left a few nits.
Suggest having Copilot review spelling/etc and also telling it to put each sentence on a new line in the markdown, as I know Andy prefers that.

Non-SDK style project only have a single target, so those project types won't see a change.

For simplicity of implementation, whenever SDKAnalysisLevel is set to 10.0.300 or more, NuGet will use the V4 assets file.
Whenever the SDKAnalysisLevel is set to a lower version, then NuGet will use the V3 assets file.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this ages out after 3 major releases, based on how SDKAnalysisLevel works, right?
In other words, there's ample time (years?) for a customer to move a solution to version 4.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it allows plenty of time to move to newer SDKs.
By that time, all .NET SDKs in support will support v4.


The following 2 changes will be made on the writer side:

- Add "framework" to the packageSpec, while keeping targetAlias for simplicity sake.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a reader not as familiar with targetAlias, I'm not understanding how this is simple. Why would I use targetAlias and framework?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The simplicity I am referring to is from an implementation perspective.
It would allow us not to version the "PackageSpec", but just the assets file.

If we drop the targetAlias, we'll need to somehow version the package spec to tell the writer what to set as the TargetAlias.

Hopefully the implementation PR clarifies this.

```diff
"frameworks": {
- "net8.0": {
- "targetAlias": "production",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a scenario where targetAlias can be selecting 1 of multiple aliases? Let's say I have "production" and "staging" and "development", can I then set targetAlias to any of those three?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Who's the setting them in this case?
I'm not sure I understand the scenario.

This is mostly just technical details for us.


Given that there is no version for the package spec at this point, we can do a simple trick to infer the type of PackageSpec to read.

- When reading a framework, if both targetAlias and framework, then this is equivalent to V4 and a .NET SDK scenario
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be simplified from the textual scenarios into a small matrix of scenarios?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll push a follow-up to this.

However, breaking changes are rarely made in NuGet, so if we have to make a breaking change to enable a feature, it's tempting to take advantage and propose other changes at the same time.

Since the lock file contains the package's content hash, and the package's `nuspec` is included in that content hash, this means that the dependencies in the the lock file are a redundant duplication of information available elsewhere.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick feedback on this spec (which is comprehensive in all the right ways - thank you!) that I wanted to get down:

Bundling more kinds of 'things' into TargetFramework-shaped holes makes sense because the only kind of 'foreach' we've built into our platform so far is TargetFramework. An alternative, which would likely be much more work but may be more extensible and/or orthogonal, would be to

Consider making the 'pivots' of multitargeting more extensible

Meaning, let a project and/or MSBuild SDK define a list of properties that should be cartesian-product-ed together when doing any kind of 'for-each-able' workflow (build, pack, publish, etc).

Now, I don't necessarily think we should do this, bit it comes to mind as an alternative to this proposal, and I don't see a section where alternatives are considered and criteria for rejection are listed.


### Pack and duplicate frameworks

There is no intuitive solution to the pack scenario.
Copy link

@ericstj ericstj Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dependencies only have a single slot for TargetFramework. I agree that there is no solution to alias dependencies.

Assets/build-output on the other hand is a different story. For example, folks using alias for runtime it's perfectly reasonable to include all aliased frameworks and allow each target to specify it's package path.

Then it's up to the project to decide which alias is used for dependencies.

I can imagine us building first-class support for this behavior. We could designate one alias as being used for dependencies, or we could allow the project to assert that all aliases should have identical dependencies (and validate that).

A couple common cases that I'd like to see us build minimal examples:

  1. Project building a .NET package for multiple runtimes with API and dependencies represented in runtime-less output.
  2. Project building a roslyn analyzer targeting multiple roslyn versions, omitting all dependencies.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Status:Do not auto close Do not auto close for PRs needs long review process

Projects

None yet

Development

Successfully merging this pull request may close these issues.