diff --git a/src/BenchmarkDotNet/Extensions/RuntimeMonikerExtensions.cs b/src/BenchmarkDotNet/Extensions/RuntimeMonikerExtensions.cs index 49c26320b3..e3fbdb04e0 100644 --- a/src/BenchmarkDotNet/Extensions/RuntimeMonikerExtensions.cs +++ b/src/BenchmarkDotNet/Extensions/RuntimeMonikerExtensions.cs @@ -75,5 +75,51 @@ internal static Runtime GetRuntime(this RuntimeMoniker runtimeMoniker) throw new ArgumentOutOfRangeException(nameof(runtimeMoniker), runtimeMoniker, "Runtime Moniker not supported"); } } + + internal static Version GetRuntimeVersion(this RuntimeMoniker runtimeMoniker) => runtimeMoniker switch + { + RuntimeMoniker.Net461 => new Version(4, 6, 1), + RuntimeMoniker.Net462 => new Version(4, 6, 2), + RuntimeMoniker.Net47 => new Version(4, 7), + RuntimeMoniker.Net471 => new Version(4, 7, 1), + RuntimeMoniker.Net472 => new Version(4, 7, 2), + RuntimeMoniker.Net48 => new Version(4, 8), + RuntimeMoniker.Net481 => new Version(4, 8, 1), + RuntimeMoniker.NetCoreApp20 => new Version(2, 0), + RuntimeMoniker.NetCoreApp21 => new Version(2, 1), + RuntimeMoniker.NetCoreApp22 => new Version(2, 2), + RuntimeMoniker.NetCoreApp30 => new Version(3, 0), + RuntimeMoniker.NetCoreApp31 => new Version(3, 1), + RuntimeMoniker.Net50 => new Version(5, 0), + RuntimeMoniker.Net60 => new Version(6, 0), + RuntimeMoniker.Net70 => new Version(7, 0), + RuntimeMoniker.Net80 => new Version(8, 0), + RuntimeMoniker.Net90 => new Version(9, 0), + RuntimeMoniker.Net10_0 => new Version(10, 0), + RuntimeMoniker.NativeAot60 => new Version(6, 0), + RuntimeMoniker.NativeAot70 => new Version(7, 0), + RuntimeMoniker.NativeAot80 => new Version(8, 0), + RuntimeMoniker.NativeAot90 => new Version(9, 0), + RuntimeMoniker.NativeAot10_0 => new Version(10, 0), + RuntimeMoniker.Mono60 => new Version(6, 0), + RuntimeMoniker.Mono70 => new Version(7, 0), + RuntimeMoniker.Mono80 => new Version(8, 0), + RuntimeMoniker.Mono90 => new Version(9, 0), + RuntimeMoniker.Mono10_0 => new Version(10, 0), + RuntimeMoniker.Wasm => Portability.RuntimeInformation.IsNetCore && CoreRuntime.TryGetVersion(out var version) ? version : new Version(5, 0), + RuntimeMoniker.WasmNet50 => new Version(5, 0), + RuntimeMoniker.WasmNet60 => new Version(6, 0), + RuntimeMoniker.WasmNet70 => new Version(7, 0), + RuntimeMoniker.WasmNet80 => new Version(8, 0), + RuntimeMoniker.WasmNet90 => new Version(9, 0), + RuntimeMoniker.WasmNet10_0 => new Version(10, 0), + RuntimeMoniker.MonoAOTLLVM => Portability.RuntimeInformation.IsNetCore && CoreRuntime.TryGetVersion(out var version) ? version : new Version(6, 0), + RuntimeMoniker.MonoAOTLLVMNet60 => new Version(6, 0), + RuntimeMoniker.MonoAOTLLVMNet70 => new Version(7, 0), + RuntimeMoniker.MonoAOTLLVMNet80 => new Version(8, 0), + RuntimeMoniker.MonoAOTLLVMNet90 => new Version(9, 0), + RuntimeMoniker.MonoAOTLLVMNet10_0 => new Version(10, 0), + _ => throw new NotImplementedException($"{nameof(GetRuntimeVersion)} not implemented for {runtimeMoniker}") + }; } } diff --git a/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs b/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs index 4ff49156b8..810e79ab8a 100644 --- a/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs +++ b/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs @@ -21,6 +21,7 @@ using BenchmarkDotNet.Mathematics; using BenchmarkDotNet.Reports; using BenchmarkDotNet.Toolchains; +using BenchmarkDotNet.Toolchains.DotNetCli; using BenchmarkDotNet.Toolchains.Parameters; using BenchmarkDotNet.Toolchains.Results; using BenchmarkDotNet.Validators; @@ -81,17 +82,21 @@ internal static Summary[] Run(BenchmarkRunInfo[] benchmarkRunInfos) compositeLogger.WriteLineHeader($"// ***** Found {totalBenchmarkCount} benchmark(s) in total *****"); var globalChronometer = Chronometer.Start(); + var buildPartitions = BenchmarkPartitioner.CreateForBuild(supportedBenchmarks, resolver); + eventProcessor.OnStartBuildStage(buildPartitions); - var parallelBuildBenchmarks = supportedBenchmarks.Where(x => !x.Config.Options.IsSet(ConfigOptions.DisableParallelBuild)).ToArray(); - var parallelBuildPartitions = BenchmarkPartitioner.CreateForBuild(parallelBuildBenchmarks, resolver); - - var sequentialBuildBenchmarks = supportedBenchmarks.Where(x => x.Config.Options.IsSet(ConfigOptions.DisableParallelBuild)).ToArray(); - var sequentialBuildPartitions = BenchmarkPartitioner.CreateForBuild(sequentialBuildBenchmarks, resolver); - - eventProcessor.OnStartBuildStage([.. parallelBuildPartitions, .. sequentialBuildPartitions]); + var sequentialBuildPartitions = buildPartitions.Where(partition => + partition.Benchmarks.Any(x => x.Config.Options.IsSet(ConfigOptions.DisableParallelBuild)) + // .Net SDK 8+ supports ArtifactsPath for proper parallel builds. + // Older SDKs may produce builds with incorrect bindings if more than 1 partition is built in parallel. + || (partition.RepresentativeBenchmarkCase.GetToolchain().Generator is DotNetCliGenerator + && partition.RepresentativeBenchmarkCase.GetRuntime().RuntimeMoniker.GetRuntimeVersion().Major < 8) + ) + .ToArray(); + var parallelBuildPartitions = buildPartitions.Except(sequentialBuildPartitions).ToArray(); var buildResults = new Dictionary(); - if (parallelBuildBenchmarks.Length > 0) + if (parallelBuildPartitions.Length > 0) { var results = BuildInParallel(compositeLogger, rootArtifactsFolderPath, parallelBuildPartitions, in globalChronometer, eventProcessor); foreach (var kvp in results) @@ -100,7 +105,7 @@ internal static Summary[] Run(BenchmarkRunInfo[] benchmarkRunInfos) } } - if (sequentialBuildBenchmarks.Length > 0) + if (sequentialBuildPartitions.Length > 0) { var results = BuildSequential(compositeLogger, rootArtifactsFolderPath, sequentialBuildPartitions, in globalChronometer, eventProcessor); foreach (var kvp in results) @@ -644,7 +649,7 @@ private static (BenchmarkRunInfo[], List) GetSupportedBenchmark validationErrors.AddRange(errors); - return errors.Length == 0; + return !errors.Any(error => error.IsCritical); }) .ToArray(); diff --git a/src/BenchmarkDotNet/Templates/WasmCsProj.txt b/src/BenchmarkDotNet/Templates/WasmCsProj.txt index de6dc3ee5c..3db39f5669 100644 --- a/src/BenchmarkDotNet/Templates/WasmCsProj.txt +++ b/src/BenchmarkDotNet/Templates/WasmCsProj.txt @@ -15,7 +15,7 @@ false $TFM$ true - $(MSBuildThisFileDirectory)\bin\$TFM$\browser-wasm\publish + $(PublishDir) $PROGRAMNAME$ browser-wasm true @@ -50,8 +50,8 @@ - - + + diff --git a/src/BenchmarkDotNet/Toolchains/ArtifactsPaths.cs b/src/BenchmarkDotNet/Toolchains/ArtifactsPaths.cs index 0c097f5bb0..a9467494fa 100644 --- a/src/BenchmarkDotNet/Toolchains/ArtifactsPaths.cs +++ b/src/BenchmarkDotNet/Toolchains/ArtifactsPaths.cs @@ -9,7 +9,7 @@ public class ArtifactsPaths [PublicAPI] public string RootArtifactsFolderPath { get; } [PublicAPI] public string BuildArtifactsDirectoryPath { get; } [PublicAPI] public string BinariesDirectoryPath { get; } - [PublicAPI] public string IntermediateDirectoryPath { get; } + [PublicAPI] public string PublishDirectoryPath { get; } [PublicAPI] public string ProgramCodePath { get; } [PublicAPI] public string AppConfigPath { get; } [PublicAPI] public string NuGetConfigPath { get; } @@ -23,7 +23,7 @@ public ArtifactsPaths( string rootArtifactsFolderPath, string buildArtifactsDirectoryPath, string binariesDirectoryPath, - string intermediateDirectoryPath, + string publishDirectoryPath, string programCodePath, string appConfigPath, string nuGetConfigPath, @@ -36,7 +36,7 @@ public ArtifactsPaths( RootArtifactsFolderPath = rootArtifactsFolderPath; BuildArtifactsDirectoryPath = buildArtifactsDirectoryPath; BinariesDirectoryPath = binariesDirectoryPath; - IntermediateDirectoryPath = intermediateDirectoryPath; + PublishDirectoryPath = publishDirectoryPath; ProgramCodePath = programCodePath; AppConfigPath = appConfigPath; NuGetConfigPath = nuGetConfigPath; diff --git a/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs b/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs index c324f66b88..7b5a642744 100644 --- a/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs +++ b/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs @@ -64,9 +64,6 @@ protected override string GetProjectFilePath(string buildArtifactsDirectoryPath) protected override string GetBinariesDirectoryPath(string buildArtifactsDirectoryPath, string configuration) => Path.Combine(buildArtifactsDirectoryPath, "bin", configuration, TargetFrameworkMoniker); - protected override string GetIntermediateDirectoryPath(string buildArtifactsDirectoryPath, string configuration) - => Path.Combine(buildArtifactsDirectoryPath, "obj", configuration, TargetFrameworkMoniker); - [SuppressMessage("ReSharper", "StringLiteralTypo")] // R# complains about $variables$ protected override void GenerateProject(BuildPartition buildPartition, ArtifactsPaths artifactsPaths, ILogger logger) { diff --git a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommand.cs b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommand.cs index e9078f293f..81f48b4413 100644 --- a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommand.cs +++ b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommand.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using BenchmarkDotNet.Characteristics; +using BenchmarkDotNet.Environments; using BenchmarkDotNet.Extensions; using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Loggers; @@ -262,10 +263,11 @@ internal static StringBuilder MaybeAppendOutputPaths(this StringBuilder stringBu ? stringBuilder : stringBuilder // Use AltDirectorySeparatorChar so it's not interpreted as an escaped quote `\"`. - .AppendArgument($"/p:IntermediateOutputPath=\"{artifactsPaths.IntermediateDirectoryPath}{Path.AltDirectorySeparatorChar}\"") + .AppendArgument($"/p:ArtifactsPath=\"{artifactsPaths.BuildArtifactsDirectoryPath}{Path.AltDirectorySeparatorChar}\"") .AppendArgument($"/p:OutDir=\"{artifactsPaths.BinariesDirectoryPath}{Path.AltDirectorySeparatorChar}\"") // OutputPath is legacy, per-project version of OutDir. We set both just in case. https://github.com/dotnet/msbuild/issues/87 .AppendArgument($"/p:OutputPath=\"{artifactsPaths.BinariesDirectoryPath}{Path.AltDirectorySeparatorChar}\"") + .AppendArgument($"/p:PublishDir=\"{artifactsPaths.PublishDirectoryPath}{Path.AltDirectorySeparatorChar}\"") .AppendArgument(isRestore ? string.Empty : $"--output \"{artifactsPaths.BinariesDirectoryPath}{Path.AltDirectorySeparatorChar}\""); } } diff --git a/src/BenchmarkDotNet/Toolchains/GeneratorBase.cs b/src/BenchmarkDotNet/Toolchains/GeneratorBase.cs index 020788cc56..165dedcbba 100644 --- a/src/BenchmarkDotNet/Toolchains/GeneratorBase.cs +++ b/src/BenchmarkDotNet/Toolchains/GeneratorBase.cs @@ -51,11 +51,11 @@ [PublicAPI] protected virtual string GetBinariesDirectoryPath(string buildArtifa => buildArtifactsDirectoryPath; /// - /// returns a path where intermediate files should be found after the build (usually \obj) + /// returns a path where the publish directory should be found after the build (usually \publish) /// [PublicAPI] - protected virtual string GetIntermediateDirectoryPath(string buildArtifactsDirectoryPath, string configuration) - => string.Empty; + protected virtual string GetPublishDirectoryPath(string buildArtifactsDirectoryPath, string configuration) + => Path.Combine(buildArtifactsDirectoryPath, "publish"); /// /// returns OS-specific executable extension @@ -138,7 +138,7 @@ private ArtifactsPaths GetArtifactsPaths(BuildPartition buildPartition, string r rootArtifactsFolderPath: rootArtifactsFolderPath, buildArtifactsDirectoryPath: buildArtifactsDirectoryPath, binariesDirectoryPath: binariesDirectoryPath, - intermediateDirectoryPath: GetIntermediateDirectoryPath(buildArtifactsDirectoryPath, buildPartition.BuildConfiguration), + publishDirectoryPath: GetPublishDirectoryPath(buildArtifactsDirectoryPath, buildPartition.BuildConfiguration), programCodePath: Path.Combine(buildArtifactsDirectoryPath, $"{programName}{codeFileExtension}"), appConfigPath: $"{executablePath}.config", nuGetConfigPath: Path.Combine(buildArtifactsDirectoryPath, "NuGet.config"), diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/InProcessEmitArtifactsPath.cs b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/InProcessEmitArtifactsPath.cs index 0686a322ac..ca8ca60974 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/InProcessEmitArtifactsPath.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/InProcessEmitArtifactsPath.cs @@ -12,7 +12,7 @@ public InProcessEmitArtifactsPath( baseArtifacts.RootArtifactsFolderPath, baseArtifacts.BuildArtifactsDirectoryPath, baseArtifacts.BinariesDirectoryPath, - baseArtifacts.IntermediateDirectoryPath, + baseArtifacts.PublishDirectoryPath, baseArtifacts.ProgramCodePath, baseArtifacts.AppConfigPath, baseArtifacts.NuGetConfigPath, diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/InProcessEmitGenerator.cs b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/InProcessEmitGenerator.cs index 18734fcf46..5e74d65f12 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/InProcessEmitGenerator.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/InProcessEmitGenerator.cs @@ -47,7 +47,7 @@ private ArtifactsPaths GetArtifactsPaths(BuildPartition buildPartition, string r rootArtifactsFolderPath: rootArtifactsFolderPath, buildArtifactsDirectoryPath: buildArtifactsDirectoryPath, binariesDirectoryPath: binariesDirectoryPath, - intermediateDirectoryPath: null, + publishDirectoryPath: null, programCodePath: null, appConfigPath: null, nuGetConfigPath: null, diff --git a/src/BenchmarkDotNet/Toolchains/MonoAotLLVM/MonoAotLLVMGenerator.cs b/src/BenchmarkDotNet/Toolchains/MonoAotLLVM/MonoAotLLVMGenerator.cs index 0816ac48f0..6ba5968617 100644 --- a/src/BenchmarkDotNet/Toolchains/MonoAotLLVM/MonoAotLLVMGenerator.cs +++ b/src/BenchmarkDotNet/Toolchains/MonoAotLLVM/MonoAotLLVMGenerator.cs @@ -53,6 +53,9 @@ protected override void GenerateProject(BuildPartition buildPartition, Artifacts File.WriteAllText(artifactsPaths.ProjectFilePath, content); } + protected override string GetPublishDirectoryPath(string buildArtifactsDirectoryPath, string configuration) + => Path.Combine(GetBinariesDirectoryPath(buildArtifactsDirectoryPath, configuration), "publish"); + protected override string GetExecutablePath(string binariesDirectoryPath, string programName) => OsDetector.IsWindows() ? Path.Combine(binariesDirectoryPath, "publish", $"{programName}.exe") diff --git a/src/BenchmarkDotNet/Validators/DotNetSdkVersionValidator.cs b/src/BenchmarkDotNet/Validators/DotNetSdkValidator.cs similarity index 62% rename from src/BenchmarkDotNet/Validators/DotNetSdkVersionValidator.cs rename to src/BenchmarkDotNet/Validators/DotNetSdkValidator.cs index 17331b49f8..66b6a35aca 100644 --- a/src/BenchmarkDotNet/Validators/DotNetSdkVersionValidator.cs +++ b/src/BenchmarkDotNet/Validators/DotNetSdkValidator.cs @@ -1,4 +1,5 @@ using BenchmarkDotNet.Environments; +using BenchmarkDotNet.Extensions; using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Running; using System; @@ -20,26 +21,19 @@ public static IEnumerable ValidateCoreSdks(string? customDotNet if (IsCliPathInvalid(customDotNetCliPath, benchmark, out ValidationError? cliPathError)) { yield return cliPathError; + yield break; } - else if (TryGetSdkVersion(benchmark, out Version requiredSdkVersion)) + var requiredSdkVersion = benchmark.GetRuntime().RuntimeMoniker.GetRuntimeVersion(); + if (!GetInstalledDotNetSdks(customDotNetCliPath).Any(sdk => sdk >= requiredSdkVersion)) { - var installedSdks = GetInstalledDotNetSdks(customDotNetCliPath); - if (!installedSdks.Any(sdk => sdk >= requiredSdkVersion)) - { - yield return new ValidationError(true, $"The required .NET Core SDK version {requiredSdkVersion} or higher for runtime moniker {benchmark.Job.Environment.Runtime.RuntimeMoniker} is not installed.", benchmark); - } + yield return new ValidationError(true, $"The required .NET Core SDK version {requiredSdkVersion} or higher for runtime moniker {benchmark.Job.Environment.Runtime.RuntimeMoniker} is not installed.", benchmark); } } public static IEnumerable ValidateFrameworkSdks(BenchmarkCase benchmark) { - if (!TryGetSdkVersion(benchmark, out Version requiredSdkVersion)) - { - yield break; - } - + var requiredSdkVersion = benchmark.GetRuntime().RuntimeMoniker.GetRuntimeVersion(); var installedVersionString = cachedFrameworkSdks.Value.FirstOrDefault(); - if (installedVersionString == null || Version.TryParse(installedVersionString, out var installedVersion) && installedVersion < requiredSdkVersion) { yield return new ValidationError(true, $"The required .NET Framework SDK version {requiredSdkVersion} or higher is not installed.", benchmark); @@ -71,17 +65,6 @@ public static bool IsCliPathInvalid(string customDotNetCliPath, BenchmarkCase be return false; } - private static bool TryGetSdkVersion(BenchmarkCase benchmark, out Version sdkVersion) - { - sdkVersion = default; - if (benchmark?.Job?.Environment?.Runtime?.RuntimeMoniker != null) - { - sdkVersion = GetSdkVersionFromMoniker(benchmark.Job.Environment.Runtime.RuntimeMoniker); - return true; - } - return false; - } - private static IEnumerable GetInstalledDotNetSdks(string? customDotNetCliPath) { string dotnetExecutable = string.IsNullOrEmpty(customDotNetCliPath) ? "dotnet" : customDotNetCliPath; @@ -198,54 +181,5 @@ private static string CheckFor45PlusVersion(int releaseKey) return ""; } - - private static Version GetSdkVersionFromMoniker(RuntimeMoniker runtimeMoniker) - { - return runtimeMoniker switch - { - RuntimeMoniker.Net461 => new Version(4, 6, 1), - RuntimeMoniker.Net462 => new Version(4, 6, 2), - RuntimeMoniker.Net47 => new Version(4, 7), - RuntimeMoniker.Net471 => new Version(4, 7, 1), - RuntimeMoniker.Net472 => new Version(4, 7, 2), - RuntimeMoniker.Net48 => new Version(4, 8), - RuntimeMoniker.Net481 => new Version(4, 8, 1), - RuntimeMoniker.NetCoreApp20 => new Version(2, 0), - RuntimeMoniker.NetCoreApp21 => new Version(2, 1), - RuntimeMoniker.NetCoreApp22 => new Version(2, 2), - RuntimeMoniker.NetCoreApp30 => new Version(3, 0), - RuntimeMoniker.NetCoreApp31 => new Version(3, 1), - RuntimeMoniker.Net50 => new Version(5, 0), - RuntimeMoniker.Net60 => new Version(6, 0), - RuntimeMoniker.Net70 => new Version(7, 0), - RuntimeMoniker.Net80 => new Version(8, 0), - RuntimeMoniker.Net90 => new Version(9, 0), - RuntimeMoniker.Net10_0 => new Version(10, 0), - RuntimeMoniker.NativeAot60 => new Version(6, 0), - RuntimeMoniker.NativeAot70 => new Version(7, 0), - RuntimeMoniker.NativeAot80 => new Version(8, 0), - RuntimeMoniker.NativeAot90 => new Version(9, 0), - RuntimeMoniker.NativeAot10_0 => new Version(10, 0), - RuntimeMoniker.Mono60 => new Version(6, 0), - RuntimeMoniker.Mono70 => new Version(7, 0), - RuntimeMoniker.Mono80 => new Version(8, 0), - RuntimeMoniker.Mono90 => new Version(9, 0), - RuntimeMoniker.Mono10_0 => new Version(10, 0), - RuntimeMoniker.Wasm => Portability.RuntimeInformation.IsNetCore && CoreRuntime.TryGetVersion(out var version) ? version : new Version(5, 0), - RuntimeMoniker.WasmNet50 => new Version(5, 0), - RuntimeMoniker.WasmNet60 => new Version(6, 0), - RuntimeMoniker.WasmNet70 => new Version(7, 0), - RuntimeMoniker.WasmNet80 => new Version(8, 0), - RuntimeMoniker.WasmNet90 => new Version(9, 0), - RuntimeMoniker.WasmNet10_0 => new Version(10, 0), - RuntimeMoniker.MonoAOTLLVM => Portability.RuntimeInformation.IsNetCore && CoreRuntime.TryGetVersion(out var version) ? version : new Version(6, 0), - RuntimeMoniker.MonoAOTLLVMNet60 => new Version(6, 0), - RuntimeMoniker.MonoAOTLLVMNet70 => new Version(7, 0), - RuntimeMoniker.MonoAOTLLVMNet80 => new Version(8, 0), - RuntimeMoniker.MonoAOTLLVMNet90 => new Version(9, 0), - RuntimeMoniker.MonoAOTLLVMNet10_0 => new Version(10, 0), - _ => throw new NotImplementedException($"SDK version check not implemented for {runtimeMoniker}") - }; - } } } \ No newline at end of file