Skip to content

Commit

Permalink
Fix platform detection to correctly identify .NET Mono
Browse files Browse the repository at this point in the history
  • Loading branch information
nike4613 committed Jun 19, 2024
1 parent 05f1d53 commit d096d62
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 4 deletions.
29 changes: 29 additions & 0 deletions src/MonoMod.Utils/ArchitectureKind.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,44 @@

namespace MonoMod.Utils
{
/// <summary>
/// A CPU architecture.
/// </summary>
[SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores",
Justification = "x86_64 is the name of the architecture, at least for Intel. AMD64 is another reasonable name.")]
[SuppressMessage("Design", "CA1027:Mark enums with FlagsAttribute",
Justification = "This isn't a set of flags. Some bit values are named to ")]
public enum ArchitectureKind
{
/// <summary>
/// An unknown architecture.
/// </summary>
Unknown,
/// <summary>
/// A flag which is set in architectures which are 64-bit.
/// </summary>
Bits64 = 1,
/// <summary>
/// The Intel x86 CPU architecture.
/// </summary>
x86 = 0x01 << 1,
/// <summary>
/// The <c>x86_64</c> 64-bit extensions to <see cref="x86"/>. Also known as <see cref="AMD64"/>.
/// </summary>
/// <seealso cref="AMD64"/>
x86_64 = x86 | Bits64,
/// <summary>
/// The AMD 64-bit extension to <see cref="x86"/>. Also known as <see cref="x86_64"/>.
/// </summary>
/// <seealso cref="x86_64"/>
AMD64 = x86_64,
/// <summary>
/// The ARM instruction set.
/// </summary>
Arm = 0x02 << 1,
/// <summary>
/// The 64-bit ARM instruction set.
/// </summary>
Arm64 = Arm | Bits64,
}
}
18 changes: 18 additions & 0 deletions src/MonoMod.Utils/CorelibKind.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace MonoMod.Utils
{
/// <summary>
/// The kind of corelib loaded by the current runtime.
/// </summary>
public enum CorelibKind
{
/// <summary>
/// The .NET Framework corelib. The corelib's name is <c>mscorlib</c>, and it is used on standard Mono and .NET Framework.
/// </summary>
Framework,
/// <summary>
/// The .NET Core corelib. The corelib's name is <c>System.Private.CoreLib</c>, and it is used on .NET Mono (from
/// <see href="https://github.com/dotnet/runtime" />) and CoreCLR (.NET Core and .NET 5+) runtimes.
/// </summary>
Core,
}
}
76 changes: 76 additions & 0 deletions src/MonoMod.Utils/OSKind.cs
Original file line number Diff line number Diff line change
@@ -1,29 +1,105 @@
namespace MonoMod.Utils
{
/// <summary>
/// An operating system kind.
/// </summary>
public enum OSKind
{
/// <summary>
/// An unknown operating system.
/// </summary>
Unknown = 0,

// low 5 bits are flags for the base OS
// bit 0 is Posix, 1 is Windows, 2 is OSX, 3 is Linux, 4 is BSD
// remaining bits are a subtype

/// <summary>
/// A POSIX-compatible operating system.
/// </summary>
Posix = 1 << 0,

/// <summary>
/// A Linux kernel.
/// </summary>
Linux = 1 << 3 | Posix,
/// <summary>
/// An Android operating system.
/// </summary>
/// <remarks>
/// <see cref="OSKindExtensions.GetKernel(OSKind)"/> will return <see cref="Linux"/>, and
/// <see cref="OSKindExtensions.GetSubtypeId(OSKind)"/> will return <c>1</c>.
/// </remarks>
Android = 0x01 << 5 | Linux, // Android is a subset of Linux
/// <summary>
/// A MacOSX kernel.
/// </summary>
OSX = 1 << 2 | Posix,
/// <summary>
/// An iOS operating system.
/// </summary>
/// <remarks>
/// <see cref="OSKindExtensions.GetKernel(OSKind)"/> will return <see cref="IOS"/>, and
/// <see cref="OSKindExtensions.GetSubtypeId(OSKind)"/> will return <c>1</c>.
/// </remarks>
IOS = 0x01 << 5 | OSX, // iOS is a subset of OSX
/// <summary>
/// A BSD kernel.
/// </summary>
BSD = 1 << 4 | Posix,

/// <summary>
/// A Windows kernel.
/// </summary>
Windows = 1 << 1,
/// <summary>
/// A Windows operating system, running on the Wine emulation layer.
/// </summary>
/// <remarks>
/// <see cref="OSKindExtensions.GetKernel(OSKind)"/> will return <see cref="Windows"/>, and
/// <see cref="OSKindExtensions.GetSubtypeId(OSKind)"/> will return <c>1</c>.
/// </remarks>
Wine = 0x01 << 5 | Windows,
}

/// <summary>
/// A collection of extensions for the <see cref="OSKind"/> enum.
/// </summary>
public static class OSKindExtensions
{
/// <summary>
/// Tests whether <paramref name="operatingSystem"/> is an operating system with the specified <paramref name="test"/> flag.
/// </summary>
/// <example>
/// <list type="bullet">
/// <item><see cref="OSKind.Wine"/> is <see cref="OSKind.Windows"/>.</item>
/// <item><see cref="OSKind.Linux"/> is <see cref="OSKind.Posix"/>.</item>
/// <item><see cref="OSKind.IOS"/> is <see cref="OSKind.OSX"/> is <see cref="OSKind.Posix"/>.</item>
/// </list>
/// </example>
/// <param name="operatingSystem">The <see cref="OSKind"/> to test.</param>
/// <param name="test">The test value.</param>
/// <returns><see langword="true"/> if <paramref name="operatingSystem"/> matches <paramref name="test"/>; <see langword="false"/> otherwise.</returns>
public static bool Is(this OSKind operatingSystem, OSKind test) => operatingSystem.Has(test);
/// <summary>
/// Gets the kernel <see cref="OSKind"/> for <paramref name="operatingSystem"/>.
/// </summary>
/// <remarks>
/// <para>This method returns <paramref name="operatingSystem"/> except when noted in the remarks of the <see cref="OSKind"/> members.</para>
/// <para>This is usually what you want to use, if you don't care about the differences between the more precise subtypes.</para>
/// </remarks>
/// <param name="operatingSystem">The <see cref="OSKind"/> to get the kernel of.</param>
/// <returns>The <see cref="OSKind"/> representing <paramref name="operatingSystem"/>'s kernel.</returns>
public static OSKind GetKernel(this OSKind operatingSystem) => (OSKind)((int)operatingSystem & 0b11111);
/// <summary>
/// Gets the subtype ID for <paramref name="operatingSystem"/>.
/// </summary>
/// <remarks>
/// The subtype ID means basically nothing on its own. Its value is <c>0</c> unless otherwise noted in the remarks of
/// <see cref="OSKind"/>'s members.
/// </remarks>
/// <param name="operatingSystem">The <see cref="OSKind"/> to get the subtype ID of.</param>
/// <returns>The subtype ID of <paramref name="operatingSystem"/>.</returns>
public static int GetSubtypeId(this OSKind operatingSystem) => (int)operatingSystem >> 5;
}
}
67 changes: 63 additions & 4 deletions src/MonoMod.Utils/PlatformDetection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,20 @@

namespace MonoMod.Utils
{
/// <summary>
/// A utility type which provides information about the executing runtime, including:
/// <list type="bullet">
/// <item>Operating system</item>
/// <item>Architecture</item>
/// <item>Runtime
/// <list type="bullet">
/// <item>Kind (Framework, CoreCLR, Mono)</item>
/// <item>Version</item>
/// </list>
/// </item>
/// <item>Corelib</item>
/// </list>
/// </summary>
public static class PlatformDetection
{
#region OS/Arch
Expand All @@ -31,6 +45,9 @@ private static void EnsurePlatformInfoInitialized()
_ = Interlocked.Exchange(ref platInitState, 1);
}

/// <summary>
/// Gets the <see cref="OSKind"/> identifying the operating system that is running this application.
/// </summary>
public static OSKind OS
{
get
Expand All @@ -40,6 +57,22 @@ public static OSKind OS
}
}

/// <summary>
/// Gets the <see cref="ArchitectureKind"/> identifying the architecture of the current process.
/// </summary>
/// <remarks>
/// Note that the architecture running here is not necessarily the architecture of the operating system.
/// In many cases, an OS can run code that is not its own native architecture, for instance:
/// <list type="bullet">
/// <item>Windows running on <see cref="ArchitectureKind.x86_64"/> can natively run
/// <see cref="ArchitectureKind.x86"/> code via its WOW64 subsystem.</item>
/// <item>Linux running on <see cref="ArchitectureKind.x86_64"/> can natively run <see cref="ArchitectureKind.x86"/>
/// code in a similar manner to Windows.</item>
/// <item>MacOS running on <see cref="ArchitectureKind.Arm64"/> can run <see cref="ArchitectureKind.x86_64"/> code
/// via its Rosetta emulation layer.</item>
/// </list>
/// Generally, the most useful value is the process's architecture, so that's what this returns.
/// </remarks>
public static ArchitectureKind Architecture
{
get
Expand Down Expand Up @@ -420,6 +453,7 @@ private static unsafe bool CheckWine()
#region Runtime
private static int runtimeInitState;
private static RuntimeKind runtime;
private static CorelibKind corelib;
private static Version? runtimeVersion;

[MemberNotNull(nameof(runtimeVersion))]
Expand All @@ -436,12 +470,16 @@ private static void EnsureRuntimeInitialized()

var runtimeInfo = DetermineRuntimeInfo();
runtime = runtimeInfo.Rt;
corelib = runtimeInfo.Cor;
runtimeVersion = runtimeInfo.Ver;

Thread.MemoryBarrier();
_ = Interlocked.Exchange(ref runtimeInitState, 1);
}

/// <summary>
/// Gets the current runtime's implementation.
/// </summary>
public static RuntimeKind Runtime
{
get
Expand All @@ -451,6 +489,24 @@ public static RuntimeKind Runtime
}
}

/// <summary>
/// Gets the current runtime's corelib.
/// </summary>
public static CorelibKind Corelib
{
get
{
EnsureRuntimeInitialized();
return corelib;
}
}

/// <summary>
/// Gets the current runtime's version.
/// </summary>
/// <remarks>
/// On .NET Framework, this version will semi-frequently be nonsense, and the same is true on .NET Core 2.1 and earlier.
/// </remarks>
public static Version RuntimeVersion
{
get
Expand All @@ -463,7 +519,7 @@ public static Version RuntimeVersion
[SuppressMessage("Design", "CA1031:Do not catch general exception types",
Justification = "In old versions of Framework, there is no Version.TryParse, and so we must call the constructor " +
"and catch any exception that may ocurr.")]
private static (RuntimeKind Rt, Version Ver) DetermineRuntimeInfo()
private static (RuntimeKind Rt, CorelibKind Cor, Version Ver) DetermineRuntimeInfo()
{
RuntimeKind runtime;
Version? version = null; // an unknown version
Expand All @@ -476,6 +532,8 @@ private static (RuntimeKind Rt, Version Ver) DetermineRuntimeInfo()

var isCoreBcl = typeof(object).Assembly.GetName().Name == "System.Private.CoreLib";

var corelib = isCoreBcl ? CorelibKind.Core : CorelibKind.Framework;

if (isMono)
{
runtime = RuntimeKind.Mono;
Expand Down Expand Up @@ -535,7 +593,8 @@ private static (RuntimeKind Rt, Version Ver) DetermineRuntimeInfo()
}
else if (fxDesc.StartsWith(Net5Plus, StringComparison.Ordinal))
{
runtime = RuntimeKind.CoreCLR;
// There is such a thing as .NET Mono in dotnet/runtime, which reports identically to CoreCLR. It also uses the same BCL.
runtime = isMono ? RuntimeKind.Mono : RuntimeKind.CoreCLR;
prefixLength = Net5Plus.Length;
}
else
Expand Down Expand Up @@ -570,9 +629,9 @@ private static (RuntimeKind Rt, Version Ver) DetermineRuntimeInfo()

// TODO: map strange (read: Framework) versions correctly

MMDbgLog.Info($"Detected runtime: {runtime} {version?.ToString() ?? "(null)"}");
MMDbgLog.Info($"Detected runtime: {runtime} {version?.ToString() ?? "(null)"} using {corelib} corelib");

return (runtime, version ?? new Version(0, 0));
return (runtime, corelib, version ?? new Version(0, 0));
}

#endregion
Expand Down
15 changes: 15 additions & 0 deletions src/MonoMod.Utils/RuntimeKind.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
namespace MonoMod.Utils
{
/// <summary>
/// A runtime implementation kind.
/// </summary>
public enum RuntimeKind
{
/// <summary>
/// Some unknown runtime implementation.
/// </summary>
Unknown = 0,
/// <summary>
/// The Windows .NET Framework CLR implementation.
/// </summary>
Framework,
/// <summary>
/// The CoreCLR implementation, used by .NET Core and .NET 5+. derived from the Silverlight runtime.
/// </summary>
CoreCLR,
/// <summary>
/// The Mono CLR implementation.
/// </summary>
Mono,
}
}

0 comments on commit d096d62

Please sign in to comment.