-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Moving platform guards out of System.Runtime.InteropServices #40111
Comments
The string-based checks like this are slow by design. It means that people will need to cache their results for hot paths. If all that we got are these string based-checks, the analyzer would need to be smart enough to deal with cached results of these checks. |
cc @adamsitnik |
Will the other overloads be fast enough though or will folks always have to cache if perf is critical? If so, I think we're better off either making the analyzer smarter or just telling people to suppress in hot paths. |
It should be possible to intrinsify the check: the current OS are constant during JIT, and the the string is typically constant too. |
Yes, it is possible in theory, but it makes the complexity go through the roof once things like AOT are added to the mix. |
For now analyzer only keep track of local cache result, we talked about this here: void M1()
{
var canUse = RuntimeInformationHelper.IsOSPlatformOrLater(OSPlatform.Windows, 11);
if (canUse )
{
M2(); // Will not warn
}
else
{
M2(); // Would warn
}
}
[MinimumOSPlatform(""Windows10.1.2.3"")]
void M2()
{
} |
Current thinking is this: namespace System
{
public partial class Environment
{
// Primary
public static bool IsOSPlatform(string platform);
public static bool IsOSPlatformVersionAtLeast(string platform, int major, int minor = 0, int build = 0, int revision = 0);
// Accelerators for platforms where versions don't make sense
public static bool IsBrowser();
public static bool IsFreeBSD();
public static bool IsLinux();
// Accelerators with version checks
public static bool IsAndroid();
public static bool IsAndroidVersionAtLeast(int major, int minor = 0, int build = 0, int revision = 0);
public static bool IsiOS();
public static bool IsiOSVersionAtLeast(int major, int minor = 0, int build = 0, int revision = 0);
public static bool IsmacOS();
public static bool IsmacOSVersionAtLeast(int major, int minor = 0, int build = 0, int revision = 0);
public static bool IstvOS();
public static bool IstvOSVersionAtLeast(int major, int minor = 0, int build = 0, int revision = 0);
public static bool IswatchOS();
public static bool IswatchOSVersionAtLeast(int major, int minor = 0, int build = 0, int revision = 0);
public static bool IsWindows();
public static bool IsWindowsVersionAtLeast(int major, int minor = 0, int build = 0, int revision = 0);
}
[AttributeUsage(AttributeTargets.Assembly |
AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Enum |
AttributeTargets.Event |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Module |
AttributeTargets.Property |
AttributeTargets.Struct,
AllowMultiple = true, Inherited = false)]
public sealed class SupportedOSPlatformAttribute : OSPlatformAttribute
{
public SupportedOSPlatformAttribute(string platformName);
}
[AttributeUsage(AttributeTargets.Assembly |
AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Enum |
AttributeTargets.Event |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Module |
AttributeTargets.Property |
AttributeTargets.Struct,
AllowMultiple = true, Inherited = false)]
public sealed class UnsupportedOSPlatformAttribute : OSPlatformAttribute
{
public UnsupportedOSPlatformAttribute(string platformName);
}
}
namespace System.Runtime.Versioning
{
public abstract class OSPlatformAttribute : Attribute
{
private protected OSPlatformAttribute(string platformName);
public string PlatformName { get; }
}
[AttributeUsage(AttributeTargets.Assembly,
AllowMultiple=false, Inherited=false)]
public sealed class TargetPlatformAttribute : OSPlatformAttribute
{
public TargetPlatformAttribute(string platformName);
}
} |
Environment has been a grab-bag for everything. It has ~40 methods for everything from current directory to current thread id and current time. The enriched counterparts of the one-off Environment methods has been separate types. For example, we have |
If we go with the discussed plan of deemphasizing namespace System.Runtime.InteropServices
{
public readonly partial struct OSPlatform
{
- public static System.Runtime.InteropServices.OSPlatform Android { get { throw null; } }
- public static System.Runtime.InteropServices.OSPlatform Browser { get { throw null; } }
public static System.Runtime.InteropServices.OSPlatform FreeBSD { get { throw null; } }
- public static System.Runtime.InteropServices.OSPlatform iOS { get { throw null; } }
public static System.Runtime.InteropServices.OSPlatform Linux { get { throw null; } }
- public static System.Runtime.InteropServices.OSPlatform macOS { get { throw null; } }
- [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public static System.Runtime.InteropServices.OSPlatform OSX { get { throw null; } }
- public static System.Runtime.InteropServices.OSPlatform tvOS { get { throw null; } }
- public static System.Runtime.InteropServices.OSPlatform watchOS { get { throw null; } }
public static System.Runtime.InteropServices.OSPlatform Windows { get { throw null; } }
...
}
} |
namespace System
{
public partial class OperatingSystem
{
// Primary
public static bool IsOSPlatform(string platform);
public static bool IsOSPlatformVersionAtLeast(string platform, int major, int minor = 0, int build = 0, int revision = 0);
// Accelerators for platforms where versions don't make sense
public static bool IsBrowser();
public static bool IsLinux();
// Accelerators with version checks
public static bool IsFreeBSD();
public static bool IsFreeBSDVersionAtLeast(int major, int minor = 0, int build = 0, int revision = 0);
public static bool IsAndroid();
public static bool IsAndroidVersionAtLeast(int major, int minor = 0, int build = 0, int revision = 0);
public static bool IsIOS();
public static bool IsIOSVersionAtLeast(int major, int minor = 0, int build = 0, int revision = 0);
public static bool IsMacOS();
public static bool IsMacOSVersionAtLeast(int major, int minor = 0, int build = 0, int revision = 0);
public static bool IsTvOS();
public static bool IsTvOSVersionAtLeast(int major, int minor = 0, int build = 0, int revision = 0);
public static bool IsWatchOS();
public static bool IsWatchOSVersionAtLeast(int major, int minor = 0, int build = 0, int revision = 0);
public static bool IsWindows();
public static bool IsWindowsVersionAtLeast(int major, int minor = 0, int build = 0, int revision = 0);
}
}
namespace System.Runtime.Versioning
{
public abstract class OSPlatformAttribute : Attribute
{
private protected OSPlatformAttribute(string platformName);
public string PlatformName { get; }
}
[AttributeUsage(AttributeTargets.Assembly,
AllowMultiple=false, Inherited=false)]
public sealed class TargetPlatformAttribute : OSPlatformAttribute
{
public TargetPlatformAttribute(string platformName);
}
[AttributeUsage(AttributeTargets.Assembly |
AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Enum |
AttributeTargets.Event |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Module |
AttributeTargets.Property |
AttributeTargets.Struct,
AllowMultiple = true, Inherited = false)]
public sealed class SupportedOSPlatformAttribute : OSPlatformAttribute
{
public SupportedOSPlatformAttribute(string platformName);
}
[AttributeUsage(AttributeTargets.Assembly |
AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Enum |
AttributeTargets.Event |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Module |
AttributeTargets.Property |
AttributeTargets.Struct,
AllowMultiple = true, Inherited = false)]
public sealed class UnsupportedOSPlatformAttribute : OSPlatformAttribute
{
public UnsupportedOSPlatformAttribute(string platformName);
}
[AttributeUsage(AttributeTargets.Assembly |
AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Event |
AttributeTargets.Method |
AttributeTargets.Module |
AttributeTargets.Property |
AttributeTargets.Struct,
AllowMultiple=true, Inherited=false)]
public sealed class ObsoletedInOSPlatformAttribute : OSPlatformAttribute
{
public ObsoletedInPlatformAttribute(string platformName);
public ObsoletedInPlatformAttribute(string platformName, string message);
public string Message { get; }
public string Url { get; set; }
}
} namespace System.Runtime.InteropServices
{
public readonly partial struct OSPlatform
{
- public static System.Runtime.InteropServices.OSPlatform Android { get; }
- public static System.Runtime.InteropServices.OSPlatform Browser { get; }
public static System.Runtime.InteropServices.OSPlatform FreeBSD { get; }
- public static System.Runtime.InteropServices.OSPlatform iOS { get; }
public static System.Runtime.InteropServices.OSPlatform Linux { get; }
- public static System.Runtime.InteropServices.OSPlatform macOS { get; }
- [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public static System.Runtime.InteropServices.OSPlatform OSX { get; }
- public static System.Runtime.InteropServices.OSPlatform tvOS { get; }
- public static System.Runtime.InteropServices.OSPlatform watchOS { get; }
public static System.Runtime.InteropServices.OSPlatform Windows { get; }
}
} |
Should the remaining members of With the current proposal we have different "options" to check for Windows / Linux / ..., so questions may arise for
In short: it might be confusing why to have different apis for the same concern. And |
Yeah, it certainly is confusing. Obsoleting the @terrajobst What do you think about obsoleting the older checks? |
While working on the implementation I've realised that on OSX we don't have the runtime/src/libraries/Common/src/Interop/OSX/Interop.libobjc.cs Lines 34 to 55 in a778b7e
@eerhardt @terrajobst Should I remove the last parameter from OSX family methods because it is always ignored or keep it because we might add in the future? -public static bool IsMacOSVersionAtLeast(int major, int minor = 0, int build = 0, int revision = 0)
+public static bool IsMacOSVersionAtLeast(int major, int minor = 0, int build = 0) I would prefer to remove it because the OSX method that we use returns a type that simply don't have it so it will rather not change in the future: |
I think it makes sense to make the APIs conform to the OS they are specific for. We discussed this a bit during the first round of reviews this. And we are also already doing this with: public static bool IsBrowser();
public static bool IsLinux(); Because the versions of those OS platforms aren't useful. Just like the So I agree with you @adamsitnik, I think we should drop the version numbers from OS platforms where it makes sense. |
should we also call the third argument |
|
@am11 you mean because there is |
The PRs to runtime repo has been merged: The PRs to SDK and ASP.NET got approved and are waiting to be merged: dotnet/sdk#12775 I've also created new issues in SDK and ASP.NET repo and suggested to switch to use the new APIs for the performance gains: dotnet/aspnetcore#24653 And ported what was portable in runtime repo: Closing. |
@akoeplinger, I meant the general shape of public least version APIs (not that particular private method; that was added as part of implementation of these APIs). E.g. instead of |
@am11 hmm I think that option wasn't discussed in the API review, calling an iOS 13.1+ API would look like this: I don't see an immediate issue with that but I haven't thought too deeply about what the implications for the linker and platform analyzer are. |
I think the primary benefit of the os-specific version methods is what @eerhardt called out:
Rewatching that review meeting, I noticed @GrabYourPitchforks mentioned that for |
So far, we have been living with a single RntimeInformation.IsOSPlatform() method for platform detection. It wouldn't have been a surprise if OperatingSystem class also get one If the potential benefit of this work is linker-friendliness, then it is bit of an overkill to add so many last-minute public |
Thanks for the suggestion. We've considered it some more but decided to stick with the OS-specific version methods. The OS-specific version methods allow us to understand versioning of each OS canonically and guide folks more easily into the right version checks. As a bonus, the XML doc comments for the methods can reference how the version is detected on each platform, as is seen in this comment: /// <summary>
/// Check for the tvOS version (returned by 'libobjc.get_operatingSystemVersion') with a >= version comparison. Used to guard APIs that were added in the given tvOS release.
/// </summary>
public static bool IsTvOSVersionAtLeast(int major, int minor = 0, int build = 0)
=> IsTvOS() && IsOSVersionAtLeast(major, minor, build, 0); |
In .NET Core 1.x and 2.x days, we added very limited ability for customers to check which OS they were on. We originally didn't even expose
Environment.OSVersion
and we built a new APIRuntimeInformation.IsOSPlatform()
. The assumption was that doing OS checks is exclusively done for P/invoke or interoperating with native code, which is whyRuntimeInformation
was put inSystem.Runtime.InteropServices
.However, guarding calls to OS bindings or avoiding unsupported APIs in Blazor isn't really a P/invoke scenario. It's seems counterproductive to force these customers to import the
System.Runtime.InteropServices
namespace.API Proposal
Since
RuntimeInformation
is a very small type today, the proposal is: don't add the API for version checks onRuntimeInformation
but instead to add them toEnvironment
.Environment
was the canonical place where people have performed OS checks in the past and are most likely to look for them moving forward.RuntimeInformation
is a fairly recent type. And compared to Environment.OSVersion the RuntimeInformation type hasn't gained much adoption yet.For the attributes, we'd like to put the core ones people are likely to apply to their own code in the same namespace as the guards, i.e. in
System
. We'll leave the base type and theTargetPlatformAttribute
inSystem.Runtime.Versioning
.This would remove the following APIs:
/cc @jeffhandley @eerhardt @adamsitnik @buyaa-n @mhutch
The text was updated successfully, but these errors were encountered: