diff --git a/src/libraries/Common/src/Interop/OSX/Interop.Libraries.cs b/src/libraries/Common/src/Interop/OSX/Interop.Libraries.cs index 84bf6d6398da8..6c2a0ab78c8d7 100644 --- a/src/libraries/Common/src/Interop/OSX/Interop.Libraries.cs +++ b/src/libraries/Common/src/Interop/OSX/Interop.Libraries.cs @@ -9,13 +9,13 @@ internal static partial class Libraries internal const string CoreFoundationLibrary = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation"; internal const string CoreServicesLibrary = "/System/Library/Frameworks/CoreServices.framework/CoreServices"; internal const string CFNetworkLibrary = "/System/Library/Frameworks/CFNetwork.framework/CFNetwork"; - internal const string libproc = "libproc"; + internal const string libobjc = "/usr/lib/libobjc.dylib"; + internal const string libproc = "/usr/lib/libproc.dylib"; internal const string LibSystemCommonCrypto = "/usr/lib/system/libcommonCrypto"; internal const string LibSystemKernel = "/usr/lib/system/libsystem_kernel"; internal const string Odbc32 = "libodbc.2.dylib"; internal const string SystemConfigurationLibrary = "/System/Library/Frameworks/SystemConfiguration.framework/SystemConfiguration"; internal const string AppleCryptoNative = "System.Security.Cryptography.Native.Apple"; internal const string MsQuic = "msquic"; - } } diff --git a/src/libraries/Common/src/Interop/OSX/Interop.libobjc.cs b/src/libraries/Common/src/Interop/OSX/Interop.libobjc.cs new file mode 100644 index 0000000000000..6658d2b4c5d40 --- /dev/null +++ b/src/libraries/Common/src/Interop/OSX/Interop.libobjc.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using System.Runtime.InteropServices; +using nint = System.IntPtr; + +internal static partial class Interop +{ + internal static partial class libobjc + { +#if TARGET_ARM64 + private const string MessageSendStructReturnEntryPoint = "objc_msgSend"; +#else + private const string MessageSendStructReturnEntryPoint = "objc_msgSend_stret"; +#endif + + [StructLayout(LayoutKind.Sequential)] + private struct NSOperatingSystemVersion + { + public nint majorVersion; + public nint minorVersion; + public nint patchVersion; + } + + [DllImport(Libraries.libobjc)] + private static extern IntPtr objc_getClass(string className); + [DllImport(Libraries.libobjc)] + private static extern IntPtr sel_getUid(string selector); + [DllImport(Libraries.libobjc)] + private static extern IntPtr objc_msgSend(IntPtr basePtr, IntPtr selector); + + internal static Version GetOperatingSystemVersion() + { + int major = 0; + int minor = 0; + int patch = 0; + + IntPtr processInfo = objc_msgSend(objc_getClass("NSProcessInfo"), sel_getUid("processInfo")); + + if (processInfo != IntPtr.Zero) + { + NSOperatingSystemVersion osVersion = get_operatingSystemVersion(processInfo, sel_getUid("operatingSystemVersion")); + + major = osVersion.majorVersion.ToInt32(); + minor = osVersion.minorVersion.ToInt32(); + patch = osVersion.patchVersion.ToInt32(); + } + + return new Version(major, minor, patch); + } + + [DllImport(Libraries.libobjc, EntryPoint = MessageSendStructReturnEntryPoint)] + private static extern NSOperatingSystemVersion get_operatingSystemVersion(IntPtr basePtr, IntPtr selector); + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 951552c11ba49..23fd990bfb42c 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -8,6 +8,7 @@ enable + true @@ -1750,14 +1751,16 @@ - + + + - - + + @@ -1773,6 +1776,14 @@ + + + Common\Interop\OSX\Interop.libobjc.cs + + + Common\Interop\OSX\Interop.Libraries.cs + + diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.GetFolderPathCore.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.GetFolderPathCore.Unix.cs similarity index 100% rename from src/libraries/System.Private.CoreLib/src/System/Environment.Unix.GetFolderPathCore.cs rename to src/libraries/System.Private.CoreLib/src/System/Environment.GetFolderPathCore.Unix.cs diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.OSVersion.OSX.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.OSVersion.OSX.cs new file mode 100644 index 0000000000000..0c8771938f041 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.OSVersion.OSX.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System +{ + public static partial class Environment + { + private static OperatingSystem GetOSVersion() + { + Version version = Interop.libobjc.GetOperatingSystemVersion(); + + // For compatibility reasons with Mono, PlatformID.Unix is returned on MacOSX. PlatformID.MacOSX + // is hidden from the editor and shouldn't be used. + return new OperatingSystem(PlatformID.Unix, version); + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.OSVersion.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.OSVersion.Unix.cs new file mode 100644 index 0000000000000..0ccea27611aed --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.OSVersion.Unix.cs @@ -0,0 +1,67 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System +{ + public static partial class Environment + { + private static OperatingSystem GetOSVersion() => GetOperatingSystem(Interop.Sys.GetUnixRelease()); + + // Tests exercise this method for corner cases via private reflection + private static OperatingSystem GetOperatingSystem(string release) + { + int major = 0, minor = 0, build = 0, revision = 0; + + // Parse the uname's utsname.release for the first four numbers found. + // This isn't perfect, but Version already doesn't map exactly to all possible release + // formats, e.g. 2.6.19-1.2895.fc6 + if (release != null) + { + int i = 0; + major = FindAndParseNextNumber(release, ref i); + minor = FindAndParseNextNumber(release, ref i); + build = FindAndParseNextNumber(release, ref i); + revision = FindAndParseNextNumber(release, ref i); + } + + return new OperatingSystem(PlatformID.Unix, new Version(major, minor, build, revision)); + } + + private static int FindAndParseNextNumber(string text, ref int pos) + { + // Move to the beginning of the number + for (; pos < text.Length; pos++) + { + char c = text[pos]; + if ('0' <= c && c <= '9') + { + break; + } + } + + // Parse the number; + int num = 0; + for (; pos < text.Length; pos++) + { + char c = text[pos]; + if ('0' > c || c > '9') + break; + + try + { + num = checked((num * 10) + (c - '0')); + } + // Integer overflow can occur for example with: + // Linux nelknet 4.15.0-24201807041620-generic + // To form a valid Version, num must be positive. + catch (OverflowException) + { + return int.MaxValue; + } + } + + return num; + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs index c29a56c14b53f..81b516dd45f5d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs @@ -63,66 +63,6 @@ public static string MachineName internal const string NewLineConst = "\n"; - private static OperatingSystem GetOSVersion() => GetOperatingSystem(Interop.Sys.GetUnixRelease()); - - // Tests exercise this method for corner cases via private reflection - private static OperatingSystem GetOperatingSystem(string release) - { - int major = 0, minor = 0, build = 0, revision = 0; - - // Parse the uname's utsname.release for the first four numbers found. - // This isn't perfect, but Version already doesn't map exactly to all possible release - // formats, e.g. 2.6.19-1.2895.fc6 - if (release != null) - { - int i = 0; - major = FindAndParseNextNumber(release, ref i); - minor = FindAndParseNextNumber(release, ref i); - build = FindAndParseNextNumber(release, ref i); - revision = FindAndParseNextNumber(release, ref i); - } - - // For compatibility reasons with Mono, PlatformID.Unix is returned on MacOSX. PlatformID.MacOSX - // is hidden from the editor and shouldn't be used. - return new OperatingSystem(PlatformID.Unix, new Version(major, minor, build, revision)); - } - - private static int FindAndParseNextNumber(string text, ref int pos) - { - // Move to the beginning of the number - for (; pos < text.Length; pos++) - { - char c = text[pos]; - if ('0' <= c && c <= '9') - { - break; - } - } - - // Parse the number; - int num = 0; - for (; pos < text.Length; pos++) - { - char c = text[pos]; - if ('0' > c || c > '9') - break; - - try - { - num = checked((num * 10) + (c - '0')); - } - // Integer overflow can occur for example with: - // Linux nelknet 4.15.0-24201807041620-generic - // To form a valid Version, num must be positive. - catch (OverflowException) - { - return int.MaxValue; - } - } - - return num; - } - public static string SystemDirectory => GetFolderPathCore(SpecialFolder.System, SpecialFolderOption.None); public static int SystemPageSize => CheckedSysConf(Interop.Sys.SysConfName._SC_PAGESIZE); diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.OSX.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.Lock.OSX.cs similarity index 100% rename from src/libraries/System.Private.CoreLib/src/System/IO/FileStream.OSX.cs rename to src/libraries/System.Private.CoreLib/src/System/IO/FileStream.Lock.OSX.cs diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.Linux.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.Lock.Unix.cs similarity index 100% rename from src/libraries/System.Private.CoreLib/src/System/IO/FileStream.Linux.cs rename to src/libraries/System.Private.CoreLib/src/System/IO/FileStream.Lock.Unix.cs diff --git a/src/libraries/System.Runtime.Extensions/tests/System/EnvironmentTests.cs b/src/libraries/System.Runtime.Extensions/tests/System/EnvironmentTests.cs index 3ea51bf15b4cf..5129caf6dc8e8 100644 --- a/src/libraries/System.Runtime.Extensions/tests/System/EnvironmentTests.cs +++ b/src/libraries/System.Runtime.Extensions/tests/System/EnvironmentTests.cs @@ -132,9 +132,9 @@ public void OSVersion_ValidVersion() Assert.Contains(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "Windows" : "Unix", versionString); } - // On Unix, we must parse the version from uname -r + // On non-OSX Unix, we must parse the version from uname -r [Theory] - [PlatformSpecific(TestPlatforms.AnyUnix)] + [PlatformSpecific(TestPlatforms.AnyUnix & ~TestPlatforms.OSX)] [InlineData("2.6.19-1.2895.fc6", 2, 6, 19, 1)] [InlineData("xxx1yyy2zzz3aaa4bbb", 1, 2, 3, 4)] [InlineData("2147483647.2147483647.2147483647.2147483647", int.MaxValue, int.MaxValue, int.MaxValue, int.MaxValue)] @@ -154,6 +154,19 @@ public void OSVersion_ParseVersion(string input, int major, int minor, int build Assert.Equal(expected, actual); } + [Fact] + [PlatformSpecific(TestPlatforms.OSX)] + public void OSVersion_ValidVersion_OSX() + { + Version version = Environment.OSVersion.Version; + + // verify that the Environment.OSVersion.Version matches the current RID + Assert.Contains(version.ToString(2), RuntimeInformation.RuntimeIdentifier); + + Assert.True(version.Build >= 0, "OSVersion Build should be non-negative"); + Assert.Equal(-1, version.Revision); // Revision is never set on OSX + } + [Fact] public void SystemPageSize_Valid() {