Skip to content

Commit

Permalink
Get accurate runtime version (#1936)
Browse files Browse the repository at this point in the history
  • Loading branch information
ob-stripe authored Mar 3, 2020
1 parent a6ab693 commit 47acaca
Show file tree
Hide file tree
Showing 3 changed files with 254 additions and 50 deletions.
32 changes: 12 additions & 20 deletions src/Stripe.net/Infrastructure/Public/SystemNetHttpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ namespace Stripe
/// </summary>
public class SystemNetHttpClient : IHttpClient
{
private const string StripeNetTargetFramework =
#if NETSTANDARD2_0
"netstandard2.0"
#elif NET45
"net45"
#else
"unknown"
#endif
;

private static readonly Lazy<System.Net.Http.HttpClient> LazyDefaultHttpClient
= new Lazy<System.Net.Http.HttpClient>(BuildDefaultSystemNetHttpClient);

Expand Down Expand Up @@ -204,29 +214,11 @@ private string BuildStripeClientUserAgentString()
{ "bindings_version", StripeConfiguration.StripeNetVersion },
{ "lang", ".net" },
{ "publisher", "stripe" },
{ "lang_version", RuntimeInformation.GetLanguageVersion() },
{ "lang_version", RuntimeInformation.GetRuntimeVersion() },
{ "os_version", RuntimeInformation.GetOSVersion() },
{ "stripe_net_target_framework", StripeNetTargetFramework },
};

#if NET45
string monoVersion = RuntimeInformation.GetMonoVersion();
if (!string.IsNullOrEmpty(monoVersion))
{
values.Add("mono_version", monoVersion);
}
#endif

var stripeNetTargetFramework =
#if NET45
"net45"
#elif NETSTANDARD2_0
"netstandard2.0"
#else
"unknown"
#endif
;
values.Add("stripe_net_target_framework", stripeNetTargetFramework);

if (this.appInfo != null)
{
values.Add("application", this.appInfo);
Expand Down
269 changes: 240 additions & 29 deletions src/Stripe.net/Infrastructure/RuntimeInformation.cs
Original file line number Diff line number Diff line change
@@ -1,53 +1,264 @@
namespace Stripe.Infrastructure
{
#if NET45
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.Win32;
#endif
using System.Runtime.Versioning;
using static System.Runtime.InteropServices.RuntimeInformation;

/// <summary>
/// This class is used to gather information about the runtime environment. This is actually a
/// non-trivial task. The code below is largely borrowed from the
/// <a href="https://github.com/dotnet/BenchmarkDotNet">BenchmarkDotNet</a> project.
/// </summary>
internal static class RuntimeInformation
{
public static string GetLanguageVersion()
internal const string Unknown = "?";

internal static bool IsMono { get; } = Type.GetType("Mono.Runtime") != null;

internal static bool IsFullFramework => FrameworkDescription.StartsWith(".NET Framework", StringComparison.OrdinalIgnoreCase);

internal static bool IsNetCore => FrameworkDescription.StartsWith(".NET Core", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(typeof(object).Assembly.Location);

/// <summary>
/// "The north star for CoreRT is to be a flavor of .NET Core" -> CoreRT reports .NET Core everywhere.
/// </summary>
internal static bool IsCoreRT
=> FrameworkDescription.StartsWith(".NET Core", StringComparison.OrdinalIgnoreCase)
&& string.IsNullOrEmpty(typeof(object).Assembly.Location); // but it's merged to a single .exe and .Location returns null here ;)

internal static bool IsRunningInContainer => string.Equals(Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_CONTAINER"), "true");

/// <summary>Returns a string that describes the operating system on which the app is running.</summary>
/// <returns>A string that describes the operating system on which the app is running.</returns>
public static string GetOSVersion()
{
#if NET45
return ".NET Framework 4.5+";
#else
return System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription;
#endif
return OSDescription;
}

public static string GetOSVersion()
/// <summary>Returns a string that indicates the name of the .NET installation on which an app is running.</summary>
/// <returns>A string that indicates the name of the .NET installation on which an app is running.</returns>
public static string GetRuntimeVersion()
{
if (IsMono)
{
return GetMonoVersion();
}
else if (IsFullFramework)
{
return GetFullFrameworkVersion();
}
else if (IsNetCore)
{
return GetNetCoreVersion();
}
else if (IsCoreRT)
{
return FrameworkDescription.Replace("Core ", "CoreRT ");
}

return Unknown;
}

internal static string GetMonoVersion()
{
var monoRuntimeType = Type.GetType("Mono.Runtime");
var monoDisplayName = monoRuntimeType?.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static);
if (monoDisplayName != null)
{
string version = monoDisplayName.Invoke(null, null)?.ToString();
if (version != null)
{
int bracket1 = version.IndexOf('('), bracket2 = version.IndexOf(')');
if (bracket1 != -1 && bracket2 != -1)
{
string comment = version.Substring(bracket1 + 1, bracket2 - bracket1 - 1);
var commentParts = comment.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (commentParts.Length > 2)
{
version = version.Substring(0, bracket1) + "(" + commentParts[0] + " " + commentParts[1] + ")";
}
}
}

return "Mono " + version;
}

return Unknown;
}

internal static string GetFullFrameworkVersion()
{
#if NET45
return Environment.OSVersion.ToString();
#else
return System.Runtime.InteropServices.RuntimeInformation.OSDescription;
#endif
var fullName = System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription; // sth like .NET Framework 4.7.3324.0
var servicingVersion = new string(fullName.SkipWhile(c => !char.IsDigit(c)).ToArray());
var releaseVersion = MapToReleaseVersion(servicingVersion);

return $".NET Framework {releaseVersion}";
}

#if NET45
public static string GetMonoVersion()
internal static string MapToReleaseVersion(string servicingVersion)
{
Type monoRuntimeType = typeof(object).Assembly.GetType("Mono.Runtime");
// the following code assumes that .NET 4.5 is the oldest supported version
if (string.Compare(servicingVersion, "4.5.1") < 0)
{
return "4.5";
}

if (string.Compare(servicingVersion, "4.5.2") < 0)
{
return "4.5.1";
}

if (string.Compare(servicingVersion, "4.6") < 0)
{
return "4.5.2";
}

if (string.Compare(servicingVersion, "4.6.1") < 0)
{
return "4.6";
}

if (string.Compare(servicingVersion, "4.6.2") < 0)
{
return "4.6.1";
}

if (string.Compare(servicingVersion, "4.7") < 0)
{
return "4.6.2";
}

if (monoRuntimeType != null)
if (string.Compare(servicingVersion, "4.7.1") < 0)
{
MethodInfo getDisplayNameMethod = monoRuntimeType.GetMethod(
"GetDisplayName",
BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DeclaredOnly | BindingFlags.ExactBinding,
null,
Type.EmptyTypes,
null);
return "4.7";
}

if (getDisplayNameMethod != null)
if (string.Compare(servicingVersion, "4.7.2") < 0)
{
return "4.7.1";
}

if (string.Compare(servicingVersion, "4.8") < 0)
{
return "4.7.2";
}

return "4.8"; // most probably the last major release of Full .NET Framework
}

internal static string GetNetCoreVersion()
{
string runtimeVersion = TryGetCoreRuntimeVersion(out var version) ? version.ToString() : "?";

return $".NET Core {runtimeVersion}";
}

internal static bool TryGetCoreRuntimeVersion(out Version version)
{
// we can't just use System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription
// because it can be null and it reports versions like 4.6.* for .NET Core 2.*
string runtimeDirectory = System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory();
if (TryGetVersionFromRuntimeDirectory(runtimeDirectory, out version))
{
return true;
}

// systemPrivateCoreLib.Product*Part properties return 0 so we have to implement some ugly parsing...
var systemPrivateCoreLib = FileVersionInfo.GetVersionInfo(typeof(object).Assembly.Location);
if (TryGetVersionFromProductInfo(systemPrivateCoreLib.ProductVersion, systemPrivateCoreLib.ProductName, out version))
{
return true;
}

string frameworkName = Assembly.GetEntryAssembly()?.GetCustomAttribute<TargetFrameworkAttribute>()?.FrameworkName;
if (TryGetVersionFromFrameworkName(frameworkName, out version))
{
return true;
}

if (IsRunningInContainer)
{
return Version.TryParse(Environment.GetEnvironmentVariable("DOTNET_VERSION"), out version)
|| Version.TryParse(Environment.GetEnvironmentVariable("ASPNETCORE_VERSION"), out version);
}

version = null;
return false;
}

// sample input:
// for dotnet run: C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.1.12\
// for dotnet publish: C:\Users\adsitnik\source\repos\ConsoleApp25\ConsoleApp25\bin\Release\netcoreapp2.0\win-x64\publish\
internal static bool TryGetVersionFromRuntimeDirectory(string runtimeDirectory, out Version version)
{
if (!string.IsNullOrEmpty(runtimeDirectory) && Version.TryParse(GetParsableVersionPart(new DirectoryInfo(runtimeDirectory).Name), out version))
{
return true;
}

version = null;
return false;
}

// sample input:
// 2.0: 4.6.26614.01 @BuiltBy: dlab14-DDVSOWINAGE018 @Commit: a536e7eec55c538c94639cefe295aa672996bf9b, Microsoft .NET Framework
// 2.1: 4.6.27817.01 @BuiltBy: dlab14-DDVSOWINAGE101 @Branch: release/2.1 @SrcCode: https://github.com/dotnet/coreclr/tree/6f78fbb3f964b4f407a2efb713a186384a167e5c, Microsoft .NET Framework
// 2.2: 4.6.27817.03 @BuiltBy: dlab14-DDVSOWINAGE101 @Branch: release/2.2 @SrcCode: https://github.com/dotnet/coreclr/tree/ce1d090d33b400a25620c0145046471495067cc7, Microsoft .NET Framework
// 3.0: 3.0.0-preview8.19379.2+ac25be694a5385a6a1496db40de932df0689b742, Microsoft .NET Core
// 5.0: 5.0.0-alpha1.19413.7+0ecefa44c9d66adb8a997d5778dc6c246ad393a7, Microsoft .NET Core
internal static bool TryGetVersionFromProductInfo(string productVersion, string productName, out Version version)
{
if (!string.IsNullOrEmpty(productVersion) && !string.IsNullOrEmpty(productName))
{
if (productName.IndexOf(".NET Core", StringComparison.OrdinalIgnoreCase) >= 0)
{
return (string)getDisplayNameMethod.Invoke(null, null);
string parsableVersion = GetParsableVersionPart(productVersion);
if (Version.TryParse(productVersion, out version) || Version.TryParse(parsableVersion, out version))
{
return true;
}
}

// yes, .NET Core 2.X has a product name == .NET Framework...
if (productName.IndexOf(".NET Framework", StringComparison.OrdinalIgnoreCase) >= 0)
{
const string releaseVersionPrefix = "release/";
int releaseVersionIndex = productVersion.IndexOf(releaseVersionPrefix);
if (releaseVersionIndex > 0)
{
string releaseVersion = GetParsableVersionPart(productVersion.Substring(releaseVersionIndex + releaseVersionPrefix.Length));

return Version.TryParse(releaseVersion, out version);
}
}
}

return null;
version = null;
return false;
}
#endif

// sample input:
// .NETCoreApp,Version=v2.0
// .NETCoreApp,Version=v2.1
internal static bool TryGetVersionFromFrameworkName(string frameworkName, out Version version)
{
const string versionPrefix = ".NETCoreApp,Version=v";
if (!string.IsNullOrEmpty(frameworkName) && frameworkName.StartsWith(versionPrefix))
{
string frameworkVersion = GetParsableVersionPart(frameworkName.Substring(versionPrefix.Length));

return Version.TryParse(frameworkVersion, out version);
}

version = null;
return false;
}

// Version.TryParse does not handle thing like 3.0.0-WORD
private static string GetParsableVersionPart(string fullVersionName) => new string(fullVersionName.TakeWhile(c => char.IsDigit(c) || c == '.').ToArray());
}
}
3 changes: 2 additions & 1 deletion src/Stripe.net/Stripe.net.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
<PackageReference Include="Stylecop.Analyzers" Version="1.1.118">
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
Expand All @@ -39,6 +39,7 @@
</PropertyGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'net45' ">
<PackageReference Include="System.Runtime.InteropServices.RuntimeInformation" Version="4.3.0" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System" />
<Reference Include="System.Configuration" />
Expand Down

0 comments on commit 47acaca

Please sign in to comment.