diff --git a/src/libraries/System.Runtime.InteropServices.RuntimeInformation/ref/System.Runtime.InteropServices.RuntimeInformation.cs b/src/libraries/System.Runtime.InteropServices.RuntimeInformation/ref/System.Runtime.InteropServices.RuntimeInformation.cs index aae34f0b6a70e4..5bba24994424d7 100644 --- a/src/libraries/System.Runtime.InteropServices.RuntimeInformation/ref/System.Runtime.InteropServices.RuntimeInformation.cs +++ b/src/libraries/System.Runtime.InteropServices.RuntimeInformation/ref/System.Runtime.InteropServices.RuntimeInformation.cs @@ -39,5 +39,15 @@ public static partial class RuntimeInformation public static string OSDescription { get { throw null; } } public static System.Runtime.InteropServices.Architecture ProcessArchitecture { get { throw null; } } public static bool IsOSPlatform(System.Runtime.InteropServices.OSPlatform osPlatform) { throw null; } + public static bool IsOSPlatformOrLater(string platformName) { throw null; } + public static bool IsOSPlatformOrLater(System.Runtime.InteropServices.OSPlatform osPlatform, int major) { throw null; } + public static bool IsOSPlatformOrLater(System.Runtime.InteropServices.OSPlatform osPlatform, int major, int minor) { throw null; } + public static bool IsOSPlatformOrLater(System.Runtime.InteropServices.OSPlatform osPlatform, int major, int minor, int build) { throw null; } + public static bool IsOSPlatformOrLater(System.Runtime.InteropServices.OSPlatform osPlatform, int major, int minor, int build, int revision) { throw null; } + public static bool IsOSPlatformEarlierThan(string platformName) { throw null; } + public static bool IsOSPlatformEarlierThan(System.Runtime.InteropServices.OSPlatform osPlatform, int major) { throw null; } + public static bool IsOSPlatformEarlierThan(System.Runtime.InteropServices.OSPlatform osPlatform, int major, int minor) { throw null; } + public static bool IsOSPlatformEarlierThan(System.Runtime.InteropServices.OSPlatform osPlatform, int major, int minor, int build) { throw null; } + public static bool IsOSPlatformEarlierThan(System.Runtime.InteropServices.OSPlatform osPlatform, int major, int minor, int build, int revision) { throw null; } } } diff --git a/src/libraries/System.Runtime.InteropServices.RuntimeInformation/src/Resources/Strings.resx b/src/libraries/System.Runtime.InteropServices.RuntimeInformation/src/Resources/Strings.resx index e554cfe1fe2eb1..c7f9079719cce4 100644 --- a/src/libraries/System.Runtime.InteropServices.RuntimeInformation/src/Resources/Strings.resx +++ b/src/libraries/System.Runtime.InteropServices.RuntimeInformation/src/Resources/Strings.resx @@ -60,6 +60,9 @@ Value cannot be empty. + + "{0}" is not a valid platform name. It must contain both name and version (with at least major and minor number specified). Example: "ios14.0". + RuntimeInformation is not supported for Portable Class Libraries. diff --git a/src/libraries/System.Runtime.InteropServices.RuntimeInformation/src/System.Runtime.InteropServices.RuntimeInformation.csproj b/src/libraries/System.Runtime.InteropServices.RuntimeInformation/src/System.Runtime.InteropServices.RuntimeInformation.csproj index 7b46386905a072..402c6678843b96 100644 --- a/src/libraries/System.Runtime.InteropServices.RuntimeInformation/src/System.Runtime.InteropServices.RuntimeInformation.csproj +++ b/src/libraries/System.Runtime.InteropServices.RuntimeInformation/src/System.Runtime.InteropServices.RuntimeInformation.csproj @@ -7,6 +7,7 @@ + @@ -45,5 +46,6 @@ + diff --git a/src/libraries/System.Runtime.InteropServices.RuntimeInformation/src/System/Runtime/InteropServices/RuntimeInformation/RuntimeInformation.PlatformVersion.cs b/src/libraries/System.Runtime.InteropServices.RuntimeInformation/src/System/Runtime/InteropServices/RuntimeInformation/RuntimeInformation.PlatformVersion.cs new file mode 100644 index 00000000000000..0eec21939bfa82 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.RuntimeInformation/src/System/Runtime/InteropServices/RuntimeInformation/RuntimeInformation.PlatformVersion.cs @@ -0,0 +1,131 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Runtime.InteropServices +{ + public static partial class RuntimeInformation + { + /// + /// Check for the OS with a >= version comparison. Used to guard APIs that were added in the given OS release. + /// + /// OS name concatenated with a version number. + /// The version number must contain at least major and minor numbers separated with a dot. + /// Example: "ios14.0" is OK, "ios14" is NOT OK. + public static bool IsOSPlatformOrLater(string platformName) + { + (OSPlatform platform, Version version) = Parse(platformName); + + return IsOSPlatformOrLater(platform, version.Major, version.Minor, version.Build, version.Revision); + } + + /// + /// Check for the OS with a >= version comparison. Used to guard APIs that were added in the given OS release. + /// + public static bool IsOSPlatformOrLater(OSPlatform osPlatform, int major) + => IsOSPlatform(osPlatform) && Environment.OSVersion.Version.Major >= major; + + /// + /// Check for the OS with a >= version comparison. Used to guard APIs that were added in the given OS release. + /// + public static bool IsOSPlatformOrLater(OSPlatform osPlatform, int major, int minor) + => IsOSPlatform(osPlatform) && IsOSVersionOrLater(major, minor, int.MinValue, int.MinValue); + + /// + /// Check for the OS with a >= version comparison. Used to guard APIs that were added in the given OS release. + /// + public static bool IsOSPlatformOrLater(OSPlatform osPlatform, int major, int minor, int build) + => IsOSPlatform(osPlatform) && IsOSVersionOrLater(major, minor, build, int.MinValue); + + /// + /// Check for the OS with a >= version comparison. Used to guard APIs that were added in the given OS release. + /// + public static bool IsOSPlatformOrLater(OSPlatform osPlatform, int major, int minor, int build, int revision) + => IsOSPlatform(osPlatform) && IsOSVersionOrLater(major, minor, build, revision); + + /// + /// Check for the OS with a < version comparison. Used to guard APIs that were obsoleted or removed in the given OS release. + /// + /// OS name concatenated with a version number. + /// The version number must contain at least major and minor numbers separated with a dot. + /// Example: "ios14.0" is OK, "ios14" is NOT OK. + public static bool IsOSPlatformEarlierThan(string platformName) + { + (OSPlatform platform, Version version) = Parse(platformName); + + return IsOSPlatformEarlierThan(platform, version.Major, version.Minor, version.Build, version.Revision); + } + + /// + /// Check for the OS with a < version comparison. Used to guard APIs that were obsoleted or removed in the given OS release. + /// + public static bool IsOSPlatformEarlierThan(OSPlatform osPlatform, int major) + => IsOSPlatform(osPlatform) && Environment.OSVersion.Version.Major < major; + + /// + /// Check for the OS with a < version comparison. Used to guard APIs that were obsoleted or removed in the given OS release. + /// + public static bool IsOSPlatformEarlierThan(OSPlatform osPlatform, int major, int minor) + => IsOSPlatform(osPlatform) && !IsOSVersionOrLater(major, minor, int.MinValue, int.MinValue); + + /// + /// Check for the OS with a < version comparison. Used to guard APIs that were obsoleted or removed in the given OS release. + /// + public static bool IsOSPlatformEarlierThan(OSPlatform osPlatform, int major, int minor, int build) + => IsOSPlatform(osPlatform) && !IsOSVersionOrLater(major, minor, build, int.MinValue); + + /// + /// Check for the OS with a < version comparison. Used to guard APIs that were obsoleted or removed in the given OS release. + /// + public static bool IsOSPlatformEarlierThan(OSPlatform osPlatform, int major, int minor, int build, int revision) + => IsOSPlatform(osPlatform) && !IsOSVersionOrLater(major, minor, build, revision); + + private static bool IsOSVersionOrLater(int major, int minor, int build, int revision) + { + Version current = Environment.OSVersion.Version; + if (current.Major != major) + { + return current.Major > major; + } + if (current.Minor != minor) + { + return current.Minor > minor; + } + if (current.Build != build) + { + return current.Build > build; + } + + return current.Revision >= revision; + } + + private static (OSPlatform, Version) Parse(string platformName) + { + if (platformName == null) + { + throw new ArgumentNullException(nameof(platformName)); + } + if (platformName.Length == 0) + { + throw new ArgumentException(SR.Argument_EmptyValue, nameof(platformName)); + } + + // iterate from the begining, as digits in the middle of the names are not supported by design + for (int i = 0; i < platformName.Length; i++) + { + if (char.IsDigit(platformName[i])) + { + if (i > 0 && Version.TryParse(platformName.AsSpan(i), out Version? version)) + { + return (OSPlatform.Create(platformName.Substring(0, i)), version); + } + else + { + break; + } + } + } + + throw new ArgumentException(SR.Format(SR.Argument_InvalidPlatfromName, platformName), nameof(platformName)); + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices.RuntimeInformation/tests/PlatformVersionTests.cs b/src/libraries/System.Runtime.InteropServices.RuntimeInformation/tests/PlatformVersionTests.cs new file mode 100644 index 00000000000000..c00ace31490338 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.RuntimeInformation/tests/PlatformVersionTests.cs @@ -0,0 +1,242 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using Xunit; + +namespace System.Runtime.InteropServices.RuntimeInformationTests +{ + public class PlatformVersionTests + { + public static IEnumerable AllKnownOsPlatforms() + { + yield return new object[] { OSPlatform.Windows }; + yield return new object[] { OSPlatform.Linux }; + yield return new object[] { OSPlatform.OSX }; + yield return new object[] { OSPlatform.Browser }; + } + + [Fact] + public void IsOSPlatformOrLater_Null_ThrowsArgumentNullExceptionWithArgumentName() + { + Assert.Throws("platformName", () => RuntimeInformation.IsOSPlatformOrLater(null)); + } + + [Fact] + public void IsOSPlatformOrLater_Empty_ThrowsArgumentNullExceptionWithArgumentName() + { + Assert.Throws("platformName", () => RuntimeInformation.IsOSPlatformOrLater(string.Empty)); + } + + [Theory] + [InlineData("ios")] // missing version number + [InlineData("ios14")] // ios14.0 is fine, ios14 is not: https://github.com/dotnet/runtime/pull/39005#discussion_r452541491 + [InlineData("ios14.0.0.0.0.0")] // too many numbers + [InlineData("ios14.0.")] // version should not end with dot (but OS name could potentially end with dot, imagine "NET.") + [InlineData("numbers1.2inplatformname1.2")] // numbers in platform names are not supported https://github.com/dotnet/runtime/pull/39005#discussion_r452644601 + public void IsOSPlatformOrLater_InvalidVersionNumber_ThrowsArgumentExceptionWithArgumentName(string platformName) + { + Assert.Throws("platformName", () => RuntimeInformation.IsOSPlatformOrLater(platformName)); + } + + [Theory] + [MemberData(nameof(AllKnownOsPlatforms))] + public void IsOSPlatformOrLater_ReturnsTrue_ForCurrentOS(OSPlatform osPlatform) + { + // IsOSPlatformOrLater("xyz1.2.3.4") running as "xyz1.2.3.4" should return true + + bool isCurrentPlatfom = RuntimeInformation.IsOSPlatform(osPlatform); + Version current = Environment.OSVersion.Version; + + Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformOrLater($"{osPlatform}{current}")); + + Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformOrLater(osPlatform, current.Major)); + + if (current.Minor >= 0) + { + Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformOrLater(osPlatform, current.Major, current.Minor)); + + if (current.Build >= 0) + { + Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformOrLater(osPlatform, current.Major, current.Minor, current.Build)); + + if (current.Revision >= 0) + { + Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformOrLater(osPlatform, current.Major, current.Minor, current.Build, current.Revision)); + } + } + } + } + + [Theory] + [MemberData(nameof(AllKnownOsPlatforms))] + public void IsOSPlatformOrLater_ReturnsFalse_ForNewerVersionOfCurrentOS(OSPlatform osPlatform) + { + // IsOSPlatformOrLater("xyz11.0") running as "xyz10.0" should return false + + Version currentVersion = Environment.OSVersion.Version; + + Version newer = new Version(currentVersion.Major + 1, 0); + Assert.False(RuntimeInformation.IsOSPlatformOrLater($"{osPlatform}{newer}")); + Assert.False(RuntimeInformation.IsOSPlatformOrLater(osPlatform, newer.Major)); + + newer = new Version(currentVersion.Major, currentVersion.Minor + 1); + Assert.False(RuntimeInformation.IsOSPlatformOrLater($"{osPlatform}{newer}")); + Assert.False(RuntimeInformation.IsOSPlatformOrLater(osPlatform, newer.Major, newer.Minor)); + + newer = new Version(currentVersion.Major, currentVersion.Minor, currentVersion.Build + 1); + Assert.False(RuntimeInformation.IsOSPlatformOrLater($"{osPlatform}{newer}")); + Assert.False(RuntimeInformation.IsOSPlatformOrLater(osPlatform, newer.Major, newer.Minor, newer.Build)); + + newer = new Version(currentVersion.Major, currentVersion.Minor, currentVersion.Build, currentVersion.Revision + 1); + Assert.False(RuntimeInformation.IsOSPlatformOrLater($"{osPlatform}{newer}")); + Assert.False(RuntimeInformation.IsOSPlatformOrLater(osPlatform, newer.Major, newer.Minor, newer.Build, newer.Revision)); + } + + [Theory] + [MemberData(nameof(AllKnownOsPlatforms))] + public void IsOSPlatformOrLater_ReturnsTrue_ForOlderVersionOfCurrentOS(OSPlatform osPlatform) + { + // IsOSPlatformOrLater("xyz10.0") running as "xyz11.0" should return true + + bool isCurrentPlatfom = RuntimeInformation.IsOSPlatform(osPlatform); + Version current = Environment.OSVersion.Version; + + Version older = new Version(current.Major - 1, 0); + Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformOrLater($"{osPlatform}{older}")); + Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformOrLater(osPlatform, older.Major)); + + if (current.Minor > 0) + { + older = new Version(current.Major, current.Minor - 1); + Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformOrLater($"{osPlatform}{older}")); + Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformOrLater(osPlatform, older.Major, older.Minor)); + } + + if (current.Build > 0) + { + older = new Version(current.Major, current.Minor, current.Build - 1); + Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformOrLater($"{osPlatform}{older}")); + Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformOrLater(osPlatform, older.Major, older.Minor, older.Build)); + } + + if (current.Revision > 0) + { + older = new Version(current.Major, current.Minor, current.Build, current.Revision - 1); + Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformOrLater($"{osPlatform}{older}")); + Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformOrLater(osPlatform, older.Major, older.Minor, older.Build, older.Revision)); + } + } + + [Fact] + public void IsOSPlatformEarlierThan_Null_ThrowsArgumentNullExceptionWithArgumentName() + { + Assert.Throws("platformName", () => RuntimeInformation.IsOSPlatformEarlierThan(null)); + } + + [Fact] + public void IsOSPlatformEarlierThan_Empty_ThrowsArgumentNullExceptionWithArgumentName() + { + Assert.Throws("platformName", () => RuntimeInformation.IsOSPlatformEarlierThan(string.Empty)); + } + + [Theory] + [InlineData("ios")] // missing version number + [InlineData("ios14")] // ios14.0 is fine, ios14 is not: https://github.com/dotnet/runtime/pull/39005#discussion_r452541491 + [InlineData("ios14.0.0.0.0.0")] // too many numbers + [InlineData("ios14.0.")] // version should not end with dot (but OS name could potentially end with dot, imagine "NET.") + [InlineData("numbers1.2inplatformname1.2")] // numbers in platform names are not supported https://github.com/dotnet/runtime/pull/39005#discussion_r452644601 + public void IsOSPlatformEarlierThan_InvalidVersionNumber_ThrowsArgumentExceptionWithArgumentName(string platformName) + { + Assert.Throws("platformName", () => RuntimeInformation.IsOSPlatformEarlierThan(platformName)); + } + + [Theory] + [MemberData(nameof(AllKnownOsPlatforms))] + public void IsOSPlatformEarlierThan_ReturnsFalse_ForCurrentOS(OSPlatform osPlatform) + { + // IsOSPlatformEarlierThan("xyz1.2.3.4") running as "xyz1.2.3.4" should return false + + Version current = Environment.OSVersion.Version; + + Assert.False(RuntimeInformation.IsOSPlatformEarlierThan($"{osPlatform}{current}")); + + Assert.False(RuntimeInformation.IsOSPlatformEarlierThan(osPlatform, current.Major)); + + if (current.Minor >= 0) + { + Assert.False(RuntimeInformation.IsOSPlatformEarlierThan(osPlatform, current.Major, current.Minor)); + + if (current.Build >= 0) + { + Assert.False(RuntimeInformation.IsOSPlatformEarlierThan(osPlatform, current.Major, current.Minor, current.Build)); + + if (current.Revision >= 0) + { + Assert.False(RuntimeInformation.IsOSPlatformEarlierThan(osPlatform, current.Major, current.Minor, current.Build, current.Revision)); + } + } + } + } + + [Theory] + [MemberData(nameof(AllKnownOsPlatforms))] + public void IsOSPlatformEarlierThan_ReturnsTrue_ForNewerVersionOfCurrentOS(OSPlatform osPlatform) + { + // IsOSPlatformEarlierThan("xyz11.0") running as "xyz10.0" should return true + + bool isCurrentPlatfom = RuntimeInformation.IsOSPlatform(osPlatform); + Version current = Environment.OSVersion.Version; + + Version newer = new Version(current.Major + 1, 0); + Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformEarlierThan($"{osPlatform}{newer}")); + Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformEarlierThan(osPlatform, newer.Major)); + + newer = new Version(current.Major, current.Minor + 1); + Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformEarlierThan($"{osPlatform}{newer}")); + Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformEarlierThan(osPlatform, newer.Major, newer.Minor)); + + newer = new Version(current.Major, current.Minor, current.Build + 1); + Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformEarlierThan($"{osPlatform}{newer}")); + Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformEarlierThan(osPlatform, newer.Major, newer.Minor, newer.Build)); + + newer = new Version(current.Major, current.Minor, current.Build, current.Revision + 1); + Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformEarlierThan($"{osPlatform}{newer}")); + Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformEarlierThan(osPlatform, newer.Major, newer.Minor, newer.Build, newer.Revision)); + } + + [Theory] + [MemberData(nameof(AllKnownOsPlatforms))] + public void IsOSPlatformEarlierThan_ReturnsFalse_ForOlderVersionOfCurrentOS(OSPlatform osPlatform) + { + // IsOSPlatformEarlierThan("xyz10.0") running as "xyz11.0" should return false + + Version current = Environment.OSVersion.Version; + + Version older = new Version(current.Major - 1, 0); + Assert.False(RuntimeInformation.IsOSPlatformEarlierThan($"{osPlatform}{older}")); + Assert.False(RuntimeInformation.IsOSPlatformEarlierThan(osPlatform, older.Major)); + + if (current.Minor > 0) + { + older = new Version(current.Major, current.Minor - 1); + Assert.False(RuntimeInformation.IsOSPlatformEarlierThan($"{osPlatform}{older}")); + Assert.False(RuntimeInformation.IsOSPlatformEarlierThan(osPlatform, older.Major, older.Minor)); + } + + if (current.Build > 0) + { + older = new Version(current.Major, current.Minor, current.Build - 1); + Assert.False(RuntimeInformation.IsOSPlatformEarlierThan($"{osPlatform}{older}")); + Assert.False(RuntimeInformation.IsOSPlatformEarlierThan(osPlatform, older.Major, older.Minor, older.Build)); + } + + if (current.Revision > 0) + { + older = new Version(current.Major, current.Minor, current.Build, current.Revision - 1); + Assert.False(RuntimeInformation.IsOSPlatformEarlierThan($"{osPlatform}{older}")); + Assert.False(RuntimeInformation.IsOSPlatformEarlierThan(osPlatform, older.Major, older.Minor, older.Build, older.Revision)); + } + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices.RuntimeInformation/tests/System.Runtime.InteropServices.RuntimeInformation.Tests.csproj b/src/libraries/System.Runtime.InteropServices.RuntimeInformation/tests/System.Runtime.InteropServices.RuntimeInformation.Tests.csproj index ce83c0a5a00384..e3745518d39eff 100644 --- a/src/libraries/System.Runtime.InteropServices.RuntimeInformation/tests/System.Runtime.InteropServices.RuntimeInformation.Tests.csproj +++ b/src/libraries/System.Runtime.InteropServices.RuntimeInformation/tests/System.Runtime.InteropServices.RuntimeInformation.Tests.csproj @@ -1,4 +1,4 @@ - + true $(NetCoreAppCurrent)-Windows_NT;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Browser @@ -8,6 +8,7 @@ +