Skip to content
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

Easy Acquisition of .NET Framework Targeting Pack #33

Merged
merged 1 commit into from
Nov 26, 2019
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 159 additions & 0 deletions accepted/targeting-packs/targeting-packs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# Easy Acquisition of .NET Framework Targeting Packs

**PM** [Immo Landwerth](https://github.com/terrajobst) |
**Dev** [Daniel Plaisted](https://github.com/dsplaisted)

This proposal is about enabling acquisition of the .NET Framework targeting
packs via NuGet so that compiling code for a specific version of the .NET
Framework doesn't require a Windows machine with a machine-wide installation of
the corresponding targeting pack.

The .NET Framework supports targeting different versions of the platform
regardless of the .NET Framework version that is installed on the developer's
machine. This allows developers to target a lower version than what they use
themselves. This is especially useful for in-place updates where the older
version of the framework cannot be installed side-by- side.

That mechanism is called *targeting packs*, which is simply a set of assemblies
that describe the public APIs of a particular version of the .NET Framework.
Since those assemblies are only used by the compiler, they don't need to contain
any code, which we call [reference assemblies](https://www.youtube.com/watch?v=EBpY1UMHDY8).

Unfortunately, the targeting packs aren't available by themselves; they are
redistributed as part of the .NET Framework Developer Packs which bundle both
the runtime as well as the targeting pack. This design ensures that developers
can never be in a state where they have a targeting pack that is higher than the
.NET Framework version they have installed. That design made sense coming from a
Visual Studio-first perspective but doesn't serve us well today:

* **It's not cross-platform friendly**. The .NET Framework Developer Packs are
only available for Windows which makes the targeting packs indirectly Windows-
only too.

* **It's not CI friendly**. Requiring a machine-wide install isn't friendly for
cloud-hosted CI machines as developers typically lack permissions to install
software.

This document proposes to offer the targeting packs via NuGet that they can be
acquired in a non-impactful way. It builds on the following existing specs:

* [Phillip Carter](https://github.com/cartermp): [Add Target Packs on NuGet](https://github.com/dotnet/core/pull/64)
* [Daniel Plaisted](https://github.com/dsplaisted): [Reference Assembly NuGet packages](https://gist.github.com/dsplaisted/83d67bbcff9ec1d0aff1bea1bf4ad79a)
* [Prototype](https://github.com/dsplaisted/ReferenceAssemblyPackages)

## Scenarios and User Experience

* Build a project which is multi-targeted to .NET Core or .NET Standard as well
as .NET Framework on Mac OS or Linux using the .NET CLI

* Build a project targeting Mono (which uses the same Target Framework
Identifier as .NET Framework) using the .NET CLI

## Requirements

### Goals
Copy link

Choose a reason for hiding this comment

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

What about PCL profiles? Is that a goal or non-goal? Several popular nuget packages still target PCLs because quite a few customers still use VS2015. Personally, I own several packages that build from .NET SDK projects and multi-target .netstandard*, net4*, and portable-*, and testing on mac+linux is important to us.

Copy link

Choose a reason for hiding this comment

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

.NET 2, 3, 3.5 and PCL profiles would be great! Partial support will only result in partial reliance, which is not good enough in many popular packages like Newtonsoft.JSON etc.

Copy link

Choose a reason for hiding this comment

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

I don't think that there are reference assemblies / targeting packs for <= 3.5
MSBuild even requires to run a version of MSBuildTaskHost.exe on the system install of the CLR 2 (.NET 2, 3, 3.5) in order to build .NET 3.5 projects.


* Doesn't require running an installer
* Works cross-platform
* Can be used in SDK-style multi-targeting projects
* Prefer the centralized targeting pack if it's present. First, this ensures we
don't change the performance characteristic of exiting solutions but also
allows working around some issue due to build extensions by installing the
targeting pack.
* Supports `<PreserveCompilationContext>` so that ASP.NET Razor compilation
Copy link
Member

@dsplaisted dsplaisted Mar 16, 2018

Choose a reason for hiding this comment

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

I've filed dotnet/sdk#2054 to track fixing PreserveCompilationContext. I don't think we should block the rest of the work on this issue though. #Resolved

Copy link
Member

@eerhardt eerhardt Mar 16, 2018

Choose a reason for hiding this comment

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

I'm not sure what you mean by "block the rest of the work", but we can't make using the NuGet targeting pack the default in SDK projects without this issue being addressed. Or else you will break every ASP.NET MVC/Razor Page app running on the desktop .NET Framework. #Resolved

Copy link
Contributor

@nguerrera nguerrera Mar 16, 2018

Choose a reason for hiding this comment

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

As I understand it, the idea for the first turn of the crank is that you can build without the program files TP but razor will still need it to be there to run. It needn't break anything that already works. You can build against nuget and dynamically compile against the same content in program files at runtime.

Step 2 would be to write out the actual nuget location that build used, and plumb that through for runtime compilation consumption. #Resolved

Copy link
Member

@dsplaisted dsplaisted Mar 16, 2018

Choose a reason for hiding this comment

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

The current plan is to only reference the NuGet packages if the targeting pack isn't found in program files. So if you have the reference assemblies installed, everything would continue to work.

I think everything would still work even if we did use the reference assembly packages for compilation as long as you have the reference assemblies installed. Different parts of the build would be getting the reference assemblies from different locations, but they should be the same files. #Resolved

Copy link
Contributor

@nguerrera nguerrera Mar 16, 2018

Choose a reason for hiding this comment

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

(I tried to say the same thing, but Daniel worded it better. :)) #Resolved

Copy link
Member

@eerhardt eerhardt Mar 16, 2018

Choose a reason for hiding this comment

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

Sounds good. I just wanted to call out that this area is a bug factory, and often gets overlooked. #Resolved

still works correctly even without the targeting pack installed.

### Non-Goals

* Supporting NuGet-based acquisition of the .NET Framework runtime
* Supporting NuGet-based acquisition from non-SDK-style projects
Copy link
Member

@tannergooding tannergooding Mar 13, 2018

Choose a reason for hiding this comment

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

I understand this is a non-goal (and why), but what would prevent this from "just working"? #ByDesign

Copy link
Member

@dsplaisted dsplaisted Mar 14, 2018

Choose a reason for hiding this comment

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

I think we should allow this to work if it's not difficult. It should be possible with the proof of concept packages I created. #ByDesign

Copy link
Member

@jaredpar jaredpar Mar 14, 2018

Choose a reason for hiding this comment

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

We were able to do this with our NuGet packages in old-school project files. Don't see this as too hard. There is really just one MSBuild variable you have to toggle. #ByDesign

Copy link
Member Author

@terrajobst terrajobst Mar 14, 2018

Choose a reason for hiding this comment

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

I wrote this before NuGet was doubling down on deprecating and replacing packages.config. I think fully supporting this in non-SDK style projects is much more viable now. #ByDesign

Copy link
Member

Choose a reason for hiding this comment

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

I'd really like to see this supported in old style .csproj projects that are using <PackageReference>...that's where many of us in ASP.NET will still be for a while yet.

Copy link
Member

Choose a reason for hiding this comment

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

@NickCraver The current plan is that it will be supported, you'll just have to explicitly add the PackageReference

Copy link
Member

Choose a reason for hiding this comment

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

Oh that changes things a bunch and adds other issues.

* Support pre-.NET Framework 4.5 targeting packs
Copy link
Contributor

@jnm2 jnm2 Mar 13, 2018

Choose a reason for hiding this comment

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

😭 Libraries like NUnit could really use this. This is what is preventing the dotnet CLI from being able to build net40 and earlier. What's the cost of two or three more targeting packs? #ByDesign

Copy link

@ghost ghost Mar 13, 2018

Choose a reason for hiding this comment

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

Please provide all PCL and .NET Framework 2+ packages. They are already shipped out and not going to get any updates anyway, so hopefully no additional cost than just package once and push to nuget.. #ByDesign

Copy link
Member

@dsplaisted dsplaisted Mar 14, 2018

Choose a reason for hiding this comment

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

I think we should provide the .NET 4.0 targeting pack, and possibly other 4.0.x versions in reference assemblies. We already have targeting packs for these, so I don't think there are any obstacles to this.

For .NET 3.5.1 and lower, we didn't ship targeting packs in the same way. PCL reference assemblies are also a bit different. I would propose that initially we not support them, in order to get basic .NET Framework support out more quickly. #ByDesign

Copy link
Contributor

@jnm2 jnm2 Mar 14, 2018

Choose a reason for hiding this comment

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

@dsplaisted If net35 is merely deprioritized without closing the door on it, I can cheer up. 😃 (net20 would also make some folks happier, and by extension, me.) #ByDesign

Copy link
Member

@tannergooding tannergooding Mar 14, 2018

Choose a reason for hiding this comment

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

This would also fix cases, like in Roslyn, where custom packages are created #ByDesign

Copy link
Member Author

@terrajobst terrajobst Mar 14, 2018

Choose a reason for hiding this comment

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

I have talked to @jaredpar and it seems you guys don't need pre-.NET Framework 4.5 support. Is that not true? #ByDesign

Copy link
Member Author

@terrajobst terrajobst Mar 14, 2018

Choose a reason for hiding this comment

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

@jnm2

What's the cost of two or three more targeting packs?

It's mostly consistency in our offerings; we don't want to breath more life into frameworks that are that old. That being said, we can always ship more targeting packs in the future if that becomes necessary. Based on adoption I don't see enough evidence that this is necessary though. #ByDesign

Copy link
Member Author

@terrajobst terrajobst Mar 14, 2018

Choose a reason for hiding this comment

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

@dsplaisted

I think we should provide the .NET 4.0 targeting pack, and possibly other 4.0.x versions in reference assemblies. We already have targeting packs for these, so I don't think there are any obstacles to this.

That's a fair point. How about this: I leave the wording in the spec as-is; if we can easily build the 4.0 set, great, we'll ship it. If not or we run out of time, we don't. Usage wise, I don't see compelling reasons to spend a great deal of time on 4.0. And 4.5 just makes sense from a .NET Standard support matrix stand point. #ByDesign

Copy link
Contributor

@jnm2 jnm2 Mar 15, 2018

Choose a reason for hiding this comment

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

@terrajobst

It's mostly consistency in our offerings; we don't want to breath more life into frameworks that are that old.

So long as VSTest ships a net35-compiled test execution engine, that puts pressure on NUnit to ship a net35 assembly so that we aren't the ones preventing people from testing on CLR v2 if they need to. A net35 targeting pack would be preferable to what we're doing now, using Mono's edition of net35. #WontFix

Copy link
Member Author

@terrajobst terrajobst Mar 15, 2018

Choose a reason for hiding this comment

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

Not sure I buy that: xUnit only supports .NET Standard 1.1. And the number of 3.5 installs is quite low. I think I'm fine with 4.0 but I'm definitely opposed to go lower, especially because the whole 2.0, 3.0, 3.5 TPs are much more convoluted. #Resolved

* Support non-.NET Framework targeting packs (Xamarin, Unity)
- More specifically, this feature will not include targeting packs for APIs
that are specific to Xamarin, Mono, or Unity such as Xamarin.iOS.dll,
Mono.Posix.dll.

## Design

Daniel has created a [prototype](https://github.com/dsplaisted/ReferenceAssemblyPackages):

* We have an aggregate package that itself is empty and just depends on
different packages split by TFM.
* This allows downloading only the assemblies for a specific version while also
having a single package which simplifies installation outside of SDK- style
projects.

### Details

* We need to fix ASP.NET Core Razor compilation to locate reference assemblies
without their custom locater that assumes the only place to look for is the
global TP location.
- See [this bug for details](https://github.com/dotnet/sdk/issues/2054)
* There will be separate NuGet packages with reference assemblies for each
version of .NET Framework. This means that projects targeting a single version
of .NET Framework don't need to download and spend disk space on the reference
assemblies and intellisense files for all the other possible versions of .NET
Framework. It also means that when a new version of .NET Framework is
released, the reference assemblies for the previous versions don't need to be
re-shipped in an updated package along with the new assemblies.
* There will be a single "metapackage" that can be referenced, that will have
conditional dependencies on each of the version-specific reference assembly
packages.
* The metapackage will automatically be referenced by the .NET SDK when required
(ie on non-Windows OS's or missing targting pack). On Windows, we will make
the .NET SDK continue to rely on the reference assemblies from the targeting
packs, if and only if, it's present. Otherwise we fall pack to the package-
based reference. This ensures existing code continues to build the same way.
* For the package IDs, I suggest the following:
- For the metapackage: `Microsoft.NETFramework.ReferenceAssemblies`
- For the version-specific packages:
`Microsoft.NETFramework.ReferencesAssemblies.net462` (where net462 is
replaced with the corresponding NuGet short framework Identifier)
* The NuGet packages will include a `.targets` file that sets the
Copy link
Member

@weshaggard weshaggard Mar 13, 2018

Choose a reason for hiding this comment

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

I think we may need to set FrameworkPathOverride as well to avoid other weird issues like the explicit mscorlib reference. #Resolved

`TargetFrameworkRootPath` to a path within the NuGet package. This will allow
the existing MSBuild logic in the `GetReferenceAssemblyPaths` target and other
logic such as automatically referencing the facades if necessary to continue
to work as normal.
- We may need to set `FrameworkPathOverride` as well to avoid other weird
issues like the explicit `mscorlib` reference.
* The logic in the `.targets` file in the NuGet packages will only set the
`TargetFrameworkRootPath` if the `TargetFrameworkIdentifier` and
`TargetFrameworkVersion` properties match the reference assemblies that the
package provides.
* The reference assembly packages should include the `.xml` intellisense
documentation files. I believe the `.xml` documentation files have to be in
the same folder as the reference assemblies, so there isn't a way to deliver
them as a separate package.
* If we want to support localized intellisense, we would need to create a
separate set of packages and corresponding meta-package for each language
supported. The IDs of these packages could follow the pattern
`Microsoft.NETFramework.ReferenceAssemblies.pt-br`. The SDK could use a
property to select which language's metapackage to reference.
* The reference assembly packages should not show up as dependencies of "normal"
packages. Thus, the reference assembly packages should set
`developmentDependency` to true in it's metadata. Likewise, when the .NET SDK
automatically references the reference assembly metapackage, it should use
`PrivateAssets="All"`.
* The reference assembly packages should include the same layout of files that
are installed by the targeting packs under `C:\Program Files (x86)\Reference
Assemblies\Microsoft\Framework`. This should be rooted at the path specified
by the `TargetFrameworkRootPath` property in the package. For example, if the
`.targets` file, which is in the `build` folder of the NuGet package, sets the
`TargetFrameworkRootPath` to `$(MSBuildThisFileDirectory)`, then the .NET
4.6.2 reference assembly package should have the reference assemblies and
intellisense files in the `build\.NETFramework\v4.6.2` folder, as well as have
the `Facades`, `PermissionSets`, and `RedistList` folders with corresponding
files under that folder.
* The version number of each package should start at 1.0.0. When a new version
Copy link
Member

@tannergooding tannergooding Mar 13, 2018

Choose a reason for hiding this comment

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

Was any thought given to just release the net462 reference assemblies in a Microsoft.NETFramework.ReferenceAssemblies, v4.6.2 package, and to have a separate Microsoft.NETFramework.ReferenceAssemblies.All metapackage?
#ByDesign

Copy link
Member

@tannergooding tannergooding Mar 13, 2018

Choose a reason for hiding this comment

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

I think this would result in (overall) fewer names, easier to find/reason about, etc #ByDesign

Copy link
Contributor

@nguerrera nguerrera Mar 13, 2018

Choose a reason for hiding this comment

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

There have been (rare) occasions where reference assemblies had a bug that was serviced. I think that would be blocked by TFM version == package version. #ByDesign

Copy link
Member

@dsplaisted dsplaisted Mar 14, 2018

Choose a reason for hiding this comment

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

Yes, using the target framework version as the package version would prevent us from servicing the package if we needed to.

Also, we wouldn't want someone to manually reference the Microsoft.NETFramework.ReferenceAssemblies package, and end up with the wrong version of the package for the version of the framework they're targeting (which could happen if they retarget their project, or update to a newer package version via the NuGet UI, for example). #ByDesign

Copy link

@jskeet jskeet Mar 14, 2018

Choose a reason for hiding this comment

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

Yes, each package feels like it's a separate "product" to me. The package targeting .NET 4.5.1 can't be used if you're trying to target .NET 4.5, for example - it's not just "a patched version of the same thing". It's a package you'd use for a different purpose.

So +1 to "start at 1.0.0". #ByDesign

Copy link
Member

@tannergooding tannergooding Mar 14, 2018

Choose a reason for hiding this comment

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

The same could be said for .NETStandard Library and any future servicing fixes that come out for it...

I would assume that whatever plan is required there would also apply here.

I don't think that these are separate products at all, they are simply the reference assemblies for different versions of the .NET Framework #ByDesign

Copy link
Contributor

@nguerrera nguerrera Mar 14, 2018

Choose a reason for hiding this comment

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

Netstandard.Library package version is not 1:1 with .NETStandard TFM version either. #ByDesign

Copy link
Member Author

@terrajobst terrajobst Mar 14, 2018

Choose a reason for hiding this comment

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

Netstandard.Library package version is not 1:1 with .NETStandard TFM version either.

Yep, and the fact that we tried to "look" close to the TFM version has caused more problem than it solved. 1.0.0 is the way to go. #ByDesign

of the .NET Framework is released, a corresponding reference assembly package
(versioned at 1.0.0) should be released, and a new metapackage that includes
the additional dependency for the newly supported version should be released.
The new version of the metapackage should have it's minor version incremented.
Copy link

@dasMulli dasMulli Mar 14, 2018

Choose a reason for hiding this comment

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

Ideally, the metapackage would also have a targets file to check for the maximum supported version and report an error if a net* version is targeted greater than the currently supported version or else the consuming project might end up with reference assemblies for the lower version if resolution falls back to the latest dependency group of the metapackage. #Resolved

Copy link
Member Author

@terrajobst terrajobst Mar 15, 2018

Choose a reason for hiding this comment

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

Hey @dsplaisted, is this needed or is this handled by the way the targets work? #Resolved

Copy link
Member

@dsplaisted dsplaisted Mar 15, 2018

Choose a reason for hiding this comment

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

In my proof of concept, this is basically handled handled by conditioning setting the reference assembly path on the exact target framework version. The package for 4.7.1, for example, has the following in the .targets file:

  <PropertyGroup Condition=" ('$(UseReferenceAssembliesFromPackage)' == 'true') And ('$(TargetFrameworkIdentifier)' == '.NETFramework') And ('$(TargetFrameworkVersion)' == 'v4.7.1') ">
    <TargetFrameworkRootPath>$(MSBuildThisFileDirectory)</TargetFrameworkRootPath>
    <_NeedToRemoveExplicitReference>true</_NeedToRemoveExplicitReference>
  </PropertyGroup>

So you wouldn't get the wrong reference assemblies, you'd just get the same error you get today if reference assemblies aren't available. #Resolved

Copy link
Contributor

@nguerrera nguerrera Mar 15, 2018

Choose a reason for hiding this comment

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

We should improve on the error that you get today. It warns, "hey, you'll get refs from the GAC, which is totally wrong and stupid, but YOLO the build might succeed." This is especially silly in SDK projects where the GAC is excluded from search paths. #Resolved

Copy link
Contributor

@nguerrera nguerrera Mar 15, 2018

Choose a reason for hiding this comment

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

(and where there often is no GAC (.NET Core msbuild)) #Resolved

If we need to fix an issue with the packages, we can ship new versions with
the patch version incremented, and a new metapackage with dependencies on the
patched packages.
* We need to determine what license to use for these packages.
Copy link
Member

Choose a reason for hiding this comment

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

Is there any reason not to use an open source license, like MIT, for these reference assemblies?

* We will also need to determine the details of the package metadata, such as
the description, project URL, icon, etc.

## Q & A