-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Description
Background and Motivation
.NET 6 heavily relies on the platform compatibility analyzer, linker and operating system detection on the mobile platforms. Currently there's a parity between the values returned by OperatingSystem.IsXXX()
APIs, the UnsupportedOSPlatform("xxx")
and attributes. The target framework moniker (TFM) also uses the same XXX
syntax for platform suffix. All the OperatingSystem.IsXXX()
APIs are mutually exclusive and at most one of them returns true
on a given platform.
Unlike most TFMs the Mac Catalyst has an implicit relationship with the iOS TFM. Application targeting net6.0-maccatalyst
may consume library assets that were built with net6.0-ios
TFMs. This creates a disparity where this relationship is not captured by the OperatingSystem.IsIOS/IsMacCatalyst
APIs and the unavailable Mac Catalyst APIs have to include explicit UnsupportedOSPlatform("maccatalyst")
annotations even though they don't target net6.0-maccatalyst
directly. Failure to do so would currently be silently ignored and a transitive library consumption will not produce Platform Compatibility Analyzer warnings.
Additionally, libraries targeting net6.0
and including iOS specific logic can easily fall into a trap of guarding the code with OperatingSystem.IsIOS()
when the correct condition is OperatingSystem.IsIOS() || OperatingSystem.IsMacCatalyst()
.
Similarly, in native C / Objective-C / Swift code the platform availability guards implicitly imply the Mac Catalyst as a variant of iOS.
Platform guard example in C
Consider the following C code:
#include <stdio.h>
int main()
{
#if __is_target_os(ios)
printf("__is_target_os(ios): true\n");
#endif
if (__builtin_available(iOS 16, *)) {
printf("__builtin_available iOS 16\n");
}
if (__builtin_available(iOS 10, *)) {
printf("__builtin_available iOS 10\n");
}
if (__builtin_available(iOS 16, macCatalyst 11, *)) {
printf("__builtin_available macCatalyst\n");
}
}
It can be compiled for Mac Catalyst by running clang -target x86_64-apple-ios13.0-macabi avail.c -o avail
and it produces the following output:
__is_target_os(ios): true
__builtin_available iOS 10
__builtin_available macCatalyst
The interpretation is that __is_target_os(...)
treats Mac Catalyst as iOS variant. __builtin_available
uses the iOS <version>
value on Mac Catalyst unless an explict check is specified.
Proposed solutions
Proposal A
OperatingSystem.IsIOS()
would returntrue
on both Mac Catalyst and iOS. In majority of the cases that is what the developer wants to check since Mac Catalyst is supposed to be a superset of iOS. Current runtime checks that doOperatingSystem.IsIOS() || OperatingSystem.IsTvOS() || OperatingSystem.IsMacCatalyst()
would be shortened toOperatingSystem.IsIOS() || OperatingSystem.IsTvOS()
.- Keep
UnsupportedOSPlatform("XXX")
consistent withOperatingSystem.IsXXX
, both in the Platform Compatibility Analyzer and in linker. Thus specifyingUnsupportedOSPlatform("ios")
would imply that an API is also unsupported on Mac Catalyst. Duplicate UnsupportedOSPlatform("ios") and UnsupportedOSPlatform("maccatalyst") attributes would coalesce into one. - For the rare case where you actually want to behave differently on iOS and MacCatalyst you would use a combination of the checks / attributes. An iOS-only API would be decorated with
[UnsupportedOSPlatform("maccatalyst")]
and runtime check would be!OperatingSystem.IsMacCatalyst()
. A MacCatalyst-only API would be decorated with[UnsupportedOSPlatform("ios")]
and[SupportedOSPlatform("maccatalyst")]
(or similar). Code block guarding specifically for iOS and not Mac Catalyst would useOperatingSystem.IsIOS() && !OperatingSystem.IsMacCatalyst()
.
Proposal B
-
Add
OperatingSystem.IsIOSOrMacCatalyst()
API with appropriateUnsupportedOSPlatformGuard
attributes. This would simplify the checks in code while keeping theOperatingSystem.IsXXX
APIs more consistent. There's a potential error for the caller to keep usingIsIOS
whereIsIOSOrMacCatalyst
should have been used. Casual observation suggests that most of theIsIOS()
API usages in .NET runtime itself would be replaceable with this alternate API since they doIsIOS() || IsMacCatalyst()
check anyway. -
Teach the Platform Compatibility analyzer about the additional TFM relationship and enforce additional rules when targeting
net6.0-ios
and not targetingnet6.0-maccatalyst
in a library code (ie. adding explicit supported/unsupported MacCatalyst annotations where iOS annotations are present; additional checks for use of theIsIOS()
API). [TODO]
Additional design considerations
- Should
OperatingSystem.IsXXX
map the TFM fallbacks in general? - Should there be a relation to how RIDs are structured too?
- Should
IsLinux()
returntrue
on Android?
Likely not; the API surface is significantly different, there's prior art with Flutter:This value is false if the operating system is a specialized version of Linux that identifies itself by a different name, for example Android (see isAndroid).
/cc @terrajobst @jeffhandley for design decisions
Kudos to @filipnavara for the write-up.