From 35b008238277de0d146a5f8e92def723a55432d3 Mon Sep 17 00:00:00 2001 From: Tim Date: Sun, 22 Dec 2024 04:29:43 -0500 Subject: [PATCH 1/4] =?UTF-8?q?=EF=BB=BFRetrieve=20current=20.Net=20Framew?= =?UTF-8?q?ork=20version=20from=20TargetFrameworkAttribute.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Environments/Runtimes/ClrRuntime.cs | 47 +++++++---- .../Helpers/FrameworkVersionHelper.cs | 82 +++++++++++++++---- .../MultipleFrameworksTest.cs | 14 ---- 3 files changed, 98 insertions(+), 45 deletions(-) diff --git a/src/BenchmarkDotNet/Environments/Runtimes/ClrRuntime.cs b/src/BenchmarkDotNet/Environments/Runtimes/ClrRuntime.cs index 21bb2821df..a379a7b914 100644 --- a/src/BenchmarkDotNet/Environments/Runtimes/ClrRuntime.cs +++ b/src/BenchmarkDotNet/Environments/Runtimes/ClrRuntime.cs @@ -2,7 +2,6 @@ using BenchmarkDotNet.Detectors; using BenchmarkDotNet.Helpers; using BenchmarkDotNet.Jobs; -using BenchmarkDotNet.Portability; namespace BenchmarkDotNet.Environments { @@ -16,6 +15,14 @@ public class ClrRuntime : Runtime, IEquatable public static readonly ClrRuntime Net48 = new ClrRuntime(RuntimeMoniker.Net48, "net48", ".NET Framework 4.8"); public static readonly ClrRuntime Net481 = new ClrRuntime(RuntimeMoniker.Net481, "net481", ".NET Framework 4.8.1"); + // Use Lazy to avoid any assembly loading issues on non Windows systems, and for fast cached access for multiple reads. + // Also so that the value will be obtained from the first call which happens on the user's thread, + // then when this is read again on a background thread from the BuildInParallel step, it will return the cached result. +#if NET6_0_OR_GREATER + [System.Runtime.Versioning.SupportedOSPlatform("windows")] +#endif + private static readonly Lazy Current = new(RetrieveCurrentVersion, true); + public string Version { get; } private ClrRuntime(RuntimeMoniker runtimeMoniker, string msBuildMoniker, string displayName, string? version = null) @@ -47,27 +54,35 @@ internal static ClrRuntime GetCurrentVersion() { if (!OsDetector.IsWindows()) { - throw new NotSupportedException(".NET Framework supports Windows OS only."); + throw new PlatformNotSupportedException(".NET Framework supports Windows OS only."); } - // this logic is put to a separate method to avoid any assembly loading issues on non Windows systems - string sdkVersion = FrameworkVersionHelper.GetLatestNetDeveloperPackVersion(); + return Current.Value; + } - string version = sdkVersion +#if NET6_0_OR_GREATER + [System.Runtime.Versioning.SupportedOSPlatform("windows")] +#endif + private static ClrRuntime RetrieveCurrentVersion() + { + // Try to determine the Framework version that the executable was compiled for. + string version = FrameworkVersionHelper.GetTargetFrameworkVersion() + // Fallback to the current running Framework version. + ?? FrameworkVersionHelper.GetLatestNetDeveloperPackVersion() ?? FrameworkVersionHelper.GetFrameworkReleaseVersion(); // .NET Developer Pack is not installed - switch (version) + return version switch { - case "4.6.1": return Net461; - case "4.6.2": return Net462; - case "4.7": return Net47; - case "4.7.1": return Net471; - case "4.7.2": return Net472; - case "4.8": return Net48; - case "4.8.1": return Net481; - default: // unlikely to happen but theoretically possible - return new ClrRuntime(RuntimeMoniker.NotRecognized, $"net{version.Replace(".", null)}", $".NET Framework {version}"); - } + "4.6.1" => Net461, + "4.6.2" => Net462, + "4.7" => Net47, + "4.7.1" => Net471, + "4.7.2" => Net472, + "4.8" => Net48, + "4.8.1" => Net481, + // unlikely to happen but theoretically possible + _ => new ClrRuntime(RuntimeMoniker.NotRecognized, $"net{version.Replace(".", null)}", $".NET Framework {version}"), + }; } } } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Helpers/FrameworkVersionHelper.cs b/src/BenchmarkDotNet/Helpers/FrameworkVersionHelper.cs index 88093a6a52..1391bdd238 100644 --- a/src/BenchmarkDotNet/Helpers/FrameworkVersionHelper.cs +++ b/src/BenchmarkDotNet/Helpers/FrameworkVersionHelper.cs @@ -1,6 +1,10 @@ using System; +using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; +using System.Reflection; +using System.Runtime.Versioning; using Microsoft.Win32; namespace BenchmarkDotNet.Helpers @@ -10,7 +14,7 @@ internal static class FrameworkVersionHelper // magic numbers come from https://docs.microsoft.com/en-us/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed // should be ordered by release number private static readonly (int minReleaseNumber, string version)[] FrameworkVersions = - { + [ (533320, "4.8.1"), // value taken from Windows 11 arm64 insider build (528040, "4.8"), (461808, "4.7.2"), @@ -18,7 +22,57 @@ private static readonly (int minReleaseNumber, string version)[] FrameworkVersio (460798, "4.7"), (394802, "4.6.2"), (394254, "4.6.1") - }; + ]; + + internal static string? GetTargetFrameworkVersion() + { + // Search assemblies until we find a TargetFrameworkAttribute with a supported Framework version. + // We don't search all assemblies, only the entry assembly and callers. + foreach (var assembly in EnumerateAssemblies()) + { + foreach (var attribute in assembly.GetCustomAttributes()) + { + 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 null; + + static IEnumerable EnumerateAssemblies() + { + var entryAssembly = Assembly.GetEntryAssembly(); + // Assembly.GetEntryAssembly() returns null in unit test frameworks. + if (entryAssembly != null) + { + yield return entryAssembly; + } + // Search calling assemblies starting from the highest stack frame + // (expected to be the entry assembly if Assembly.GetEntryAssembly() returned null), + // excluding this assembly. + var stacktrace = new StackTrace(false); + var searchedAssemblies = new HashSet() + { + stacktrace.GetFrame(0).GetMethod().ReflectedType.Assembly + }; + for (int i = stacktrace.FrameCount - 1; i >= 1 ; --i) + { + var assembly = stacktrace.GetFrame(i).GetMethod().ReflectedType.Assembly; + if (searchedAssemblies.Add(assembly)) + { + yield return assembly; + } + } + } + } internal static string GetFrameworkDescription() { @@ -57,30 +111,28 @@ internal static string MapToReleaseVersion(string servicingVersion) #if NET6_0_OR_GREATER - [System.Runtime.Versioning.SupportedOSPlatform("windows")] + [SupportedOSPlatform("windows")] #endif private static int? GetReleaseNumberFromWindowsRegistry() { - using (var baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32)) - using (var ndpKey = baseKey.OpenSubKey(@"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\")) - { - if (ndpKey == null) - return null; - return Convert.ToInt32(ndpKey.GetValue("Release")); - } + using var baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32); + using var ndpKey = baseKey.OpenSubKey(@"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\"); + if (ndpKey == null) + return null; + return Convert.ToInt32(ndpKey.GetValue("Release")); } #if NET6_0_OR_GREATER - [System.Runtime.Versioning.SupportedOSPlatform("windows")] + [SupportedOSPlatform("windows")] #endif - internal static string GetLatestNetDeveloperPackVersion() + internal static string? GetLatestNetDeveloperPackVersion() { - if (!(GetReleaseNumberFromWindowsRegistry() is int releaseNumber)) + if (GetReleaseNumberFromWindowsRegistry() is not int releaseNumber) return null; return FrameworkVersions - .FirstOrDefault(v => releaseNumber >= v.minReleaseNumber && IsDeveloperPackInstalled(v.version)) - .version; + .FirstOrDefault(v => releaseNumber >= v.minReleaseNumber && IsDeveloperPackInstalled(v.version)) + .version; } // Reference Assemblies exists when Developer Pack is installed diff --git a/tests/BenchmarkDotNet.IntegrationTests.ManualRunning.MultipleFrameworks/MultipleFrameworksTest.cs b/tests/BenchmarkDotNet.IntegrationTests.ManualRunning.MultipleFrameworks/MultipleFrameworksTest.cs index b9cf360c8f..9de46632d0 100644 --- a/tests/BenchmarkDotNet.IntegrationTests.ManualRunning.MultipleFrameworks/MultipleFrameworksTest.cs +++ b/tests/BenchmarkDotNet.IntegrationTests.ManualRunning.MultipleFrameworks/MultipleFrameworksTest.cs @@ -18,20 +18,6 @@ public class MultipleFrameworksTest : BenchmarkTestExecutor [InlineData(RuntimeMoniker.Net80)] public void EachFrameworkIsRebuilt(RuntimeMoniker runtime) { -#if NET461 - // We cannot detect what target framework version the host was compiled for on full Framework, - // which causes the RoslynToolchain to be used instead of CsProjClassicNetToolchain when the host is full Framework - // (because full Framework always uses the version that's installed on the machine, unlike Core), - // which means if the machine has net48 installed (not net481), the net461 host with net48 runtime moniker - // will not be recompiled, causing the test to fail. - - // If we ever change the default toolchain to CsProjClassicNetToolchain instead of RoslynToolchain, we can remove this check. - if (runtime == RuntimeMoniker.Net48) - { - // XUnit doesn't provide Assert.Skip API yet. - return; - } -#endif var config = ManualConfig.CreateEmpty().AddJob(Job.Dry.WithRuntime(runtime.GetRuntime()).WithEnvironmentVariable(TfmEnvVarName, runtime.ToString())); CanExecute(config); } From 12808989e731c6ce14446e39d138523d1ab6be62 Mon Sep 17 00:00:00 2001 From: Tim Cassell Date: Sun, 9 Nov 2025 03:22:01 -0500 Subject: [PATCH 2/4] Get TargetFrameworkAttribute from the benchmark assembly. --- .../Environments/Runtimes/ClrRuntime.cs | 37 ++++++------- .../Helpers/FrameworkVersionHelper.cs | 55 ++++--------------- .../Portability/RuntimeInformation.cs | 5 ++ src/BenchmarkDotNet/Running/BenchmarkCase.cs | 2 +- .../Running/BenchmarkPartitioner.cs | 22 +------- .../Toolchains/ToolchainExtensions.cs | 18 ++++-- 6 files changed, 51 insertions(+), 88 deletions(-) diff --git a/src/BenchmarkDotNet/Environments/Runtimes/ClrRuntime.cs b/src/BenchmarkDotNet/Environments/Runtimes/ClrRuntime.cs index a379a7b914..a8e22eb4f5 100644 --- a/src/BenchmarkDotNet/Environments/Runtimes/ClrRuntime.cs +++ b/src/BenchmarkDotNet/Environments/Runtimes/ClrRuntime.cs @@ -1,4 +1,5 @@ using System; +using System.Reflection; using BenchmarkDotNet.Detectors; using BenchmarkDotNet.Helpers; using BenchmarkDotNet.Jobs; @@ -15,14 +16,6 @@ public class ClrRuntime : Runtime, IEquatable public static readonly ClrRuntime Net48 = new ClrRuntime(RuntimeMoniker.Net48, "net48", ".NET Framework 4.8"); public static readonly ClrRuntime Net481 = new ClrRuntime(RuntimeMoniker.Net481, "net481", ".NET Framework 4.8.1"); - // Use Lazy to avoid any assembly loading issues on non Windows systems, and for fast cached access for multiple reads. - // Also so that the value will be obtained from the first call which happens on the user's thread, - // then when this is read again on a background thread from the BuildInParallel step, it will return the cached result. -#if NET6_0_OR_GREATER - [System.Runtime.Versioning.SupportedOSPlatform("windows")] -#endif - private static readonly Lazy Current = new(RetrieveCurrentVersion, true); - public string Version { get; } private ClrRuntime(RuntimeMoniker runtimeMoniker, string msBuildMoniker, string displayName, string? version = null) @@ -57,21 +50,28 @@ internal static ClrRuntime GetCurrentVersion() throw new PlatformNotSupportedException(".NET Framework supports Windows OS only."); } - return Current.Value; + string version = FrameworkVersionHelper.GetLatestNetDeveloperPackVersion() + ?? FrameworkVersionHelper.GetFrameworkReleaseVersion(); // .NET Developer Pack is not installed + return GetRuntimeFromVersion(version); } -#if NET6_0_OR_GREATER - [System.Runtime.Versioning.SupportedOSPlatform("windows")] -#endif - private static ClrRuntime RetrieveCurrentVersion() + internal static ClrRuntime GetTargetOrCurrentVersion(Assembly? assembly) { - // Try to determine the Framework version that the executable was compiled for. - string version = FrameworkVersionHelper.GetTargetFrameworkVersion() + if (!OsDetector.IsWindows()) + { + throw new PlatformNotSupportedException(".NET Framework supports Windows OS only."); + } + + // Try to determine the Framework version that the assembly was compiled for. + string? version = FrameworkVersionHelper.GetTargetFrameworkVersion(assembly); + return version != null + ? GetRuntimeFromVersion(version) // Fallback to the current running Framework version. - ?? FrameworkVersionHelper.GetLatestNetDeveloperPackVersion() - ?? FrameworkVersionHelper.GetFrameworkReleaseVersion(); // .NET Developer Pack is not installed + : GetCurrentVersion(); + } - return version switch + private static ClrRuntime GetRuntimeFromVersion(string version) + => version switch { "4.6.1" => Net461, "4.6.2" => Net462, @@ -83,6 +83,5 @@ private static ClrRuntime RetrieveCurrentVersion() // unlikely to happen but theoretically possible _ => new ClrRuntime(RuntimeMoniker.NotRecognized, $"net{version.Replace(".", null)}", $".NET Framework {version}"), }; - } } } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Helpers/FrameworkVersionHelper.cs b/src/BenchmarkDotNet/Helpers/FrameworkVersionHelper.cs index 1391bdd238..92732fe571 100644 --- a/src/BenchmarkDotNet/Helpers/FrameworkVersionHelper.cs +++ b/src/BenchmarkDotNet/Helpers/FrameworkVersionHelper.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; @@ -24,54 +22,25 @@ private static readonly (int minReleaseNumber, string version)[] FrameworkVersio (394254, "4.6.1") ]; - internal static string? GetTargetFrameworkVersion() + internal static string? GetTargetFrameworkVersion(Assembly? assembly) { - // Search assemblies until we find a TargetFrameworkAttribute with a supported Framework version. - // We don't search all assemblies, only the entry assembly and callers. - foreach (var assembly in EnumerateAssemblies()) + // Look for a TargetFrameworkAttribute with a supported Framework version. + foreach (var attribute in assembly.GetCustomAttributes()) { - foreach (var attribute in assembly.GetCustomAttributes()) + switch (attribute.FrameworkName) { - 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"; - } + 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"; } } + // TargetFrameworkAttribute not found, or the assembly targeted a version older than we support. return null; - - static IEnumerable EnumerateAssemblies() - { - var entryAssembly = Assembly.GetEntryAssembly(); - // Assembly.GetEntryAssembly() returns null in unit test frameworks. - if (entryAssembly != null) - { - yield return entryAssembly; - } - // Search calling assemblies starting from the highest stack frame - // (expected to be the entry assembly if Assembly.GetEntryAssembly() returned null), - // excluding this assembly. - var stacktrace = new StackTrace(false); - var searchedAssemblies = new HashSet() - { - stacktrace.GetFrame(0).GetMethod().ReflectedType.Assembly - }; - for (int i = stacktrace.FrameCount - 1; i >= 1 ; --i) - { - var assembly = stacktrace.GetFrame(i).GetMethod().ReflectedType.Assembly; - if (searchedAssemblies.Add(assembly)) - { - yield return assembly; - } - } - } } internal static string GetFrameworkDescription() diff --git a/src/BenchmarkDotNet/Portability/RuntimeInformation.cs b/src/BenchmarkDotNet/Portability/RuntimeInformation.cs index 9b8348a125..530c4dd8a4 100644 --- a/src/BenchmarkDotNet/Portability/RuntimeInformation.cs +++ b/src/BenchmarkDotNet/Portability/RuntimeInformation.cs @@ -182,6 +182,11 @@ string GetDetailedVersion() } } + internal static Runtime GetTargetOrCurrentRuntime(Assembly? assembly) + => !IsMono && !IsWasm && IsFullFramework // Match order of checks in GetCurrentRuntime(). + ? ClrRuntime.GetTargetOrCurrentVersion(assembly) + : GetCurrentRuntime(); + internal static Runtime GetCurrentRuntime() { //do not change the order of conditions because it may cause incorrect determination of runtime diff --git a/src/BenchmarkDotNet/Running/BenchmarkCase.cs b/src/BenchmarkDotNet/Running/BenchmarkCase.cs index b517ab5676..2a8f749a62 100644 --- a/src/BenchmarkDotNet/Running/BenchmarkCase.cs +++ b/src/BenchmarkDotNet/Running/BenchmarkCase.cs @@ -30,7 +30,7 @@ internal BenchmarkCase(Descriptor descriptor, Job job, ParameterInstances parame public Runtime GetRuntime() => Job.Environment.HasValue(EnvironmentMode.RuntimeCharacteristic) ? Job.Environment.Runtime - : RuntimeInformation.GetCurrentRuntime(); + : RuntimeInformation.GetTargetOrCurrentRuntime(Descriptor.WorkloadMethod.DeclaringType.Assembly); public void Dispose() => Parameters.Dispose(); diff --git a/src/BenchmarkDotNet/Running/BenchmarkPartitioner.cs b/src/BenchmarkDotNet/Running/BenchmarkPartitioner.cs index 4892de7f0c..20afe08cd4 100644 --- a/src/BenchmarkDotNet/Running/BenchmarkPartitioner.cs +++ b/src/BenchmarkDotNet/Running/BenchmarkPartitioner.cs @@ -2,9 +2,7 @@ using System.Collections.Generic; using System.Linq; using BenchmarkDotNet.Characteristics; -using BenchmarkDotNet.Environments; using BenchmarkDotNet.Jobs; -using BenchmarkDotNet.Portability; using BenchmarkDotNet.Toolchains; namespace BenchmarkDotNet.Running @@ -22,18 +20,16 @@ internal class BenchmarkRuntimePropertiesComparer : IEqualityComparer Instance = new BenchmarkRuntimePropertiesComparer(); - private static readonly Runtime Current = RuntimeInformation.GetCurrentRuntime(); - public bool Equals(BenchmarkCase x, BenchmarkCase y) { - if (x == null && y == null) + if (x == y) return true; if (x == null || y == null) return false; var jobX = x.Job; var jobY = y.Job; - if (AreDifferent(GetRuntime(jobX), GetRuntime(jobY))) // Mono vs .NET vs Core + if (AreDifferent(x.GetRuntime(), y.GetRuntime())) // Mono vs .NET vs Core return false; if (AreDifferent(x.GetToolchain(), y.GetToolchain())) // Mono vs .NET vs Core vs InProcess return false; @@ -90,20 +86,8 @@ public int GetHashCode(BenchmarkCase obj) return hashCode.ToHashCode(); } - private static Runtime GetRuntime(Job job) - => job.Environment.HasValue(EnvironmentMode.RuntimeCharacteristic) - ? job.Environment.Runtime - : Current; - private static bool AreDifferent(object x, object y) - { - if (x == null && y == null) - return false; - if (x == null || y == null) - return true; - - return !x.Equals(y); - } + => !Equals(x, y); private static bool AreDifferent(IReadOnlyList x, IReadOnlyList y) { diff --git a/src/BenchmarkDotNet/Toolchains/ToolchainExtensions.cs b/src/BenchmarkDotNet/Toolchains/ToolchainExtensions.cs index 2beab5018c..934b7eb93e 100644 --- a/src/BenchmarkDotNet/Toolchains/ToolchainExtensions.cs +++ b/src/BenchmarkDotNet/Toolchains/ToolchainExtensions.cs @@ -19,17 +19,23 @@ namespace BenchmarkDotNet.Toolchains { internal static class ToolchainExtensions { - internal static IToolchain GetToolchain(this BenchmarkCase benchmarkCase) => GetToolchain(benchmarkCase.Job, benchmarkCase.Descriptor); - - internal static IToolchain GetToolchain(this Job job) => GetToolchain(job, null); + internal static IToolchain GetToolchain(this BenchmarkCase benchmarkCase) + => benchmarkCase.Job.Infrastructure.TryGetToolchain(out var toolchain) + ? toolchain + : GetToolchain( + benchmarkCase.GetRuntime(), + benchmarkCase.Descriptor, + benchmarkCase.Job.HasDynamicBuildCharacteristic() + ); - private static IToolchain GetToolchain(Job job, Descriptor descriptor) + internal static IToolchain GetToolchain(this Job job) => job.Infrastructure.TryGetToolchain(out var toolchain) ? toolchain : GetToolchain( job.ResolveValue(EnvironmentMode.RuntimeCharacteristic, EnvironmentResolver.Instance), - descriptor, - job.HasDynamicBuildCharacteristic()); + null, + job.HasDynamicBuildCharacteristic() + ); internal static IToolchain GetToolchain(this Runtime runtime, Descriptor? descriptor = null, bool preferMsBuildToolchains = false) { From 300a918be497d64a8086fcb29fc70d82b5d70298 Mon Sep 17 00:00:00 2001 From: Tim Cassell Date: Sun, 9 Nov 2025 04:12:59 -0500 Subject: [PATCH 3/4] Fix MultipleFrameworksTest --- .../Helpers/FrameworkVersionHelper.cs | 5 +++++ .../Toolchains/ToolchainExtensions.cs | 20 ++++++++++++------- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/BenchmarkDotNet/Helpers/FrameworkVersionHelper.cs b/src/BenchmarkDotNet/Helpers/FrameworkVersionHelper.cs index 92732fe571..7c87f0c561 100644 --- a/src/BenchmarkDotNet/Helpers/FrameworkVersionHelper.cs +++ b/src/BenchmarkDotNet/Helpers/FrameworkVersionHelper.cs @@ -24,6 +24,11 @@ private static readonly (int minReleaseNumber, string version)[] FrameworkVersio internal static string? GetTargetFrameworkVersion(Assembly? assembly) { + if (assembly is null) + { + return null; + } + // Look for a TargetFrameworkAttribute with a supported Framework version. foreach (var attribute in assembly.GetCustomAttributes()) { diff --git a/src/BenchmarkDotNet/Toolchains/ToolchainExtensions.cs b/src/BenchmarkDotNet/Toolchains/ToolchainExtensions.cs index 934b7eb93e..acc6832e4f 100644 --- a/src/BenchmarkDotNet/Toolchains/ToolchainExtensions.cs +++ b/src/BenchmarkDotNet/Toolchains/ToolchainExtensions.cs @@ -2,7 +2,6 @@ using BenchmarkDotNet.Detectors; using BenchmarkDotNet.Environments; using BenchmarkDotNet.Extensions; -using BenchmarkDotNet.Helpers; using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Portability; using BenchmarkDotNet.Running; @@ -25,7 +24,8 @@ internal static IToolchain GetToolchain(this BenchmarkCase benchmarkCase) : GetToolchain( benchmarkCase.GetRuntime(), benchmarkCase.Descriptor, - benchmarkCase.Job.HasDynamicBuildCharacteristic() + benchmarkCase.Job.HasDynamicBuildCharacteristic(), + benchmarkCase.Job.Environment.HasValue(EnvironmentMode.RuntimeCharacteristic) ); internal static IToolchain GetToolchain(this Job job) @@ -34,20 +34,26 @@ internal static IToolchain GetToolchain(this Job job) : GetToolchain( job.ResolveValue(EnvironmentMode.RuntimeCharacteristic, EnvironmentResolver.Instance), null, - job.HasDynamicBuildCharacteristic() + job.HasDynamicBuildCharacteristic(), + job.Environment.HasValue(EnvironmentMode.RuntimeCharacteristic) ); - internal static IToolchain GetToolchain(this Runtime runtime, Descriptor? descriptor = null, bool preferMsBuildToolchains = false) + internal static IToolchain GetToolchain(this Runtime runtime, Descriptor? descriptor = null, bool preferMsBuildToolchains = false, bool isRuntimeExplicit = false) { switch (runtime) { case ClrRuntime clrRuntime: - if (!preferMsBuildToolchains && RuntimeInformation.IsFullFramework - && RuntimeInformation.GetCurrentRuntime().MsBuildMoniker == runtime.MsBuildMoniker) + bool ShouldUseCurrentRuntime() { - return RoslynToolchain.Instance; + var assembly = isRuntimeExplicit + ? descriptor?.WorkloadMethod.DeclaringType.Assembly + : null; + return runtime.MsBuildMoniker == ClrRuntime.GetTargetOrCurrentVersion(assembly).MsBuildMoniker; } + if (!preferMsBuildToolchains && RuntimeInformation.IsFullFramework && ShouldUseCurrentRuntime()) + return RoslynToolchain.Instance; + return clrRuntime.RuntimeMoniker != RuntimeMoniker.NotRecognized ? GetToolchain(clrRuntime.RuntimeMoniker) : CsProjClassicNetToolchain.From(clrRuntime.MsBuildMoniker); From abb38201a06ab9842bcf4ddbe21deeea70752d5b Mon Sep 17 00:00:00 2001 From: Tim Cassell Date: Sun, 9 Nov 2025 05:26:07 -0500 Subject: [PATCH 4/4] Use RoslynToolchain if the runtime was not explicitly set. --- .../Toolchains/ToolchainExtensions.cs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/BenchmarkDotNet/Toolchains/ToolchainExtensions.cs b/src/BenchmarkDotNet/Toolchains/ToolchainExtensions.cs index acc6832e4f..89918679a9 100644 --- a/src/BenchmarkDotNet/Toolchains/ToolchainExtensions.cs +++ b/src/BenchmarkDotNet/Toolchains/ToolchainExtensions.cs @@ -43,15 +43,11 @@ internal static IToolchain GetToolchain(this Runtime runtime, Descriptor? descri switch (runtime) { case ClrRuntime clrRuntime: - bool ShouldUseCurrentRuntime() - { - var assembly = isRuntimeExplicit - ? descriptor?.WorkloadMethod.DeclaringType.Assembly - : null; - return runtime.MsBuildMoniker == ClrRuntime.GetTargetOrCurrentVersion(assembly).MsBuildMoniker; - } - - if (!preferMsBuildToolchains && RuntimeInformation.IsFullFramework && ShouldUseCurrentRuntime()) + bool UseRoslyn() + => !isRuntimeExplicit + || runtime.MsBuildMoniker == ClrRuntime.GetTargetOrCurrentVersion(descriptor?.WorkloadMethod.DeclaringType.Assembly).MsBuildMoniker; + + if (!preferMsBuildToolchains && RuntimeInformation.IsFullFramework && UseRoslyn()) return RoslynToolchain.Instance; return clrRuntime.RuntimeMoniker != RuntimeMoniker.NotRecognized