Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/BenchmarkDotNet/Diagnosers/PerfCollectProfiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -230,11 +230,13 @@ private void EnsureSymbolsForNativeRuntime(DiagnoserActionParameters parameters)
string toolPath = Path.Combine(Path.GetTempPath(), "BenchmarkDotNet", "symbols");
DotNetCliCommand cliCommand = new(
cliPath: cliPath,
filePath: string.Empty,
tfm: string.Empty,
arguments: $"tool install dotnet-symbol --tool-path \"{toolPath}\"",
generateResult: null,
logger: logger,
buildPartition: null,
environmentVariables: Array.Empty<EnvironmentVariable>(),
environmentVariables: [],
timeout: TimeSpan.FromMinutes(3),
logOutput: true); // the following commands might take a while and fail, let's log them

Expand Down
76 changes: 44 additions & 32 deletions src/BenchmarkDotNet/Environments/Runtimes/CoreRuntime.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Versioning;
using BenchmarkDotNet.Helpers;
using BenchmarkDotNet.Extensions;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Portability;
Expand Down Expand Up @@ -47,6 +49,13 @@ public static CoreRuntime CreateForNewVersion(string msBuildMoniker, string disp
return new CoreRuntime(RuntimeMoniker.NotRecognized, msBuildMoniker, displayName);
}

internal static CoreRuntime GetTargetOrCurrentVersion(Assembly? assembly)
// Try to determine the version that the assembly was compiled for.
=> FrameworkVersionHelper.GetTargetCoreVersion(assembly) is { } version
? FromVersion(version, assembly)
// Fallback to the current running version.
: GetCurrentVersion();

internal static CoreRuntime GetCurrentVersion()
{
if (!RuntimeInformation.IsNetCore)
Expand All @@ -59,28 +68,24 @@ internal static CoreRuntime GetCurrentVersion()
throw new NotSupportedException("Unable to recognize .NET Core version, please report a bug at https://github.com/dotnet/BenchmarkDotNet");
}

return FromVersion(version);
return FromVersion(version, null);
}

internal static CoreRuntime FromVersion(Version version)
internal static CoreRuntime FromVersion(Version version, Assembly? assembly = null) => version switch
{
switch (version)
{
case Version v when v.Major == 2 && v.Minor == 0: return Core20;
case Version v when v.Major == 2 && v.Minor == 1: return Core21;
case Version v when v.Major == 2 && v.Minor == 2: return Core22;
case Version v when v.Major == 3 && v.Minor == 0: return Core30;
case Version v when v.Major == 3 && v.Minor == 1: return Core31;
case Version v when v.Major == 5 && v.Minor == 0: return GetPlatformSpecific(Core50);
case Version v when v.Major == 6 && v.Minor == 0: return GetPlatformSpecific(Core60);
case Version v when v.Major == 7 && v.Minor == 0: return GetPlatformSpecific(Core70);
case Version v when v.Major == 8 && v.Minor == 0: return GetPlatformSpecific(Core80);
case Version v when v.Major == 9 && v.Minor == 0: return GetPlatformSpecific(Core90);
case Version v when v.Major == 10 && v.Minor == 0: return GetPlatformSpecific(Core10_0);
default:
return CreateForNewVersion($"net{version.Major}.{version.Minor}", $".NET {version.Major}.{version.Minor}");
}
}
{ Major: 2, Minor: 0 } => Core20,
{ Major: 2, Minor: 1 } => Core21,
{ Major: 2, Minor: 2 } => Core22,
{ Major: 3, Minor: 0 } => Core30,
{ Major: 3, Minor: 1 } => Core31,
{ Major: 5 } => GetPlatformSpecific(Core50, assembly),
{ Major: 6 } => GetPlatformSpecific(Core60, assembly),
{ Major: 7 } => GetPlatformSpecific(Core70, assembly),
{ Major: 8 } => GetPlatformSpecific(Core80, assembly),
{ Major: 9 } => GetPlatformSpecific(Core90, assembly),
{ Major: 10 } => GetPlatformSpecific(Core10_0, assembly),
_ => CreateForNewVersion($"net{version.Major}.{version.Minor}", $".NET {version.Major}.{version.Minor}"),
};

internal static bool TryGetVersion(out Version? version)
{
Expand Down Expand Up @@ -220,29 +225,36 @@ internal static bool TryGetVersionFromFrameworkName(string frameworkName, out Ve
// Version.TryParse does not handle thing like 3.0.0-WORD
internal static string GetParsableVersionPart(string fullVersionName) => new string(fullVersionName.TakeWhile(c => char.IsDigit(c) || c == '.').ToArray());

private static CoreRuntime GetPlatformSpecific(CoreRuntime fallback)
private static CoreRuntime GetPlatformSpecific(CoreRuntime fallback, Assembly? assembly)
=> TryGetTargetPlatform(assembly ?? Assembly.GetEntryAssembly(), out var platform)
? new CoreRuntime(fallback.RuntimeMoniker, $"{fallback.MsBuildMoniker}-{platform}", fallback.Name)
: fallback;

internal static bool TryGetTargetPlatform(Assembly? assembly, [NotNullWhen(true)] out string? platform)
{
// TargetPlatformAttribute is not part of .NET Standard 2.0 so as usuall we have to use some reflection hacks...
platform = null;

if (assembly is null)
return false;

// TargetPlatformAttribute is not part of .NET Standard 2.0 so as usual we have to use some reflection hacks.
var targetPlatformAttributeType = typeof(object).Assembly.GetType("System.Runtime.Versioning.TargetPlatformAttribute", throwOnError: false);
if (targetPlatformAttributeType is null) // an old preview version of .NET 5
return fallback;

var exe = Assembly.GetEntryAssembly();
if (exe is null)
return fallback;
return false;

var attributeInstance = exe.GetCustomAttribute(targetPlatformAttributeType);
var attributeInstance = assembly.GetCustomAttribute(targetPlatformAttributeType);
if (attributeInstance is null)
return fallback;
return false;

var platformNameProperty = targetPlatformAttributeType.GetProperty("PlatformName");
if (platformNameProperty is null)
return fallback;
return false;

if (!(platformNameProperty.GetValue(attributeInstance) is string platformName))
return fallback;
if (platformNameProperty.GetValue(attributeInstance) is not string platformName)
return false;

return new CoreRuntime(fallback.RuntimeMoniker, $"{fallback.MsBuildMoniker}-{platformName}", fallback.Name);
platform = platformName;
return true;
}
}
}
83 changes: 67 additions & 16 deletions src/BenchmarkDotNet/Helpers/FrameworkVersionHelper.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Versioning;
using BenchmarkDotNet.Environments;
using Microsoft.Win32;

namespace BenchmarkDotNet.Helpers
Expand All @@ -23,28 +25,38 @@ private static readonly (int minReleaseNumber, string version)[] FrameworkVersio
];

internal static string? GetTargetFrameworkVersion(Assembly? assembly)
{
if (assembly is null)
// Look for a TargetFrameworkAttribute with a supported Framework version.
=> assembly?.GetCustomAttribute<TargetFrameworkAttribute>()?.FrameworkName switch
{
return null;
}
".NETFramework,Version=v4.6.1" => "4.6.1",
".NETFramework,Version=v4.6.2" => "4.6.2",
".NETFramework,Version=v4.7" => "4.7",
".NETFramework,Version=v4.7.1" => "4.7.1",
".NETFramework,Version=v4.7.2" => "4.7.2",
".NETFramework,Version=v4.8" => "4.8",
".NETFramework,Version=v4.8.1" => "4.8.1",
// Null assembly, or TargetFrameworkAttribute not found, or the assembly targeted a version older than we support,
// or the assembly targeted a non-framework tfm (like netstandard2.0).
_ => null,
};

internal static Version? GetTargetCoreVersion(Assembly? assembly)
{
//.NETCoreApp,Version=vX.Y
const string FrameworkPrefix = ".NETCoreApp,Version=v";

// Look for a TargetFrameworkAttribute with a supported Framework version.
foreach (var attribute in assembly.GetCustomAttributes<TargetFrameworkAttribute>())
string? framework = assembly?.GetCustomAttribute<TargetFrameworkAttribute>()?.FrameworkName;
if (framework?.StartsWith(FrameworkPrefix) == true
&& Version.TryParse(framework[FrameworkPrefix.Length..], out var version)
// We don't support netcoreapp1.X
&& version.Major >= 2)
{
switch (attribute.FrameworkName)
{
case ".NETFramework,Version=v4.6.1": return "4.6.1";
case ".NETFramework,Version=v4.6.2": return "4.6.2";
case ".NETFramework,Version=v4.7": return "4.7";
case ".NETFramework,Version=v4.7.1": return "4.7.1";
case ".NETFramework,Version=v4.7.2": return "4.7.2";
case ".NETFramework,Version=v4.8": return "4.8";
case ".NETFramework,Version=v4.8.1": return "4.8.1";
}
return version;
}

// TargetFrameworkAttribute not found, or the assembly targeted a version older than we support.
// Null assembly, or TargetFrameworkAttribute not found, or the assembly targeted a version older than we support,
// or the assembly targeted a non-core tfm (like netstandard2.0).
return null;
}

Expand Down Expand Up @@ -112,5 +124,44 @@ private static bool IsDeveloperPackInstalled(string version) => Directory.Exists
Environment.Is64BitOperatingSystem
? Environment.SpecialFolder.ProgramFilesX86
: Environment.SpecialFolder.ProgramFiles);

internal static string? GetTfm(Assembly assembly)
{
// We don't support exotic frameworks like Silverlight, WindowsPhone, Xamarin.Mac, etc.
const string CorePrefix = ".NETCoreApp,Version=v";
const string FrameworkPrefix = ".NETFramework,Version=v";
const string StandardPrefix = ".NETStandard,Version=v";

// Look for a TargetFrameworkAttribute with a supported Framework version.
string? framework = assembly.GetCustomAttribute<TargetFrameworkAttribute>()?.FrameworkName;
if (TryParseVersion(CorePrefix, out var version))
{
return version.Major < 5
? $"netcoreapp{version.Major}.{version.Minor}"
: CoreRuntime.TryGetTargetPlatform(assembly, out var platform)
? $"net{version.Major}.{version.Minor}-{platform}"
: $"net{version.Major}.{version.Minor}";
}
if (TryParseVersion(FrameworkPrefix, out version))
{
return version.Build > 0
? $"net{version.Major}{version.Minor}{version.Build}"
: $"net{version.Major}{version.Minor}";
}
if (!TryParseVersion(StandardPrefix, out version))
{
return $"netstandard{version.Major}.{version.Minor}";
}

// TargetFrameworkAttribute not found, or the assembly targeted a framework we don't support.
return null;

bool TryParseVersion(string prefix, [NotNullWhen(true)] out Version? version)
{
version = null;
return framework?.StartsWith(prefix) == true
&& Version.TryParse(framework[prefix.Length..], out version);
}
}
}
}
15 changes: 12 additions & 3 deletions src/BenchmarkDotNet/Portability/RuntimeInformation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -171,9 +171,18 @@ string GetDetailedVersion()
}

internal static Runtime GetTargetOrCurrentRuntime(Assembly? assembly)
=> !IsMono && !IsWasm && IsFullFramework // Match order of checks in GetCurrentRuntime().
? ClrRuntime.GetTargetOrCurrentVersion(assembly)
: GetCurrentRuntime();
{
// Match order of checks in GetCurrentRuntime().
if (!IsMono && !IsWasm)
{
if (IsFullFramework)
return ClrRuntime.GetTargetOrCurrentVersion(assembly);
// 99% of the time the core runtime is the same as the target framework, but the runtime could roll forward if it's not self-contained.
if (IsNetCore)
return CoreRuntime.GetTargetOrCurrentVersion(assembly);
}
return GetCurrentRuntime();
}

internal static Runtime GetCurrentRuntime()
{
Expand Down
2 changes: 1 addition & 1 deletion src/BenchmarkDotNet/Templates/CsProj.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<ImportDirectoryBuildProps>false</ImportDirectoryBuildProps>
<ImportDirectoryBuildTargets>false</ImportDirectoryBuildTargets>
<AssemblyTitle>$PROGRAMNAME$</AssemblyTitle>
<TargetFramework>$TFM$</TargetFramework>
<TargetFrameworks>$TFM$</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PlatformTarget>$PLATFORM$</PlatformTarget>
<AssemblyName>$PROGRAMNAME$</AssemblyName>
Expand Down
2 changes: 1 addition & 1 deletion src/BenchmarkDotNet/Templates/MonoAOTLLVMCsProj.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>$TFM$</TargetFramework>
<TargetFrameworks>$TFM$</TargetFrameworks>
<MicrosoftNetCoreAppRuntimePackDir>$RUNTIMEPACK$</MicrosoftNetCoreAppRuntimePackDir>
<RuntimeIdentifier>$RUNTIMEIDENTIFIER$</RuntimeIdentifier>
<EnableTargetingPackDownload>false</EnableTargetingPackDownload>
Expand Down
2 changes: 1 addition & 1 deletion src/BenchmarkDotNet/Templates/WasmCsProj.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<OutputType>Exe</OutputType>
<RuntimeConfig>Release</RuntimeConfig>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<TargetFramework>$TFM$</TargetFramework>
<TargetFrameworks>$TFM$</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<AppDir>$(PublishDir)</AppDir>
<AssemblyName>$PROGRAMNAME$</AssemblyName>
Expand Down
2 changes: 1 addition & 1 deletion src/BenchmarkDotNet/Toolchains/ArtifactsPaths.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace BenchmarkDotNet.Toolchains
{
public class ArtifactsPaths
{
public static readonly ArtifactsPaths Empty = new ArtifactsPaths("", "", "", "", "", "", "", "", "", "", "", "");
public static readonly ArtifactsPaths Empty = new("", "", "", "", "", "", "", "", "", "", "", "");

[PublicAPI] public string RootArtifactsFolderPath { get; }
[PublicAPI] public string BuildArtifactsDirectoryPath { get; }
Expand Down
18 changes: 4 additions & 14 deletions src/BenchmarkDotNet/Toolchains/CoreRun/CoreRunPublisher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,11 @@

namespace BenchmarkDotNet.Toolchains.CoreRun
{
public class CoreRunPublisher : IBuilder
public class CoreRunPublisher(string tfm, FileInfo coreRun, FileInfo? customDotNetCliPath = null) : DotNetCliPublisher(tfm, customDotNetCliPath?.FullName)
{
public CoreRunPublisher(FileInfo coreRun, FileInfo? customDotNetCliPath = null)
public override BuildResult Build(GenerateResult generateResult, BuildPartition buildPartition, ILogger logger)
{
CoreRun = coreRun;
DotNetCliPublisher = new DotNetCliPublisher(customDotNetCliPath?.FullName);
}

private FileInfo CoreRun { get; }

private DotNetCliPublisher DotNetCliPublisher { get; }

public BuildResult Build(GenerateResult generateResult, BuildPartition buildPartition, ILogger logger)
{
var buildResult = DotNetCliPublisher.Build(generateResult, buildPartition, logger);
var buildResult = base.Build(generateResult, buildPartition, logger);

if (buildResult.IsBuildSuccess)
UpdateDuplicatedDependencies(buildResult.ArtifactsPaths, logger);
Expand All @@ -37,7 +27,7 @@ public BuildResult Build(GenerateResult generateResult, BuildPartition buildPart
private void UpdateDuplicatedDependencies(ArtifactsPaths artifactsPaths, ILogger logger)
{
var publishedDirectory = new DirectoryInfo(artifactsPaths.BinariesDirectoryPath);
var coreRunDirectory = CoreRun.Directory;
var coreRunDirectory = coreRun.Directory;

foreach (var publishedDependency in publishedDirectory
.EnumerateFileSystemInfos()
Expand Down
2 changes: 1 addition & 1 deletion src/BenchmarkDotNet/Toolchains/CoreRun/CoreRunToolchain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public CoreRunToolchain(FileInfo coreRun, bool createCopy = true,

Name = displayName;
Generator = new CoreRunGenerator(SourceCoreRun, CopyCoreRun, targetFrameworkMoniker, customDotNetCliPath?.FullName, restorePath?.FullName);
Builder = new CoreRunPublisher(CopyCoreRun, customDotNetCliPath);
Builder = new CoreRunPublisher(targetFrameworkMoniker, CopyCoreRun, customDotNetCliPath);
Executor = new DotNetCliExecutor(customDotNetCliPath: CopyCoreRun.FullName); // instead of executing "dotnet $pathToDll" we do "CoreRun $pathToDll"
}

Expand Down
Loading