Skip to content

Commit 80a0db9

Browse files
committed
Andy's first updates
1 parent cf26500 commit 80a0db9

File tree

1 file changed

+117
-48
lines changed

1 file changed

+117
-48
lines changed

accepted/2022/Multiple-Equivalent-Framework-Support-TFM-As-Aliases.md

+117-48
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
# Support for handling multiple equivalent framework
22

3-
- [Nikolche Kolev](https://github.com/nkolev92)
4-
- Start Date: (2022-08-18)
3+
- [Nikolche Kolev](https://github.com/nkolev92), [Andy Zivkovic](https://github.com/zivkan)
54
- GitHub Issue ([5154](https://github.com/NuGet/Home/issues/5154)) - Treat Target Framework as Aliases
65

76
## Summary
87

98
.NET SDK projects are capable of multi targeting based on a target framework where you can have different dependencies for each framework.
10-
In some cases, it is convenient to multi target based on different pivots such as target appplication version or maybe the specific runtime, as packages can contain runtime assets.
9+
In some cases, it is convenient to multi target based on different pivots such as target application version or maybe the specific runtime, as packages can contain runtime assets.
1110
This proposals covers the steps that need to be taken to enable that capability.
1211

1312
## Motivation
@@ -66,34 +65,42 @@ MSBuild version 17.3.0+92e077650 for .NET
6665

6766
The missing part is allowing the same framework to be targeting among these aliases.
6867

69-
The following scenarios would be enabled:
68+
The following hypothetical scenarios would be enabled.
69+
Note that these examples are not being proposed as syntax that will be implemented.
70+
Rather, they're intended to demonstrate a scenario that is not possible today.
71+
72+
Firstly, a single project that creates multiple packages for different platforms, but the same TFM:
7073

7174
```xml
7275
<Project Sdk="Microsoft.NET.Sdk">
7376

7477
<PropertyGroup>
75-
<TargetFrameworks>latestnet-linux;latestnet-mac</TargetFrameworks>
78+
<TargetFrameworks>linux;mac</TargetFrameworks>
7679
</PropertyGroup>
7780

78-
<PropertyGroup Condition=" '$(TargetFramework)' == 'latestnet-mac' OR '$(TargetFramework)' == 'latestnet-linux'">
81+
<PropertyGroup>
7982
<TargetFrameworkIdentifier>.NETCoreApp</TargetFrameworkIdentifier>
8083
<TargetFrameworkVersion>v6.0</TargetFrameworkVersion>
8184
<TargetFrameworkMoniker>.NETCoreApp,Version=v6.0</TargetFrameworkMoniker>
85+
86+
<DefineConstants>$(DefineConstants);$(TargetFramework)</>
87+
<PackageId>$(AssemblyName).$(TargetFramework)</PackageId>
8288
</PropertyGroup>
83-
84-
</Project>
89+
90+
</Project>
8591
```
8692

87-
Alternatively, the VS extension scenario would be something like:
93+
Secondly, a Visual Studio Extension project targeting multiple versions of VS could look similar to the following.
94+
In this example, the Visual Studio Extensibility SDK would be responsible for setting `TargetFrameworkIdentifier`, `TargetFrameworkVersion` and `TargetFrameworkMoniker`.
8895

8996
```xml
9097
<Project Sdk="Microsoft.VisualStudio.Extensibility.Sdk">
9198

9299
<PropertyGroup>
93-
<TargetFrameworks>vs17.2;vs17.3</TargetFrameworks>
100+
<TargetFrameworks>vs17.7;vs16.11</TargetFrameworks>
94101
</PropertyGroup>
95-
96-
</Project>
102+
103+
</Project>
97104
```
98105

99106
### Technical explanation
@@ -120,87 +127,124 @@ While this scenario does have a number of interesting uses, there are many comma
120127
| dotnet list package | dotnet list package | L | The work here would likely follow the work from the the restore side |
121128
| Visual Studio challenges | Multi targeting, displaying (transitive) dependencies | L(+) | |
122129

123-
124-
125-
### Build challenges
130+
### Assets file changes
126131

127132
In PackageReference, NuGet has a contract with the .NET SDK, where NuGet writes the assets file and the .NET SDK consumes it.
133+
Therefore, the assets file schema that NuGet writes must match what's expected by the .NET SDK.
128134
In most scenarios, people use `dotnet restore` and `dotnet build --no-restore` and there are no issues.
129-
It is not uncommon that people use `nuget.exe restore` due to the fact that they have non-SDK projects in their solution.
135+
It is not uncommon that people use `nuget.exe restore` due to the fact that they have non-SDK projects in their solution and they have not modernized their CI build to use MSBuild restore.
136+
Given that NuGet.exe and .NET SDK do not ship together, it is very common that the assets file was generated by a different version of NuGet.exe than what the .NET SDK can contains.
137+
Normally, this works.
138+
However, when NuGet makes a breaking change to the assets file, a significant number of customers will experience issues.
139+
We should design the feature to avoid incorrect results, and also make error messages as easy as possible to understand and resolve.
130140

131-
Given that NuGet.exe and .NET SDK do not ship together, it is super common that the assets file was generated by a newer/older version of NuGet.exe than the .NET SDK can support.
141+
#### Assets file version
132142

133-
Normally, this works. NuGet rarely makes non-additive changes to the assets file.
143+
A breaking change in the assets file will be very difficult to implement unless we allow a period where NuGet supports multiple versions of the assets file in the same release.
144+
Additionally, the [.NET SDK assets file reader](https://github.com/dotnet/sdk/blob/main/src/Tasks/Microsoft.NET.Build.Tasks/ResolvePackageAssets.cs) and [non-SDK style projects assets file reader](https://github.com/dotnet/NuGet.BuildTasks) have different implementations, across different repos.
145+
Therefore, unless Nuget supports multiple multiple asset file versions in a single binary, it would be necessary to insert the change simultaneously across NuGet, the .NET SDK, NuGet.BuildTools, VS, and possibly MSBuild.
146+
This is so infeasible it's effectively impossible.
134147

135-
Sample part of an assets file:
148+
Therefore, the proposal is that the .NET SDK will define an MSBuild property `ProjectAssetsFileVersion`, with allowed values `3` or `4`.
149+
When the property is not defined, it will assume `3`, in order to maintain backwards compatibility with the existing build tools.
150+
The .NET SDK already defines property `PropertyAssetsFile`, which is the location it reads the assets file from, hence the proposal of `ProjectAssetsFileVersion`.
151+
It may be that the non-SDK style project assets file reader (NuGet.BuildTools) may never migrate to v4 assets file, so supporting both assets file schemas could be a permanent cost, rather than a temporary situation.
136152

137-
```json
138-
"targets": {
139-
".NETFramework,Version=v4.7.2": {},
140-
".NETFramework,Version=v4.8.0": {}
141-
},
153+
Reading the assets file will need a new data structure, and therefore will need new APIs to read.
154+
The goal is to enable the new data structure to be compatible with both v3 and v4 assets files, so that consumers of the assets file don't need multiple code paths to handle both assets file versions.
155+
This is not only relevant for the .NET SDK, but NuGet's own Visual Studio integration.
142156

143-
```
157+
The assets file already contains a `version` property, always written out as the first property on the root object.
158+
This enables our assets file reader to use polymorphic deserialization to handle.
159+
Although if this turns out to be difficult to implement, we can provide separate V3 and V4 read methods (but both using the new data structures), and callers will need to check the `ProjectAssetsFileVersion` MSBuild property to determine which one to use.
160+
However, I'm reasonably confident that polymorphic deserialization should be feasible to implement.
144161

145-
NuGet's assets file is written pivoting on the actual target framework.
162+
#### Inner build pivot
146163

147-
To solve this, NuGet would need to:
164+
There are multiple places in the assets fie where the "NuGet Framework" (before .NET 5 it was just the `TargetFrameworkMoniker` MSBuild property, but since .NET 5 it can include the TargetPlatformMoniker as well, although in a simplified format) is used as a JSON property key.
165+
Sometimes with an additional Runtime Identifier (RID).
166+
Since the goal of this spec is to allow multiple `TargetFramework`s to resolve to the same "NuGet Framework", this means these parts of the assets file have to change.
148167

149-
- Add vNext for the assets file format, version 4. Version 4 would either a dictionary based on the `TargetFramework` value instead of the effective target framework, built by coming `TargetFrameworkMoniker` and `TargetPlatformMoniker`.
150-
- This behavior would probably have to be opt-in to avoid breaking all combinations of new `NuGet.exe`, old `.NET SDK` combinations, but we can choose to intentionally not do this to encourage maintaining the 2 versions equivalently.
151-
- Make a decision what would be a satisfactory behavior for combining newer/older versions of `NuGet.exe` and `.NET SDK`. This is largely just confirming some of the recent decisions.
168+
Here is a short extract from an assets file:
152169

153-
To solve this, the .NET SDK would need:
170+
```json
171+
"targets": {
172+
".NETFramework,Version=v4.8": {},
173+
".NETFramework,Version=v4.8/win7-x64": {},
174+
"net8.0": {},
175+
"net8.0/win7-x64": {},
176+
"net8.0-windows7.0": {},
177+
"net8.0-windows7.0/win7-x64": {},
178+
},
179+
```
180+
181+
Currently the `targets` section of the assets file is loaded into a `Dictionary<string, NotRelevant>`, and therefore the `targets` node cannot contain two identical TFMs when two Target Frameworks resolve to the same TFM. The proposal is to change this, and all other examples in the assets file, to use the `TargetFramework` as a string literal instead.
154182

155-
- Be able to `build` with both versions 3 and version 4 of the assets file. The NuGet dependencies would naturally flow into the .NET SDK and the work is to just differentiate between the 2 different versions.
156-
- The amount of work needed to be done here would likely be affected by the exact shape of the NuGet APIs. These changes may be seamless.
183+
Todo: figure out best choice for proposed data model & schema.
184+
Will there be a problem if customer uses `TargetFramework` with a `/` character?
157185

158-
### Restore challenges
186+
### Lock file changes
159187

160-
In addition to the assets file, restore has a lock file which has a similar schema, that schema would need to be amended. Fortunately when Central Package Management and its transitive pinning was introduced we introduced the concept of a PackagesLockFile version succesfully. We'd just add a version 3.
188+
NuGet's "repeatable build" feature adds a package lock file to the project directory.
189+
The restore has a lock file has a similar schema to the assets file, so that schema would also need to be amended.
190+
Fortunately when Central Package Management and its transitive pinning was introduced we introduced the concept of a PackagesLockFile version successfully.
191+
We'd just add a version 3.
161192

162193
NuGet would:
163194

164195
- Add version 3 of the packages lock file.
165196

166-
NuGet's intra restore caching is based on the existing of a single framework. It is difficult to predict the amount of work that'd be required to implement this correctly, but it'll certainly involve public API changes to NuGet libraries. (NuGet ships libraries, NuGet.exe, VS and dotnet.exe)
197+
NuGet's intra restore caching is based on the existing of a single framework.
198+
It is difficult to predict the amount of work that'd be required to implement this correctly, but it'll certainly involve public API changes to NuGet libraries.
199+
(NuGet ships libraries, NuGet.exe, VS and dotnet.exe)
167200

168-
### Pack challenges
201+
### Pack changes
169202

170-
Multi-targetted pack only really works when each build leg (TargetFramework value) has 1 target location such as `lib/net5.0`.
171-
Packing projects that do target the same framework multiple may be helpful in some scenarios, so completely blocking it is likely not a great idea, but having a coherent experience would require better understanding of the scenarios at question.
172-
173-
At the minimum, the pack command should be able to detect the scenario at hand and *not succeed queitly* as there's no guarantee what gets packed.
174-
This scenario would require separate design work.
203+
For the first version of this feature, projects that have more than one `TargetFramework` (inner-build) resolve to the same "NuGet Framework" will result in a pack error, therefore preventing these projects from being packed.
204+
See [Extending Pack, under Future Possibilities](#extending-pack) for more information.
175205

176206
### dotnet.exe challenges
177207

178-
Today, some values in dotnet.exe work with an aliased `TargetFramework`, such as `build` and `publish`.
208+
Today, some values in dotnet.exe work with an aliased `TargetFramework`, such as `build` and `publish`, using the `--framework` or `-f` argument.
179209
The expectation is that all the commands support this, but this would require a deeper investigation.
180210

181211
#### dotnet list package
182212

183-
`dotnet list package` has an output that pivots on the effective target framework. This would need to be changed to use the aliased value.
213+
`dotnet list package` has an output that pivots on the effective target framework.
214+
This would need to be changed to use the aliased value.
184215

185216
NuGet would need to:
186217

187218
- Display the output with an aliased pivot.
188-
- Update the machine readable output to handle the new schema, <https://github.com/NuGet/Home/issues/11449>. Given that the machine readable output has not shipped yet, this may be something that can be done ahead of time.
219+
- Update the machine readable output to handle the new schema, <https://github.com/NuGet/Home/issues/11449>.
189220

190221
#### Visual Studio challenges
191222

192-
Currently the PM UI does not have any support for multi targeting. This isn't anything new, but it can affect the experience of customers moving to SDK projects for the first time.
223+
Currently the PM UI does not have any support for multi targeting.
224+
This isn't anything new, but it can affect the experience of customers moving to SDK projects for the first time.
225+
Especially for customers who are not comfortable hand editing XML or MSBuild files.
193226

194227
Another concern are all the APIs for displaying the dependencies, transitive dependencies and Get Installed Packages APIs in NuGet.VisualStudio.Contracts.
195228
All of these APIs may require an incremental addition.
196229

230+
#### Project to Project references
231+
232+
MSBuild has a [`ProjectReference` protocol](https://github.com/dotnet/msbuild/blob/25fdeb3c8c2608f248faec3d6d37733ae144bbbb/documentation/ProjectReference-Protocol.md), which explains how `ProjectReferences` work.
233+
This will need changes to support scenarios where a project where more than one `TargetFramework` resolves to the same "NuGet Framework".
234+
235+
Additionally, NuGet's `PackageReference` requires a project's entire dependency graph to be resolved, which includes projects, in order to calculate transitive packages.
236+
Therefore, NuGet has the same, or at least very similar, problems as MSBuild with regards to `ProjectReference`s.
237+
238+
This needs further discussion between the MSBuild and NuGet teams.
239+
197240
## Drawbacks
198241

199242
<!-- Why should we not do this? -->
200243

201244
## Rationale and alternatives
202245

203-
- N/A. This is a binary decision to either support or not support this scenario.
246+
From a high level feature point of view, the alternative is that customers must use separate projects, rather than a single project file.
247+
Therefore, this feature is a binary decision to either support or not support this scenario.
204248

205249
## Prior Art
206250

@@ -215,6 +259,31 @@ All of these APIs may require an incremental addition.
215259
<!-- What parts of the proposal need to be resolved before the proposal is stabilized? -->
216260
<!-- What related issues would you consider out of scope for this proposal but can be addressed in the future? -->
217261

262+
### Exact assets file schema changes
263+
264+
Should we "simply" replace the TFM in property keys with the Target Framework alias?
265+
For example, change ".NETFramework,Version=4.7.2/win-x64" with "vs17.7/win-x64"?
266+
But there will be parsing problems when a `TargetFramework` contains a `/` character.
267+
While "something/another thing/win-x64" can determine that "win-x64" is a RID, when we get the RID-less "something/another thing" string, it's not clear that what follows the `/` is a RID, unless the assets file parser starts knowing the entire RID graph.
268+
269+
### How ProjectReferences will work
270+
271+
Currently ProjectReferences (P2P) do a compatibility check, both by MSBuild and NuGet (although I'm sure MSBuild delegates to NuGet APIs).
272+
When a project has multiple `TargetFramework`s that map to a single "NuGet Framework", which one should MSBuild and NuGet use?
273+
MSBuild needs to choose in order to know which dll to pass to the compiler (in case different aliases have different APIs), and so test projects actualy test the desired product binary.
274+
NuGet needs to know for the package dependency graph.
275+
276+
A simple option is to match the alias string.
277+
But do we want to support different projects using different aliases?
278+
If so, then a way to "set the desired alias in the target project" will be needed.
279+
That may be usable as a way to [override selection of "nearest" framework](https://github.com/NuGet/Home/issues/7416), which may or may not be considered a good thing, depending on your view if overriding asset selection is good or not.
280+
218281
## Future Possibilities
219282

220-
<!-- What future possibilities can you think of that this proposal would help with? -->
283+
<!-- What future possibilities can you think of that this proposal would help with? -->
284+
285+
### Extending Pack
286+
287+
It seems feasible that if a single project can target multiple versions of Visual Studio, through a new Visual Studio Extensibility SDK, that someone will also want to create a package that supports multiple versions of Visual Studio.
288+
289+
Designing how this could work is out of scope for this spec, and can be introduced in a different spec.

0 commit comments

Comments
 (0)