Skip to content

Commit c62caa6

Browse files
committed
Get TargetFrameworkAttribute from the benchmark assembly.
1 parent 35b0082 commit c62caa6

File tree

6 files changed

+52
-93
lines changed

6 files changed

+52
-93
lines changed

src/BenchmarkDotNet/Environments/Runtimes/ClrRuntime.cs

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Reflection;
23
using BenchmarkDotNet.Detectors;
34
using BenchmarkDotNet.Helpers;
45
using BenchmarkDotNet.Jobs;
@@ -15,14 +16,6 @@ public class ClrRuntime : Runtime, IEquatable<ClrRuntime>
1516
public static readonly ClrRuntime Net48 = new ClrRuntime(RuntimeMoniker.Net48, "net48", ".NET Framework 4.8");
1617
public static readonly ClrRuntime Net481 = new ClrRuntime(RuntimeMoniker.Net481, "net481", ".NET Framework 4.8.1");
1718

18-
// Use Lazy to avoid any assembly loading issues on non Windows systems, and for fast cached access for multiple reads.
19-
// Also so that the value will be obtained from the first call which happens on the user's thread,
20-
// then when this is read again on a background thread from the BuildInParallel step, it will return the cached result.
21-
#if NET6_0_OR_GREATER
22-
[System.Runtime.Versioning.SupportedOSPlatform("windows")]
23-
#endif
24-
private static readonly Lazy<ClrRuntime> Current = new(RetrieveCurrentVersion, true);
25-
2619
public string Version { get; }
2720

2821
private ClrRuntime(RuntimeMoniker runtimeMoniker, string msBuildMoniker, string displayName, string? version = null)
@@ -57,21 +50,28 @@ internal static ClrRuntime GetCurrentVersion()
5750
throw new PlatformNotSupportedException(".NET Framework supports Windows OS only.");
5851
}
5952

60-
return Current.Value;
53+
string version = FrameworkVersionHelper.GetLatestNetDeveloperPackVersion()
54+
?? FrameworkVersionHelper.GetFrameworkReleaseVersion(); // .NET Developer Pack is not installed
55+
return GetRuntimeFromVersion(version);
6156
}
6257

63-
#if NET6_0_OR_GREATER
64-
[System.Runtime.Versioning.SupportedOSPlatform("windows")]
65-
#endif
66-
private static ClrRuntime RetrieveCurrentVersion()
58+
internal static ClrRuntime GetTargetOrCurrentVersion(Assembly? assembly)
6759
{
68-
// Try to determine the Framework version that the executable was compiled for.
69-
string version = FrameworkVersionHelper.GetTargetFrameworkVersion()
60+
if (!OsDetector.IsWindows())
61+
{
62+
throw new PlatformNotSupportedException(".NET Framework supports Windows OS only.");
63+
}
64+
65+
// Try to determine the Framework version that the assembly was compiled for.
66+
string? version = FrameworkVersionHelper.GetTargetFrameworkVersion(assembly);
67+
return version != null
68+
? GetRuntimeFromVersion(version)
7069
// Fallback to the current running Framework version.
71-
?? FrameworkVersionHelper.GetLatestNetDeveloperPackVersion()
72-
?? FrameworkVersionHelper.GetFrameworkReleaseVersion(); // .NET Developer Pack is not installed
70+
: GetCurrentVersion();
71+
}
7372

74-
return version switch
73+
private static ClrRuntime GetRuntimeFromVersion(string version)
74+
=> version switch
7575
{
7676
"4.6.1" => Net461,
7777
"4.6.2" => Net462,
@@ -83,6 +83,5 @@ private static ClrRuntime RetrieveCurrentVersion()
8383
// unlikely to happen but theoretically possible
8484
_ => new ClrRuntime(RuntimeMoniker.NotRecognized, $"net{version.Replace(".", null)}", $".NET Framework {version}"),
8585
};
86-
}
8786
}
8887
}

src/BenchmarkDotNet/Helpers/FrameworkVersionHelper.cs

Lines changed: 12 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
using System;
2-
using System.Collections.Generic;
3-
using System.Diagnostics;
42
using System.IO;
53
using System.Linq;
64
using System.Reflection;
@@ -24,54 +22,25 @@ private static readonly (int minReleaseNumber, string version)[] FrameworkVersio
2422
(394254, "4.6.1")
2523
];
2624

27-
internal static string? GetTargetFrameworkVersion()
25+
internal static string? GetTargetFrameworkVersion(Assembly? assembly)
2826
{
29-
// Search assemblies until we find a TargetFrameworkAttribute with a supported Framework version.
30-
// We don't search all assemblies, only the entry assembly and callers.
31-
foreach (var assembly in EnumerateAssemblies())
27+
// Look for a TargetFrameworkAttribute with a supported Framework version.
28+
foreach (var attribute in assembly.GetCustomAttributes<TargetFrameworkAttribute>())
3229
{
33-
foreach (var attribute in assembly.GetCustomAttributes<TargetFrameworkAttribute>())
30+
switch (attribute.FrameworkName)
3431
{
35-
switch (attribute.FrameworkName)
36-
{
37-
case ".NETFramework,Version=v4.6.1": return "4.6.1";
38-
case ".NETFramework,Version=v4.6.2": return "4.6.2";
39-
case ".NETFramework,Version=v4.7": return "4.7";
40-
case ".NETFramework,Version=v4.7.1": return "4.7.1";
41-
case ".NETFramework,Version=v4.7.2": return "4.7.2";
42-
case ".NETFramework,Version=v4.8": return "4.8";
43-
case ".NETFramework,Version=v4.8.1": return "4.8.1";
44-
}
32+
case ".NETFramework,Version=v4.6.1": return "4.6.1";
33+
case ".NETFramework,Version=v4.6.2": return "4.6.2";
34+
case ".NETFramework,Version=v4.7": return "4.7";
35+
case ".NETFramework,Version=v4.7.1": return "4.7.1";
36+
case ".NETFramework,Version=v4.7.2": return "4.7.2";
37+
case ".NETFramework,Version=v4.8": return "4.8";
38+
case ".NETFramework,Version=v4.8.1": return "4.8.1";
4539
}
4640
}
4741

42+
// TargetFrameworkAttribute not found, or the assembly targeted a version older than we support.
4843
return null;
49-
50-
static IEnumerable<Assembly> EnumerateAssemblies()
51-
{
52-
var entryAssembly = Assembly.GetEntryAssembly();
53-
// Assembly.GetEntryAssembly() returns null in unit test frameworks.
54-
if (entryAssembly != null)
55-
{
56-
yield return entryAssembly;
57-
}
58-
// Search calling assemblies starting from the highest stack frame
59-
// (expected to be the entry assembly if Assembly.GetEntryAssembly() returned null),
60-
// excluding this assembly.
61-
var stacktrace = new StackTrace(false);
62-
var searchedAssemblies = new HashSet<Assembly>()
63-
{
64-
stacktrace.GetFrame(0).GetMethod().ReflectedType.Assembly
65-
};
66-
for (int i = stacktrace.FrameCount - 1; i >= 1 ; --i)
67-
{
68-
var assembly = stacktrace.GetFrame(i).GetMethod().ReflectedType.Assembly;
69-
if (searchedAssemblies.Add(assembly))
70-
{
71-
yield return assembly;
72-
}
73-
}
74-
}
7544
}
7645

7746
internal static string GetFrameworkDescription()

src/BenchmarkDotNet/Portability/RuntimeInformation.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,11 @@ string GetDetailedVersion()
182182
}
183183
}
184184

185+
internal static Runtime GetTargetOrCurrentRuntime(Assembly? assembly)
186+
=> !IsMono && !IsWasm && IsFullFramework // Match order of checks in GetCurrentRuntime().
187+
? ClrRuntime.GetTargetOrCurrentVersion(assembly)
188+
: GetCurrentRuntime();
189+
185190
internal static Runtime GetCurrentRuntime()
186191
{
187192
//do not change the order of conditions because it may cause incorrect determination of runtime

src/BenchmarkDotNet/Running/BenchmarkCase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ internal BenchmarkCase(Descriptor descriptor, Job job, ParameterInstances parame
3030

3131
public Runtime GetRuntime() => Job.Environment.HasValue(EnvironmentMode.RuntimeCharacteristic)
3232
? Job.Environment.Runtime
33-
: RuntimeInformation.GetCurrentRuntime();
33+
: RuntimeInformation.GetTargetOrCurrentRuntime(Descriptor.WorkloadMethod.DeclaringType.Assembly);
3434

3535
public void Dispose() => Parameters.Dispose();
3636

src/BenchmarkDotNet/Running/BenchmarkPartitioner.cs

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22
using System.Collections.Generic;
33
using System.Linq;
44
using BenchmarkDotNet.Characteristics;
5-
using BenchmarkDotNet.Environments;
65
using BenchmarkDotNet.Jobs;
7-
using BenchmarkDotNet.Portability;
86
using BenchmarkDotNet.Toolchains;
97

108
namespace BenchmarkDotNet.Running
@@ -22,18 +20,16 @@ internal class BenchmarkRuntimePropertiesComparer : IEqualityComparer<BenchmarkC
2220
{
2321
internal static readonly IEqualityComparer<BenchmarkCase> Instance = new BenchmarkRuntimePropertiesComparer();
2422

25-
private static readonly Runtime Current = RuntimeInformation.GetCurrentRuntime();
26-
2723
public bool Equals(BenchmarkCase x, BenchmarkCase y)
2824
{
29-
if (x == null && y == null)
25+
if (x == y)
3026
return true;
3127
if (x == null || y == null)
3228
return false;
3329
var jobX = x.Job;
3430
var jobY = y.Job;
3531

36-
if (AreDifferent(GetRuntime(jobX), GetRuntime(jobY))) // Mono vs .NET vs Core
32+
if (AreDifferent(x.GetRuntime(), x.GetRuntime())) // Mono vs .NET vs Core
3733
return false;
3834
if (AreDifferent(x.GetToolchain(), y.GetToolchain())) // Mono vs .NET vs Core vs InProcess
3935
return false;
@@ -90,20 +86,8 @@ public int GetHashCode(BenchmarkCase obj)
9086
return hashCode.ToHashCode();
9187
}
9288

93-
private static Runtime GetRuntime(Job job)
94-
=> job.Environment.HasValue(EnvironmentMode.RuntimeCharacteristic)
95-
? job.Environment.Runtime
96-
: Current;
97-
9889
private static bool AreDifferent(object x, object y)
99-
{
100-
if (x == null && y == null)
101-
return false;
102-
if (x == null || y == null)
103-
return true;
104-
105-
return !x.Equals(y);
106-
}
90+
=> !Equals(x, y);
10791

10892
private static bool AreDifferent(IReadOnlyList<Argument> x, IReadOnlyList<Argument> y)
10993
{

src/BenchmarkDotNet/Toolchains/ToolchainExtensions.cs

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
using BenchmarkDotNet.Detectors;
33
using BenchmarkDotNet.Environments;
44
using BenchmarkDotNet.Extensions;
5-
using BenchmarkDotNet.Helpers;
65
using BenchmarkDotNet.Jobs;
76
using BenchmarkDotNet.Portability;
87
using BenchmarkDotNet.Running;
@@ -19,28 +18,31 @@ namespace BenchmarkDotNet.Toolchains
1918
{
2019
internal static class ToolchainExtensions
2120
{
22-
internal static IToolchain GetToolchain(this BenchmarkCase benchmarkCase) => GetToolchain(benchmarkCase.Job, benchmarkCase.Descriptor);
23-
24-
internal static IToolchain GetToolchain(this Job job) => GetToolchain(job, null);
21+
internal static IToolchain GetToolchain(this BenchmarkCase benchmarkCase)
22+
=> benchmarkCase.Job.Infrastructure.TryGetToolchain(out var toolchain)
23+
? toolchain
24+
: GetToolchain(
25+
benchmarkCase.GetRuntime(),
26+
benchmarkCase.Descriptor,
27+
benchmarkCase.Job.HasDynamicBuildCharacteristic()
28+
);
2529

26-
private static IToolchain GetToolchain(Job job, Descriptor descriptor)
30+
internal static IToolchain GetToolchain(this Job job)
2731
=> job.Infrastructure.TryGetToolchain(out var toolchain)
2832
? toolchain
2933
: GetToolchain(
3034
job.ResolveValue(EnvironmentMode.RuntimeCharacteristic, EnvironmentResolver.Instance),
31-
descriptor,
32-
job.HasDynamicBuildCharacteristic());
35+
null,
36+
job.HasDynamicBuildCharacteristic()
37+
);
3338

3439
internal static IToolchain GetToolchain(this Runtime runtime, Descriptor? descriptor = null, bool preferMsBuildToolchains = false)
3540
{
3641
switch (runtime)
3742
{
3843
case ClrRuntime clrRuntime:
39-
if (!preferMsBuildToolchains && RuntimeInformation.IsFullFramework
40-
&& RuntimeInformation.GetCurrentRuntime().MsBuildMoniker == runtime.MsBuildMoniker)
41-
{
44+
if (!preferMsBuildToolchains && RuntimeInformation.IsFullFramework)
4245
return RoslynToolchain.Instance;
43-
}
4446

4547
return clrRuntime.RuntimeMoniker != RuntimeMoniker.NotRecognized
4648
? GetToolchain(clrRuntime.RuntimeMoniker)

0 commit comments

Comments
 (0)