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

Add spec for MinimumOSVersion. #97

Merged
merged 10 commits into from
Apr 17, 2020
Merged

Conversation

mhutch
Copy link
Member

@mhutch mhutch commented Mar 13, 2020

This spec describes a standard mechanism for libraries to express the availability of APIs on different platforms and different versions of those platforms using attributes and MSBuild properties, and how those annotations can be used to improve the developer experience at multiple levels. It's based on the model already present in Xamarin.iOS.

I expect parts to be split into other documents and refined but this should be a good starting point.

@mhutch
Copy link
Member Author

mhutch commented Mar 13, 2020

cc @jonpryor @rolfbjarne @spouliot

@mhutch mhutch requested a review from richlander March 13, 2020 03:49
accepted/2020/minimum-os-version/minimum-os-version.md Outdated Show resolved Hide resolved
accepted/2020/minimum-os-version/minimum-os-version.md Outdated Show resolved Hide resolved
accepted/2020/minimum-os-version/minimum-os-version.md Outdated Show resolved Hide resolved
accepted/2020/minimum-os-version/minimum-os-version.md Outdated Show resolved Hide resolved


- the call is guarded by a `CheckOS` or `CheckOSVersion` call against a version that is greater than or equal to the member’s minimum platform version
- the caller member has a minimum platform version that is equal to or higher than the callee

Choose a reason for hiding this comment

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

There's potential to have analyzers to ensure availability attributes are logical - but it brings some special cases.

E.g. when a new type is introduced in a hierarchy, which is not a breaking change, you can end up with

[Introduced (.ios, 8,0)]
class NSFoo : NSObject {
   string Id {get;}
   string Name {get;}
}

becoming

[Introduced (.ios, 14,0)]
class NSMiddle : NSObject {
   [Introduced (.ios, 8,0)]
   string Id {get;}
}

[Introduced (.ios, 8,0)]
class NSFoo : NSMiddle {
   string Name {get;}
}

and it's easy to think there's a mistake in Middle since a member was available well before it's type. While you can't create NSMiddle on iOS8 you can still use NSFoo.Middle (without warnings) on iOS8.

Copy link

Choose a reason for hiding this comment

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

We can use Windows's existing version and api tagging or we could go with existing tags which are already there to expose them as such in a more .NET/WinMD way. Existing tools could be easily supported if we did follow latter idea.

Try to use ideas as much as from the Windows platform teams. They are relevant to all other platforms out there.

@marek-safar
Copy link
Contributor

/cc @joshpeterson


The `Microsoft.Net.Sdk` targets will extract the OS API Version component from the `TargetFramework` into the `TargetPlatformVersion` MSBuild property and burn it into an assembly attribute. Tools that use this value are expected to access it from the MSBuild property or from the assembly attribute.

Project files may specify a `MinimumPlatformVersion`, and if they do not it will default to the `TargetPlatformVersion` value. The `MinimumPlatformVersion` must not be higher than the `TargetPlatformVersion`, and this will be validated at the start of the build.
Copy link
Contributor

Choose a reason for hiding this comment

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

Immo's TFM spec refers to this as TargetPlatformMinVersion which is what we use in Windows today. Should this be changed to that?

Suggested change
Project files may specify a `MinimumPlatformVersion`, and if they do not it will default to the `TargetPlatformVersion` value. The `MinimumPlatformVersion` must not be higher than the `TargetPlatformVersion`, and this will be validated at the start of the build.
Project files may specify a `TargetPlatformMinVersion`, and if they do not it will default to the `TargetPlatformVersion` value. The `TargetPlatformMinVersion` must not be higher than the `TargetPlatformVersion`, and this will be validated at the start of the build.

Copy link
Member Author

Choose a reason for hiding this comment

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

These two specs were written in parallel after we had a discussion a few weeks ago - I don't think either name is canonical right now.

Copy link

Choose a reason for hiding this comment

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

Those property names exist in Windows SDK for a while now. That's why I suggested using those. We can conceptually map those 3 version properties for all platforms, no matter what their platform specific behaviour is.

They differ and that's why we have different platforms 😐. But we don't have to bring that complexity into the user facing project file.

accepted/2020/minimum-os-version/minimum-os-version.md Outdated Show resolved Hide resolved

> NOTE: would `{PlatformIdentifier}MinimumVersion` be better? It would sort/group more easily with other OS-specific properties.

> NOTE: Using the existing `TargetPlatformVersion` property to represent the API version may be confusing for platforms such as Android that allow having different values for the target version and API version..
Copy link
Contributor

Choose a reason for hiding this comment

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

when would you want to specify a different target and API version?

Copy link

Choose a reason for hiding this comment

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

Android always target the API levels. So *MinVersion -> becomes minimum API level, *MaxVersion becomes targeted API level and we can have TargetPlatformVersion point to either of these depending on the project or have new value that mirrors the behaviour of TargetPlatformMaxVersionTested.

Before you guys ask, yes, Windows SDK has more than 4 properties just for versions and 3 just for UWP project files!

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 comments on things that could be improved.


Platform-neutral assemblies, i.e. assemblies that target a platform-neutral TFM such as `net5`, will have to declare the attribute explicitly.

> NOTE: This seems a bit inconsistent. As a user, I’d expect `Minimum{OSName}Version` properties to work in platform-neutral assemblies too, but handling all those properties when building platform-neutral assemblies would be problematic as it would centralize the definitions of all of the platform-specific `MinimumVersion` properties into the `Microsoft.Net.Sdk` targets.
Copy link

Choose a reason for hiding this comment

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

As a user, I expect the runtime take care of the compatibility but with escape hatches for users (like me...) who want to further configure certain behaviours (based on some custom native lib and it's interop lib that I might be introducing into the App).

But for large pool of libs, they are mostly pure IL than native code. so, yes it's inconsistent!


> NOTE: This seems a bit inconsistent. As a user, I’d expect `Minimum{OSName}Version` properties to work in platform-neutral assemblies too, but handling all those properties when building platform-neutral assemblies would be problematic as it would centralize the definitions of all of the platform-specific `MinimumVersion` properties into the `Microsoft.Net.Sdk` targets.
>
>Perhaps we could use items and metadata instead of platform-specific properties, e.g. `<PlatformInfo Include="ios" MinimumVersion="15.0" />`. For platform-specific projects, we could allow the simpler `MinimumPlatformVersion` property by mapping it into one of these items, e.g. `<PlatformInfo Include="$(TargetPlatformIdentifier)" Version="$(MinimumPlatformVersion)" Condition="'$(TargetPlatformIdentifier)' != ''" />`.
Copy link

Choose a reason for hiding this comment

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

We can do Items for build side but it can't be used user facing, since they won't be available to users for customizing the builds.

You could have this...

<TargetPlatformInfo Include="$(TargetPlatformIdentifier)" MinVersion="$(TargetPlatformMinVersion)" Version="$(TargetPlatformVersion)" MaxVersion="$(TargetPlatformMaxVersion)"/>

but only for MSBuild scripts while authoring the SDKs but not as a user facing one.

Comment on lines 201 to 209
<metadata>
<platformInfo>
<net5-ios15.0 minimumVersion="13.0" />
<net5-mac10.15 minimumVersion="10.11" />
</platformInfo>
</metadata>
Copy link

Choose a reason for hiding this comment

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

Suggested change
<metadata>
<platformInfo>
<net5-ios15.0 minimumVersion="13.0" />
<net5-mac10.15 minimumVersion="10.11" />
</platformInfo>
</metadata>
<metadata>
<platforms framework="net5.0">
<platform id="ios" minVersion="13.0" targetVersion="13.2" maxVersion="14.0">
<platform id="mac" minVersion="10.10" targetVersion="10.11" maxVersion="10.14">
<platform id="android" minVersion="18" targetVersion="21" maxVersion="24">
</platforms>
<platforms framework="net6.0">
<platform id="ios" minVersion="13.1" targetVersion="13.3" maxVersion="14.0">
<platform id="mac" minVersion="10.11" targetVersion="10.12" maxVersion="10.14">
<platform id="android" minVersion="19" targetVersion="22" maxVersion="24">
</platforms>
</metadata>

We can use this format for the platform neutral declarations too (see below). These are only needed if we have interop between native lib and a managed implementation but could be useful for binding libs too as they are essentially more or less the same!

targetVersion and maxVersion are totally optional. They could used to further manage the dependencies and resolve conflicts for large typical project that targets multiple platforms.



- the call is guarded by a `CheckOS` or `CheckOSVersion` call against a version that is greater than or equal to the member’s minimum platform version
- the caller member has a minimum platform version that is equal to or higher than the callee
Copy link

Choose a reason for hiding this comment

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

We can use Windows's existing version and api tagging or we could go with existing tags which are already there to expose them as such in a more .NET/WinMD way. Existing tools could be easily supported if we did follow latter idea.

Try to use ideas as much as from the Windows platform teams. They are relevant to all other platforms out there.

```csharp
[Introduced(PlatformIdentifier.macOS, 10, 2)]
[Deprecated(PlatformIdentifier.macOS, 10, 9)]
[Removed(PlatformIdentifier.macOS, 10, 12, 1)]
Copy link

@Nirmal4G Nirmal4G Apr 2, 2020

Choose a reason for hiding this comment

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

Why not use solutions like WinMD and XLang to generate metadata (bindings) about the platforms' APIs instead manually duplicating the Swift's attributes into .NET attributes?

That way bindings can be language/framework neutral and in unified format.

@kennykerr @migueldeicaza Is this possible?

Copy link
Member

@richlander richlander Apr 8, 2020

Choose a reason for hiding this comment

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

This is a super simple and inexpensive mechanism. We don't want to take on heavy weight dependencies for it. It's also "OK" to duplicate attributes like this across dev stacks.


The OS API Version is part of the target framework, and affects which framework reference assemblies the app is compiled against. It does not affect which OS versions the app can run on (for well behaved platforms where backwards ABI compatibility is preserved). The primary reason for an app project to target an old OS API version is to allow the project to continue to build on older versions of the SDK that do not support the more recent API versions. A secondary reason may be to limit the API surface shown in IntelliSense so that it’s easier to avoid newer APIs, though this use case could perhaps be better supported in future by IDE experiences such allowing filtering IntelliSense by platform version.

However, for libraries that are distributed as NuGets, it is useful to be able to multi-target across multiple API versions. This allows NuGets to access features available in newer API versions while continuing to ship updates for consumption by apps and libraries that (for whatever reason) target older API versions.
Copy link
Member

Choose a reason for hiding this comment

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

Maybe too niche, but in a big shop, you could also use this same pattern for P2P.

Copy link
Member Author

Choose a reason for hiding this comment

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

True! I'm not sure it's worth specifically calling it out though

</metadata>
```

A reference assembly for a platform-neutral TFM (e.g. `net5` or `net6`) may still need to express platform-specific minimum versions if the implementations are platform-specific (aka ‘bait and switch’). As before, the element matches the TFM of the reference assembly, but this time there is no `minimumVersion`. Instead there are sub-elements that provide `minimumVersion` values for platforms using the `TargetPlatformIdentifier` of those platforms to identify them:
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
A reference assembly for a platform-neutral TFM (e.g. `net5` or `net6`) may still need to express platform-specific minimum versions if the implementations are platform-specific (aka ‘bait and switch’). As before, the element matches the TFM of the reference assembly, but this time there is no `minimumVersion`. Instead there are sub-elements that provide `minimumVersion` values for platforms using the `TargetPlatformIdentifier` of those platforms to identify them:
A reference assembly for a platform-neutral TFM (e.g. `net5.0` or `net6.0`) may still need to express platform-specific minimum versions if the implementations are platform-specific (aka ‘bait and switch’). As before, the element matches the TFM of the reference assembly, but this time there is no `minimumVersion`. Instead there are sub-elements that provide `minimumVersion` values for platforms using the `TargetPlatformIdentifier` of those platforms to identify them:

Copy link
Member

Choose a reason for hiding this comment

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

Honest question ... do we have satisfactory support for bait-and-switch in dotnet pack today? If not, it should noted.

Copy link
Member Author

Choose a reason for hiding this comment

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

No, we do not. That was one of the things that NuGetizer did well.

@jonwis
Copy link

jonwis commented Apr 8, 2020

From the Windows Platform POV, using version numbers is only half the story.

Apps should use feature-detection and light-up through runtime metadata:

Some individual Windows API also have IsSupported methods that further refine the answer. While MSDN does document "this API is available in Windows 10 Creator's Edition" there are times when an API has been serviced into an earlier version of the OS. Windows is not a single linear edition. Certain APIs are available (or functional) on Enterprise or Server editions.

We recognize that this matrix makes developing version-and-edition software challenging, and having a standardized approach like this is useful for declaring in code what's possible when.

Are applications ever expected to call the proposed APIs, or are they only for components to know if they can do their work, or only compile-time markup? I would not want to see an application say:

if (RuntimeInformation.CheckWindowsVersion(WindowsVersions.Win10_1903)) {
    var x = new Component { ... };
    x.Muffin();
}

Instead, the application should say:

if (Component.IsSupported()) {
    var x = new Component { ... };
}

And let the component do the light-up checks. That way when the Component figures out a way to work on Windows 10 1803 the app doesn't have to find and fix the CheckWindowsVersions points.

@mhutch
Copy link
Member Author

mhutch commented Apr 15, 2020

From the Windows Platform POV, using version numbers is only half the story.

Apps should use feature-detection and light-up through runtime metadata:

Some individual Windows API also have IsSupported methods that further refine the answer. While MSDN does document "this API is available in Windows 10 Creator's Edition" there are times when an API has been serviced into an earlier version of the OS. Windows is not a single linear edition. Certain APIs are available (or functional) on Enterprise or Server editions.

We recognize that this matrix makes developing version-and-edition software challenging, and having a standardized approach like this is useful for declaring in code what's possible when.>
Are applications ever expected to call the proposed APIs, or are they only for components to know if they can do their work, or only compile-time markup? I would not want to see an application say:

if (RuntimeInformation.CheckWindowsVersion(WindowsVersions.Win10_1903)) {
    var x = new Component { ... };
    x.Muffin();
}

Applications would be expected to call these APIs, though in some cases the JIT/AOT compiler could elide these checks.

Instead, the application should say:

if (Component.IsSupported()) {
    var x = new Component { ... };
}

And let the component do the light-up checks. That way when the Component figures out a way to work on Windows 10 1803 the app doesn't have to find and fix the CheckWindowsVersions points.

iOS and Android don't neatly support these kinds of checks though. The bindings are thin wrappers over the native APIs of the underlying platform, which in a given OS release may add things as granular as new helper methods on existing classes. We could probably we could probably break a lot of these new APIs down into feature/component/capability groups, but it would be a lot of error-prone manual annotation work for no real gain.

That said, I do think some kind of RequiresCapabilityAttribute annotations and RuntimeInformation.HasCapability(capability) check would be useful and complementary to the version checks. Capability checks are often used for features that are dependent on hardware, or as a mechanism to abstract out the underlying OS in multi-platform libraries. We could consider bridging the two mechanisms with some kind of "OS A, version B is known to support C,D,E capabilities" metadata.

accepted/2020/minimum-os-version/minimum-os-version.md Outdated Show resolved Hide resolved
accepted/2020/minimum-os-version/minimum-os-version.md Outdated Show resolved Hide resolved
accepted/2020/minimum-os-version/minimum-os-version.md Outdated Show resolved Hide resolved

# MSBuild Properties

The `Microsoft.Net.Sdk` targets will extract the OS API Version component from the `TargetFramework` into the `TargetPlatformVersion` MSBuild property and burn it into an assembly attribute. Tools that use this value are expected to access it from the MSBuild property or from the assembly attribute.

Project files may specify a `MinimumPlatformVersion`, and if they do not it will default to the `TargetPlatformVersion` value. The `MinimumPlatformVersion` must not be higher than the `TargetPlatformVersion`, and this will be validated at the start of the build.
Project files may specify an `OSMinimumVersion`, and if they do not it will default to OS Version equivalent to the OS API version specified in the `TargetPlatformVersion` value. The `OSMinimumVersion` must not be higher than the `TargetPlatformVersion`, and this will be validated at the start of 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.

How will we handle this if we update the bindings outside of the OS version. IE we ship iOS bindings 15.0.1, but it still maps to iOS 15.0. Does each workload need to keep a mapping between TargetPlatformVersion and OSMinimumVersion for that platform?

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, that's unfortunate but necessary. Still thinking about whether to formally define a standard mechanism or leave it up to the platform targets.

Choose a reason for hiding this comment

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

Why not define a roll forward mechanism for bindings just like the runtime packages?

That would certainly remove these complications!!

Copy link
Member Author

Choose a reason for hiding this comment

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

@Nirmal4G it's not that easy.

For example: from the TFM net5-ios15.0 we can infer that the OSMinimumVersion default is 15.0. However, suppose the TFM net5-ios15.0.1 was a revision of the bindings rather than bindings for a new OS version, then its OSMinimumVersion would also be need to be 15.0.

We could handle that in a general way by saying that only the first two components of the binding version are the OS version and the third component is the bindings revision. However, those kinds of conventions we need to be determined on a platform by platform basis, as different platforms have different versioning schemes.

Choose a reason for hiding this comment

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

This is still a problem in the current proposal with the TF having bindings revision. Do we expect people to target the same platform for each bindings revision, I don't think so. I didn't encounter any packages targeting specifically any platform with multiple bindings!!! Are there?

If so, Why not separate the bindings version into a separate property?

Say PlatformPackageVersion if we're using nuget as the delivery mechanism for the bindings and PlatformBindingsRevision if we're shipping bindings within the workload itself. If we're using both we could use the latter to set the former for resolving the implicit FrameworkReference.


> NOTE: Using the existing `TargetPlatformVersion` property to represent the API version may be confusing for platforms such as Android that allow having different values for the target version and API version..
> ***NOTE***: `{PlatformIdentifier}MinimumVersion` was chosen over `Minimum{PlatformIdentifier}Version` as it it's more easily searchable and sorts/group more easily with other OS-specific properties.
Copy link
Member

Choose a reason for hiding this comment

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

I think Windows already has a property defined that follows a different format: TargetPlatformMinVersion. We should cover how that relates to the new properties.

Copy link
Member Author

Choose a reason for hiding this comment

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

Added a clarifying note - 7c34d18 /cc @terrajobst

Choose a reason for hiding this comment

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

Apple platforms and Windows defines the same version for both API and OS. But Android has API levels. But do the MSBuild projects (and the Android SDK tools) need the OS version? If so, Why?

accepted/2020/minimum-os-version/minimum-os-version.md Outdated Show resolved Hide resolved
@mhutch mhutch merged commit c98a850 into dotnet:master Apr 17, 2020
@mhutch mhutch deleted the minimum-os-version branch April 17, 2020 22:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.