diff --git a/src/app/SharpRaven/Data/Context/Runtime.cs b/src/app/SharpRaven/Data/Context/Runtime.cs index 0136b12b..f34ef68b 100644 --- a/src/app/SharpRaven/Data/Context/Runtime.cs +++ b/src/app/SharpRaven/Data/Context/Runtime.cs @@ -62,6 +62,12 @@ public class Runtime /// [JsonProperty(PropertyName = "raw_description", NullValueHandling = NullValueHandling.Ignore)] public string RawDescription { get; set; } + /// + /// An optional build number + /// + /// + [JsonProperty(PropertyName = "build", NullValueHandling = NullValueHandling.Ignore)] + public string Build { get; set; } /// /// Clones this instance @@ -73,6 +79,7 @@ internal Runtime Clone() { Name = this.Name, Version = this.Version, + Build = this.Build, RawDescription = this.RawDescription }; } @@ -85,12 +92,7 @@ public static Runtime Create() { try { - var runtime = new Runtime - { - RawDescription = RuntimeInfoHelper.GetRuntimeVersion() - }; - - return runtime; + return RuntimeInfoHelper.GetRuntime(); } catch (Exception e) { diff --git a/src/app/SharpRaven/Data/Context/RuntimeExtensions.cs b/src/app/SharpRaven/Data/Context/RuntimeExtensions.cs new file mode 100644 index 00000000..598b8561 --- /dev/null +++ b/src/app/SharpRaven/Data/Context/RuntimeExtensions.cs @@ -0,0 +1,14 @@ +namespace SharpRaven.Data.Context +{ + internal static class RuntimeExtensions + { + public static bool IsNetFx(this Runtime runtime) => runtime.IsRuntime(".NET Framework"); + public static bool IsNetCore(this Runtime runtime) => runtime.IsRuntime(".NET Core"); + + private static bool IsRuntime(this Runtime runtime, string runtimeName) + { + return runtime?.Name?.StartsWith(runtimeName) == true + || runtime?.RawDescription?.StartsWith(runtimeName) == true; + } + } +} diff --git a/src/app/SharpRaven/SharpRaven.csproj b/src/app/SharpRaven/SharpRaven.csproj index cb276b7c..e988e48a 100644 --- a/src/app/SharpRaven/SharpRaven.csproj +++ b/src/app/SharpRaven/SharpRaven.csproj @@ -21,26 +21,37 @@ - + + - - - - - - + + NETFX;$(AdditionalConstants) + + + + + + - + + NETFX;$(AdditionalConstants) + + + + + + + - HAS_RUNTIME_INFORMATION;$(AdditionalConstants) + NETFX;HAS_RUNTIME_INFORMATION;NET45PLUS_REGISTRY_VERSION;$(AdditionalConstants) @@ -51,7 +62,7 @@ - HAS_RUNTIME_INFORMATION;$(AdditionalConstants) + NETFX;HAS_RUNTIME_INFORMATION;NET45PLUS_REGISTRY_VERSION;$(AdditionalConstants) diff --git a/src/app/SharpRaven/Utilities/RuntimeInfoHelper.cs b/src/app/SharpRaven/Utilities/RuntimeInfoHelper.cs index 468f7887..eb4ddeaf 100644 --- a/src/app/SharpRaven/Utilities/RuntimeInfoHelper.cs +++ b/src/app/SharpRaven/Utilities/RuntimeInfoHelper.cs @@ -1,46 +1,98 @@ using System; using System.Reflection; + +using SharpRaven.Data.Context; #if HAS_RUNTIME_INFORMATION using System.Runtime.InteropServices; #endif +#if NET45PLUS_REGISTRY_VERSION +using Microsoft.Win32; +#endif namespace SharpRaven.Utilities { internal static class RuntimeInfoHelper { - public static string GetRuntimeVersion() + public static Runtime GetRuntime() { -#if HAS_RUNTIME_INFORMATION +#if HAS_RUNTIME_INFORMATION // .NET Core 2+, .NET Framework 4.5+ // Prefered API: netstandard2.0 and vNext // https://github.com/dotnet/corefx/blob/master/src/System.Runtime.InteropServices.RuntimeInformation/src/System/Runtime/InteropServices/RuntimeInformation/RuntimeInformation.cs // e.g: .NET Framework 4.7.2633.0, .NET Native, WebAssembly - var version = RuntimeInformation.FrameworkDescription; + var runtime = new Runtime + { + RawDescription = RuntimeInformation.FrameworkDescription + }; #else - var mono = GetFromMono(); - var version = mono + var runtime = GetFromMono(); + runtime = runtime // Environment.Version: NET Framework 4, 4.5, 4.5.1, 4.5.2 = 4.0.30319.xxxxx // .NET Framework 4.6, 4.6.1, 4.6.2, 4.7, 4.7.1 = 4.0.30319.42000 // Not recommended on NET45+ - ?? $".NET Framework {Environment.Version}"; + ?? new Runtime + { + Name = ".NET Framework", + Version = Environment.Version.ToString() + }; +#endif + +#if NET45PLUS_REGISTRY_VERSION // .NET Framework 4.5 and later + if (runtime.IsNetFx()) + { + runtime.Build = Get45PlusLatestInstallationFromRegistry()?.ToString(); + } +#endif + +#if !NETFX // Non .NET Framework (i.e: netstandard, netcoreapp) + if (runtime.IsNetCore()) + { + // RuntimeInformation.FrameworkDescription returns 4.6 on .NET Core 2.0 which is counter intuitive + var assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly; + var assemblyPath = assembly.CodeBase.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries); + var netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App"); + if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2) + { + runtime.Name = ".NET Core"; + runtime.Version = assemblyPath[netCoreAppIndex + 1]; + } + } #endif - return version; + return runtime; } - private static string GetFromMono() + private static Runtime GetFromMono() { // The implementation of Mono to RuntimeInformation: // https://github.com/mono/mono/blob/90b49aa3aebb594e0409341f9dca63b74f9df52e/mcs/class/corlib/System.Runtime.InteropServices.RuntimeInformation/RuntimeInformation.cs - // e.g; Mono 5.10.0 (Visual Studio built mono) - var monoVersion = Type.GetType("Mono.Runtime", false) - ?.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static) - ?.Invoke(null, null) as string; - - if (monoVersion != null) + if (Type.GetType("Mono.Runtime", false) + ?.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static) + ?.Invoke(null, null) is string monoVersion) { - monoVersion = "Mono " + monoVersion; + return new Runtime + { + // Send complete (raw) and let Sentry parse it. UI can display short version but details are not lost + // e.g; Mono 5.10.0 (Visual Studio built mono) + // e.g: Mono 5.10.1.47 (tarball Tue Apr 17 09:23:16 UTC 2018) + RawDescription = "Mono " + monoVersion + }; } - return monoVersion; + return null; + } + +#if NET45PLUS_REGISTRY_VERSION + // https://docs.microsoft.com/en-us/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed#to-find-net-framework-versions-by-querying-the-registry-in-code-net-framework-45-and-later + private static int? Get45PlusLatestInstallationFromRegistry() + { + const string subkey = @"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\"; + + using (var ndpKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey(subkey)) + { + return int.TryParse(ndpKey?.GetValue("Release")?.ToString(), out var releaseId) + ? releaseId + : (int?)null; + } } +#endif } } diff --git a/src/tests/SharpRaven.UnitTests/Data/Context/RuntimeTests.cs b/src/tests/SharpRaven.UnitTests/Data/Context/RuntimeTests.cs index 3ee2c76a..ebc6a9bb 100644 --- a/src/tests/SharpRaven.UnitTests/Data/Context/RuntimeTests.cs +++ b/src/tests/SharpRaven.UnitTests/Data/Context/RuntimeTests.cs @@ -10,13 +10,16 @@ namespace SharpRaven.UnitTests.Data.Context public class RuntimeTests { [Test] - public void Create_RawDescription_NotNullAndAsHelper() + public void Create_Runtime_NotNullAndAsHelper() { - var runtime = Runtime.Create(); + var actual = Runtime.Create(); - var expected = RuntimeInfoHelper.GetRuntimeVersion(); - Assert.NotNull(runtime.RawDescription); - Assert.AreEqual(expected, runtime.RawDescription); + var expected = RuntimeInfoHelper.GetRuntime(); + Assert.NotNull(actual); + Assert.AreEqual(expected.Build, actual.Build); + Assert.AreEqual(expected.Version, actual.Version); + Assert.AreEqual(expected.Name, actual.Name); + Assert.AreEqual(expected.RawDescription, actual.RawDescription); } [Test] diff --git a/src/tests/SharpRaven.UnitTests/SharpRaven.UnitTests.csproj b/src/tests/SharpRaven.UnitTests/SharpRaven.UnitTests.csproj index bc7dcadd..8d933f9a 100644 --- a/src/tests/SharpRaven.UnitTests/SharpRaven.UnitTests.csproj +++ b/src/tests/SharpRaven.UnitTests/SharpRaven.UnitTests.csproj @@ -12,35 +12,44 @@ + + + + + + + + HAS_RUNTIME_INFORMATION;$(AdditionalConstants) + - + - - - - - - - - - - - - - + HAS_RUNTIME_INFORMATION;$(AdditionalConstants) + + + + + + + + + + + + diff --git a/src/tests/SharpRaven.UnitTests/Utilities/RuntimeInfoHelperTests.cs b/src/tests/SharpRaven.UnitTests/Utilities/RuntimeInfoHelperTests.cs new file mode 100644 index 00000000..12d08c96 --- /dev/null +++ b/src/tests/SharpRaven.UnitTests/Utilities/RuntimeInfoHelperTests.cs @@ -0,0 +1,17 @@ +using System; + +using NUnit.Framework; +using SharpRaven.Utilities; + +namespace SharpRaven.UnitTests.Utilities +{ + public class RuntimeInfoHelperTests + { + [Test] + public void GetRuntime_NotNull() + { + var runtime = RuntimeInfoHelper.GetRuntime(); + Assert.That(runtime, Is.Not.Null); + } + } +}