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

.NET 6.0 TFMs #174

Merged
merged 24 commits into from
Feb 19, 2021
Merged
Changes from 22 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f42f327
Add initial draft for .NET 6.0 TFMs
terrajobst Feb 2, 2021
c616b42
Address minor feedback on wording
terrajobst Feb 5, 2021
ccf4f1c
Remove watchOS because we scoped this out from .NET 6
terrajobst Feb 5, 2021
95b11cb
Resolve open issues
terrajobst Feb 5, 2021
665cf78
Add section on APIs and RIDs
terrajobst Feb 5, 2021
24ee92c
Expand on RIDs
terrajobst Feb 5, 2021
7110009
Include RIDs for Android, macOS, iOS, and tvOS
terrajobst Feb 8, 2021
beb388e
There was no .NET Core 3.2 (ayayyay!)
terrajobst Feb 8, 2021
077eabd
Address Alexander's feedback
terrajobst Feb 8, 2021
d3d7d8c
Add section for platform annotations
terrajobst Feb 8, 2021
76d9135
Clarify merged vs. individual bindings
terrajobst Feb 8, 2021
ee14a32
Add user experience and requirement around Mac Catalyst and Xamarin iOS
terrajobst Feb 8, 2021
b953b89
Fix wording
terrajobst Feb 8, 2021
ae07c4a
Clarified annotations for bindings vs BCL APIs
terrajobst Feb 8, 2021
90bcc19
Add open question based on Frank's feedback
terrajobst Feb 8, 2021
13cb286
Fix typo
terrajobst Feb 8, 2021
589fbc4
Fix API name
terrajobst Feb 8, 2021
1d40848
Sort RIDs for Mac Catalyst to match the other platforms
terrajobst Feb 8, 2021
deec01a
Add iOS 14 and tvOS 14
terrajobst Feb 8, 2021
b199c76
Close italics
terrajobst Feb 9, 2021
0b22921
Clarify how xmarin.ios relates to net5.0 in Mac Catalyst
terrajobst Feb 12, 2021
c7aa606
List precedence rules for all new TFMs
terrajobst Feb 16, 2021
3cddc29
Update accepted/2021/net6.0-tfms/net6.0-tfms.md
terrajobst Feb 17, 2021
e9393d4
Include xamarin.macos and xamarin.tvos in precedence rules
terrajobst Feb 18, 2021
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
396 changes: 396 additions & 0 deletions accepted/2021/net6.0-tfms/net6.0-tfms.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,396 @@
# .NET 6.0 Target Frameworks

**PM** [Immo Landwerth](https://github.com/terrajobst)

In [.NET 5.0][net5.0] we defined a new syntax for target frameworks (TFM) that
makes it unnecessary for both tools and humans to use a decoder ring, such as
the .NET Standard version table, to figure out what is compatible with what. In
this document, we're listing which TFMs we're adding in .NET 6.0.

## Scenarios and User Experience

### Using an existing library in Mac Catalyst

Sophia is building an application for Mac Catalyst. From her experience with
Xamarin iOS she knows the library `Xamarin.FFImageLoading` and wants to use it.
The package installs successfully, but upon building she gets the following
warning:

> Package 'Xamarin.FFImageLoading' was restored using 'xamarin.ios' instead of
> the project target framework 'net6.0-maccatalyst'. This package may not be
> fully compatible with your project.

To find out more, she visits the package's project site, which is on GitHub. She
decides to file an issue to ask whether the library will add support for Mac
Catalyst, but for the time being she's unblocked and can continue to develop her
app.

## Requirements

### Goals

* Support the definition of frameworks relevant for Xamarin development
* Mac Catalyst is able to consume libraries built for Xamarin iOS, even though
the binary might not work. The developer should get a warning, but the tools
shouldn't block it.

### Non-Goals

* Re-design the TFM syntax

## Stakeholders and Reviewers

* MSBuild / SDK team
* NuGet team
* Project system team
* Xamarin team

## Design

| TFM | Compatible With |
|--------------------|------------------------------------------|
| net6.0 | (subsequent version of net5.0) |
| net6.0-windows | (subsequent version of net5.0-windows) |
| net6.0-android | xamarin.android |
| | (+everything else inherited from net6.0) |
| net6.0-ios | xamarin.ios |
| | (+everything else inherited from net6.0) |
| net6.0-macos | xamarin.mac |
| | (+everything else inherited from net6.0) |
| net6.0-maccatalyst | xamarin.ios |
terrajobst marked this conversation as resolved.
Show resolved Hide resolved
terrajobst marked this conversation as resolved.
Show resolved Hide resolved
| | (+everything else inherited from net6.0) |
| net6.0-tvos | xamarin.tvos |
| | (+everything else inherited from net6.0) |

### Compatibility rules

The [built-in compatibility rules][net5.0] of the `netX.Y-platform` TFMs make a
TFM automatically compatible with:

1. Itself
2. Earlier versions of itself
Copy link
Member

Choose a reason for hiding this comment

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

Maybe we should make it clear this list order does not correspond to precedence?

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 believe it is the default precedence for a regular netX.Y-platform, no?

3. The corresponding `netX.Y` TFM
4. Earlier versions of the corresponding `netX.Y` TFM

The `net5.0` TFM has the additional twist that it's compatible with
`netcoreapp3.1` (and earlier), `netstandard2.1` (and earlier) as well as .NET
Framework (`net4x` and earlier, but under a warning). The .NET Framework
compatibility is achieved as a *fallback*, that is, it's only used when nothing
else applies.

We don't want to model the Xamarin TFMs as a fallback but instead want them to
apply **before** `netcoreapp` and `netstandard`. Usually the existence of the
Xamarin TFM implementation is to bait a `netstandard` API and switch the
platform specific implementation. Most of the Plugin type packages use this
pattern and in fact the `netstandard` "implementation" is more like a reference
assembly with just stubs that throw at runtime. `Xamarin.Essentials` is a good
example of this. The same rational can be applied to `netcoreapp` where this
would logically be the server implementation (or maybe a .NET Core 3.x desktop
implementation).

A `net6.0-maccatalyst` project referencing a NuGet package should behave as
follows:

* It should prefer `xamarin.ios` assets over `netcoreapp`, `netstandard`,
and `net5.0` assets.
- However, it should still prefer `net6.0` assets over `xamarin.ios`.
- It also shouldn't be compatible with any other existing Xamarin TFM (such
as `xamarin.mac`)
* Generate NuGet warning [NU1701] when a `xamarin.ios` asset is being used
Copy link

Choose a reason for hiding this comment

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

From a NuGet perspective this would need to be hard-coded into the product, one of the designs of NET5 was for the SDK & runtime to be able to add new platform implementations without NuGet's support, but that can't be the case here.

Copy link
Member Author

Choose a reason for hiding this comment

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

Agreed. As I said on email, I'm not particularly fond of this either. But I can get behind this because this doesn't introduce new crazy rules between the new TFMs but between a new TFM and an old TFM. I can justify that with our obligation to existing customers.

Copy link

@nkolev92 nkolev92 Feb 12, 2021

Choose a reason for hiding this comment

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

If xamarin.ios is preferred over netcoreapp, I think that the warning is not ideal. Same reasoning as above applies, it's not really a fallback, it's just how compatibility is in this case.

Copy link
Member Author

@terrajobst terrajobst Feb 16, 2021

Choose a reason for hiding this comment

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

The problem I see is this:

  1. Silently using the .NET Core assets is unlikely to result in a workable application due to bait & switch
  2. Silently using xamarin.ios, while having a higher chance of working, might not work either because Mac Catalyst isn't 100% compatible with iOS.

So it seems to me the precedence rules + warning is best user experience we can deliver.

Choose a reason for hiding this comment

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

Given 1, why not warn in the case of .NET Core assets older than 3.1 as well?

The thing that stands out to me is the following (actual names abstracted)

Given A:

A - works
A' - works
B - works (with a warning)
C - not likely to work (no warning).

I know it's an oversimplification, but just curious :)

Copy link
Member Author

@terrajobst terrajobst Feb 17, 2021

Choose a reason for hiding this comment

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

It's not that netcoreapp/netstandard assets wouldn't work in Mac Catalyst; it's that if a package offers both xamarin.ios and netcoreapp/netstandard then the likelyhood of netcoreapp/netstandard being the right one is low. IOW, its probability isn't just a function of the project TFM and a given offered TFM, it's about which TFM to pick given a set of TFMs. And that's what the precedence rules try to accommodate.

Does that make more sense?

- Package 'packageId' was restored using 'xamarin.ios' instead the project
target framework 'net6.0-maccatalyst'. This package may not be fully
compatible with your project.
* Should only use compatible `net5.0` based TFMs, namely `net5.0`, `net6.0`, and
`net6.0-maccatalyst`.
- Specifically, it should not accept `net6.0-ios`. The expectation is that
moving forward libraries that want to work on iOS and Mac Catalyst should
use `net6.0` or multi-target for `net6.0-ios` and `net6.0-maccatalyst`

We want the same rules for `net6.0-ios` except that using `xamarin.ios` should
not generate a warning. This reasoning results in these precedence rules:

**net6.0-ios**

1. `net6.0-ios`
1. `net6.0`
1. `xamarin.ios` (no warning)
1. `net5.0`
1. `netcoreapp3.1` – `1.0`
1. `netstandard2.1` – `1.0`
1. `net4.x` – `1.0` (warning)

**net6.0-maccatalyst**

1. `net6.0-ios`
terrajobst marked this conversation as resolved.
Show resolved Hide resolved
1. `net6.0`
1. `xamarin.ios` ([NU1701] warning)
1. `net5.0`
1. `netcoreapp3.1` – `1.0`
1. `netstandard2.1` – `1.0`
1. `net4.x` – `1.0 ([NU1701] warning)

**net6.0-macos**
terrajobst marked this conversation as resolved.
Show resolved Hide resolved

1. `net6.0-macos`
1. `net6.0` - `5.0`
1. `netcoreapp3.1` – `1.0`
1. `netstandard2.1` – `1.0`
1. `net4.x` – `1.0` ([NU1701] warning)

**net6.0-tvos**
terrajobst marked this conversation as resolved.
Show resolved Hide resolved

1. `net6.0-tvos`
1. `net6.0` - `5.0`
1. `netcoreapp3.1` – `1.0`
1. `netstandard2.1` – `1.0`
1. `net4.x` – `1.0` ([NU1701] warning)

## APIs

We need to add platform detection APIs for Mac Catalyst:

```C#
namespace System
{
public partial class OperatingSystem
{
public static bool IsMacCatalyst();
public static bool IsMacCatalystVersionAtLeast(int major, int minor = 0, int build = 0);
}
}
```

## Platform support annotations

It seems the Xamarin team wants to have a single assembly with bindings for the
Copy link
Member

Choose a reason for hiding this comment

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

Can we clarify the purpose of having a single assembly?

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 point I was trying to make was less about a single assembly vs multiple but a having a combined set of bindings for Mac-based platforms. Is that not accurate?

iOS platforms (e.g. Mac Catalyst and iOS). The APIs should be annotated with
`[SupportedOSPlatform]` and don't need any `[UnsupportedOSPlatform]` (the
absence of a platform is interpreted as the platform not being supported). For
instance:

```C#
public class SomeType
{
[SupportedOSPlatform("ios8.0")]
[SupportedOSPlatform("maccatalyst13.0")]
public void MethodSupportedOn_iOS_and_Mac_Catalyst();

[SupportedOSPlatform("ios8.0")]
public void MethodSupportedOn_iOS_Only();

[SupportedOSPlatform("maccatalyst13.0")]
public void MethodSupportedOn_Mac_Catalyst_Only();
}
```

However, from a user experience it's irrelevant if the Xamarin team provides the
bindings in separate libraries or in a single library because there is no TFM
that developers can use to build a single binary that can work in both Mac
Catalyst and iOS. A developer either has to target Mac Catalyst (via
`net6.0-maccatalyst`) or iOS (via `net6.0-ios`). Of course, the developer can
multi-target for both but that means two binaries are being produced which means
the Xamarin bindings could also be provided as separate libraries.

This is slightly different for cross-platform BCL APIs. Here we'd follow the
path we took for Blazor WebAssembly and mark APIs that don't work in Xamarin
with attributes like `[UnsupportedOSPlatform("iOS")].

## RIDs

We need to add RIDs for all these platforms:

* `net6.0-android`
- `android`
- `android-arm`
- `android-arm64`
- `android-x64`
- `android-x86`
- `android.21`
- `android.21-arm`
- `android.21-arm64`
- `android.21-x64`
- `android.21-x86`
- `android.22`
- `android.22-arm`
- `android.22-arm64`
- `android.22-x64`
- `android.22-x86`
- `android.23`
- `android.23-arm`
- `android.23-arm64`
- `android.23-x64`
- `android.23-x86`
- `android.24`
- `android.24-arm`
- `android.24-arm64`
- `android.24-x64`
- `android.24-x86`
- `android.25`
- `android.25-arm`
- `android.25-arm64`
- `android.25-x64`
- `android.25-x86`
- `android.26`
- `android.26-arm`
- `android.26-arm64`
- `android.26-x64`
- `android.26-x86`
- `android.27`
- `android.27-arm`
- `android.27-arm64`
- `android.27-x64`
- `android.27-x86`
- `android.28`
- `android.28-arm`
- `android.28-arm64`
- `android.28-x64`
- `android.28-x86`
- `android.29`
- `android.29-arm`
- `android.29-arm64`
- `android.29-x64`
- `android.29-x86`
- `android.30`
- `android.30-arm`
- `android.30-arm64`
- `android.30-x64`
- `android.30-x86`
* `net6.0-ios`
- `ios`
- `ios-arm`
- `ios-arm64`
- `ios-x64`
- `ios-x86`
- `ios.8`
- `ios.8-arm`
- `ios.8-arm64`
- `ios.8-x64`
- `ios.8-x86`
- `ios.9`
- `ios.9-arm`
- `ios.9-arm64`
- `ios.9-x64`
- `ios.9-x86`
- `ios.10`
- `ios.10-arm`
- `ios.10-arm64`
- `ios.10-x64`
- `ios.10-x86`
- `ios.11`
- `ios.11-arm64`
- `ios.11-x64`
- `ios.12`
- `ios.12-arm64`
- `ios.12-x64`
- `ios.13`
- `ios.13-arm64`
- `ios.13-x64`
- `ios.14`
- `ios.14-arm64`
- `ios.14-x64`
* `net6.0-maccatalyst`
- `maccatalyst-arm64`
terrajobst marked this conversation as resolved.
Show resolved Hide resolved
- `maccatalyst-x64`
- `maccatalyst.13-arm64`
- `maccatalyst.13-x64`
- `maccatalyst.14-arm64`
- `maccatalyst.14-x64`
* `net6.0-macos`
- `osx`
- `osx-arm64`
- `osx-x64`
- `osx.10.10`
- `osx.10.10-arm64`
- `osx.10.10-x64`
- `osx.10.11`
- `osx.10.11-arm64`
- `osx.10.11-x64`
- `osx.10.12`
- `osx.10.12-arm64`
- `osx.10.12-x64`
- `osx.10.13`
- `osx.10.13-arm64`
- `osx.10.13-x64`
- `osx.10.14`
- `osx.10.14-arm64`
- `osx.10.14-x64`
- `osx.10.15`
- `osx.10.15-arm64`
- `osx.10.15-x64`
- `osx.10.16`
- `osx.10.16-arm64`
- `osx.10.16-x64`
- `osx.11.0`
- `osx.11.0-arm64`
- `osx.11.0-x64`
* `net6.0-tvos`
- `tvos`
- `tvos-arm64`
- `tvos-x64`
- `tvos.10`
- `tvos.10-arm64`
- `tvos.10-x64`
- `tvos.11`
- `tvos.11-arm64`
- `tvos.11-x64`
- `tvos.12`
- `tvos.12-arm64`
- `tvos.12-x64`
- `tvos.13`
- `tvos.13-arm64`
- `tvos.13-x64`
- `tvos.14`
- `tvos.14-arm64`
- `tvos.14-x64`

## Q & A

### Why didn't we name it `net6.0-macos-catalyst`?

We didn't want a secondary dash because it implies some relationship with
`net6.0-macos`. Also, in the past we talked about supporting prefix-based
compatibility rules with more levels, using a dash for this new TFM would make
it harder to add support for this later.

### Why didn't we name it `net6.0-catalyst`?

Looks like [Apple refers to it as "Mac Catalyst"](https://developer.apple.com/mac-catalyst/).

### Why did we make `net6.0-maccatalyst` compatible with the existing Xamarin TFMs?

It's true that for native code existing binaries don't work in Mac
Catalyst and that developers are generally expected to recompile. However, the
Xamarin team believes that enough managed code would just work that makes this
compat relationship useful. This follows the principle of the .NET Framework
compatibility mode which is:

> Instead of blocking things that might not work, unblock things that could
> work.

### Why did we make `net6.0-maccatalyst` prefer `net6.0` over `xamarin.ios`?

The intention of making `net6.0-maccatalyst` being able to use `xamarin.ios` is
so that the new platform can use packages that were designed before Mac Catalyst
existed but would have a high chance of working just fine.

Considering that many Xamarin packages use bait & switch (that is they provide a
cross-platform API but have a Xamarin-specific implementation) it is desirable
that `xamarin.ios` is preferred over `netcoreapp` and `netstandard`. This also
includes `net5.0`, because it is the successor of `netstandard` and existed
before `net6.0-maccatalyst`.

This rationale doesn't apply for packages that target .NET 6. When they do that,
they are generally not expected to target the old Xamarin TFMs but the new .NET
6 TFMs, such as `net6.0-maccatalyst` and `net6.0-ios`. As such, preferring
`net6.0` over `xamarin.ios` is more sensible. It also avoids ambiguities for
package authors that want to ship assets for the old Xamarin stack and the new
.NET 6-based Xamarin stack.

### Why didn't we make `net6.0-ios` compatible with `net6.0-maccatalyst`?

The expectation is that moving forward libraries that want to work on iOS and
Mac Catalyst would use `net6.0` or multi-target for `net6.0-ios` and
`net6.0-maccatalyst`

[net5.0]: ../../2020/net5/net5.md
[NU1701]: https://docs.microsoft.com/en-us/nuget/reference/errors-and-warnings/nu1701