From 293ed5149c6079a42eaf73469c1ce649a7057187 Mon Sep 17 00:00:00 2001 From: Tim Date: Mon, 15 Apr 2024 18:14:32 -0400 Subject: [PATCH 1/6] Use ArtifactsPath in sdk 8+ instead of IntermediateOutputPath. Pass PublishDir property to dotnet. --- src/BenchmarkDotNet/Templates/WasmCsProj.txt | 6 +-- .../Toolchains/ArtifactsPaths.cs | 5 +- .../Toolchains/DotNetCli/DotNetCliCommand.cs | 46 +++++++++++++------ .../DotNetCli/DotNetCliCommandExecutor.cs | 20 +++++++- .../DotNetCli/DotNetCliGenerator.cs | 9 +++- .../Toolchains/GeneratorBase.cs | 8 ++++ .../Emit/InProcessEmitArtifactsPath.cs | 1 + .../InProcess/Emit/InProcessEmitGenerator.cs | 1 + .../MonoAotLLVM/MonoAotLLVMGenerator.cs | 3 ++ .../Toolchains/NativeAot/Generator.cs | 4 +- 10 files changed, 78 insertions(+), 25 deletions(-) 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..f72efc207b 100644 --- a/src/BenchmarkDotNet/Toolchains/ArtifactsPaths.cs +++ b/src/BenchmarkDotNet/Toolchains/ArtifactsPaths.cs @@ -4,10 +4,11 @@ namespace BenchmarkDotNet.Toolchains { public class ArtifactsPaths { - public static readonly ArtifactsPaths Empty = new ArtifactsPaths("", "", "", "", "", "", "", "", "", "", "", ""); + public static readonly ArtifactsPaths Empty = new ArtifactsPaths("", "", "", "", "", "", "", "", "", "", "", "", ""); [PublicAPI] public string RootArtifactsFolderPath { get; } [PublicAPI] public string BuildArtifactsDirectoryPath { get; } + [PublicAPI] public string PublishDirectoryPath { get; } [PublicAPI] public string BinariesDirectoryPath { get; } [PublicAPI] public string IntermediateDirectoryPath { get; } [PublicAPI] public string ProgramCodePath { get; } @@ -22,6 +23,7 @@ public class ArtifactsPaths public ArtifactsPaths( string rootArtifactsFolderPath, string buildArtifactsDirectoryPath, + string publishDirectoryPath, string binariesDirectoryPath, string intermediateDirectoryPath, string programCodePath, @@ -35,6 +37,7 @@ public ArtifactsPaths( { RootArtifactsFolderPath = rootArtifactsFolderPath; BuildArtifactsDirectoryPath = buildArtifactsDirectoryPath; + PublishDirectoryPath = publishDirectoryPath; BinariesDirectoryPath = binariesDirectoryPath; IntermediateDirectoryPath = intermediateDirectoryPath; ProgramCodePath = programCodePath; diff --git a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommand.cs b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommand.cs index e9078f293f..3ab4f37932 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; @@ -32,6 +33,9 @@ public class DotNetCliCommand [PublicAPI] public bool LogOutput { get; } + // Whether to use ArtifactsPath or IntermediateOutputPath. ArtifactsPath is only supported in dotnet sdk 8+. + private readonly bool _useArtifactsPath; + public DotNetCliCommand(string cliPath, string arguments, GenerateResult generateResult, ILogger logger, BuildPartition buildPartition, IReadOnlyList environmentVariables, TimeSpan timeout, bool logOutput = false) { @@ -43,6 +47,8 @@ public DotNetCliCommand(string cliPath, string arguments, GenerateResult generat EnvironmentVariables = environmentVariables; Timeout = timeout; LogOutput = logOutput || (buildPartition is not null && buildPartition.LogBuildOutput); + + _useArtifactsPath = DotNetCliCommandExecutor.DotNetSdkSupportsArtifactsPath(cliPath); } public DotNetCliCommand WithArguments(string arguments) @@ -71,12 +77,12 @@ public BuildResult RestoreThenBuild() if (BuildPartition.ForcedNoDependenciesForIntegrationTests) { var restoreResult = DotNetCliCommandExecutor.Execute(WithArguments( - GetRestoreCommand(GenerateResult.ArtifactsPaths, BuildPartition, $"{Arguments} --no-dependencies", "restore-no-deps", excludeOutput: true))); + GetRestoreCommand(GenerateResult.ArtifactsPaths, BuildPartition, _useArtifactsPath, $"{Arguments} --no-dependencies", "restore-no-deps", excludeOutput: true))); if (!restoreResult.IsSuccess) return BuildResult.Failure(GenerateResult, restoreResult.AllInformation); return DotNetCliCommandExecutor.Execute(WithArguments( - GetBuildCommand(GenerateResult.ArtifactsPaths, BuildPartition, $"{Arguments} --no-restore --no-dependencies", "build-no-restore-no-deps", excludeOutput: true))) + GetBuildCommand(GenerateResult.ArtifactsPaths, BuildPartition, _useArtifactsPath, $"{Arguments} --no-restore --no-dependencies", "build-no-restore-no-deps", excludeOutput: true))) .ToBuildResult(GenerateResult); } else @@ -130,29 +136,30 @@ public DotNetCliCommandResult AddPackages() public DotNetCliCommandResult Restore() => DotNetCliCommandExecutor.Execute(WithArguments( - GetRestoreCommand(GenerateResult.ArtifactsPaths, BuildPartition, Arguments, "restore"))); + GetRestoreCommand(GenerateResult.ArtifactsPaths, BuildPartition, _useArtifactsPath, Arguments, "restore"))); public DotNetCliCommandResult Build() => DotNetCliCommandExecutor.Execute(WithArguments( - GetBuildCommand(GenerateResult.ArtifactsPaths, BuildPartition, Arguments, "build"))); + GetBuildCommand(GenerateResult.ArtifactsPaths, BuildPartition, _useArtifactsPath, Arguments, "build"))); public DotNetCliCommandResult BuildNoRestore() => DotNetCliCommandExecutor.Execute(WithArguments( - GetBuildCommand(GenerateResult.ArtifactsPaths, BuildPartition, $"{Arguments} --no-restore", "build-no-restore"))); + GetBuildCommand(GenerateResult.ArtifactsPaths, BuildPartition, _useArtifactsPath, $"{Arguments} --no-restore", "build-no-restore"))); public DotNetCliCommandResult Publish() => DotNetCliCommandExecutor.Execute(WithArguments( - GetPublishCommand(GenerateResult.ArtifactsPaths, BuildPartition, Arguments, "publish"))); + GetPublishCommand(GenerateResult.ArtifactsPaths, BuildPartition, _useArtifactsPath, Arguments, "publish"))); // PublishNoBuildAndNoRestore was removed because we set --output in the build step. We use the implicit build included in the publish command. public DotNetCliCommandResult PublishNoRestore() => DotNetCliCommandExecutor.Execute(WithArguments( - GetPublishCommand(GenerateResult.ArtifactsPaths, BuildPartition, $"{Arguments} --no-restore", "publish-no-restore"))); + GetPublishCommand(GenerateResult.ArtifactsPaths, BuildPartition, _useArtifactsPath, $"{Arguments} --no-restore", "publish-no-restore"))); internal static IEnumerable GetAddPackagesCommands(BuildPartition buildPartition) => GetNuGetAddPackageCommands(buildPartition.RepresentativeBenchmarkCase, buildPartition.Resolver); - internal static string GetRestoreCommand(ArtifactsPaths artifactsPaths, BuildPartition buildPartition, string? extraArguments = null, string? binLogSuffix = null, bool excludeOutput = false) + internal static string GetRestoreCommand(ArtifactsPaths artifactsPaths, BuildPartition buildPartition, + bool useArtifactsPath, string? extraArguments = null, string? binLogSuffix = null, bool excludeOutput = false) => new StringBuilder() .AppendArgument("restore") .AppendArgument(string.IsNullOrEmpty(artifactsPaths.PackagesDirectoryName) ? string.Empty : $"--packages \"{artifactsPaths.PackagesDirectoryName}\"") @@ -160,10 +167,11 @@ internal static string GetRestoreCommand(ArtifactsPaths artifactsPaths, BuildPar .AppendArgument(extraArguments) .AppendArgument(GetMandatoryMsBuildSettings(buildPartition.BuildConfiguration)) .AppendArgument(GetMsBuildBinLogArgument(buildPartition, binLogSuffix)) - .MaybeAppendOutputPaths(artifactsPaths, true, excludeOutput) + .MaybeAppendOutputPaths(artifactsPaths, useArtifactsPath, true, excludeOutput) .ToString(); - internal static string GetBuildCommand(ArtifactsPaths artifactsPaths, BuildPartition buildPartition, string? extraArguments = null, string? binLogSuffix = null, bool excludeOutput = false) + internal static string GetBuildCommand(ArtifactsPaths artifactsPaths, BuildPartition buildPartition, + bool useArtifactsPath, string? extraArguments = null, string? binLogSuffix = null, bool excludeOutput = false) => new StringBuilder() .AppendArgument($"build -c {buildPartition.BuildConfiguration}") // we don't need to specify TFM, our auto-generated project contains always single one .AppendArgument(GetCustomMsBuildArguments(buildPartition.RepresentativeBenchmarkCase, buildPartition.Resolver)) @@ -171,10 +179,11 @@ internal static string GetBuildCommand(ArtifactsPaths artifactsPaths, BuildParti .AppendArgument(GetMandatoryMsBuildSettings(buildPartition.BuildConfiguration)) .AppendArgument(string.IsNullOrEmpty(artifactsPaths.PackagesDirectoryName) ? string.Empty : $"/p:NuGetPackageRoot=\"{artifactsPaths.PackagesDirectoryName}\"") .AppendArgument(GetMsBuildBinLogArgument(buildPartition, binLogSuffix)) - .MaybeAppendOutputPaths(artifactsPaths, excludeOutput: excludeOutput) + .MaybeAppendOutputPaths(artifactsPaths, useArtifactsPath, excludeOutput: excludeOutput) .ToString(); - internal static string GetPublishCommand(ArtifactsPaths artifactsPaths, BuildPartition buildPartition, string? extraArguments = null, string? binLogSuffix = null) + internal static string GetPublishCommand(ArtifactsPaths artifactsPaths, BuildPartition buildPartition, + bool useArtifactsPath, string? extraArguments = null, string? binLogSuffix = null) => new StringBuilder() .AppendArgument($"publish -c {buildPartition.BuildConfiguration}") // we don't need to specify TFM, our auto-generated project contains always single one .AppendArgument(GetCustomMsBuildArguments(buildPartition.RepresentativeBenchmarkCase, buildPartition.Resolver)) @@ -182,7 +191,7 @@ internal static string GetPublishCommand(ArtifactsPaths artifactsPaths, BuildPar .AppendArgument(GetMandatoryMsBuildSettings(buildPartition.BuildConfiguration)) .AppendArgument(string.IsNullOrEmpty(artifactsPaths.PackagesDirectoryName) ? string.Empty : $"/p:NuGetPackageRoot=\"{artifactsPaths.PackagesDirectoryName}\"") .AppendArgument(GetMsBuildBinLogArgument(buildPartition, binLogSuffix)) - .MaybeAppendOutputPaths(artifactsPaths) + .MaybeAppendOutputPaths(artifactsPaths, useArtifactsPath) .ToString(); private static string GetMsBuildBinLogArgument(BuildPartition buildPartition, string suffix) @@ -257,15 +266,22 @@ internal static class DotNetCliCommandExtensions // We force the project to output binaries to a new directory. // Specifying --output and --no-dependencies breaks the build (because the previous build was not done using the custom output path), // so we don't include it if we're building no-deps (only supported for integration tests). - internal static StringBuilder MaybeAppendOutputPaths(this StringBuilder stringBuilder, ArtifactsPaths artifactsPaths, bool isRestore = false, bool excludeOutput = false) + internal static StringBuilder MaybeAppendOutputPaths(this StringBuilder stringBuilder, ArtifactsPaths artifactsPaths, bool useArtifactsPath, bool isRestore = false, bool excludeOutput = false) => excludeOutput ? stringBuilder : stringBuilder // Use AltDirectorySeparatorChar so it's not interpreted as an escaped quote `\"`. - .AppendArgument($"/p:IntermediateOutputPath=\"{artifactsPaths.IntermediateDirectoryPath}{Path.AltDirectorySeparatorChar}\"") + .AppendArgument(useArtifactsPath + // We set ArtifactsPath for dotnet sdk 8+, fallback to IntermediateOutputPath for older sdks. + ? $"/p:ArtifactsPath=\"{artifactsPaths.BuildArtifactsDirectoryPath}{Path.AltDirectorySeparatorChar}\"" + // This is technically incorrect (#2664, #2425), but it's the best we can do for older sdks. + // MSBuild does not support setting BaseIntermediateOutputPath from command line. https://github.com/dotnet/sdk/issues/2003#issuecomment-369408964 + : $"/p:IntermediateOutputPath=\"{artifactsPaths.IntermediateDirectoryPath}{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/DotNetCli/DotNetCliCommandExecutor.cs b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommandExecutor.cs index 5cbff89b47..2c813a4b5c 100644 --- a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommandExecutor.cs +++ b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommandExecutor.cs @@ -6,6 +6,7 @@ using System.Text; using System.Text.RegularExpressions; using BenchmarkDotNet.Detectors; +using BenchmarkDotNet.Environments; using BenchmarkDotNet.Extensions; using BenchmarkDotNet.Helpers; using BenchmarkDotNet.Jobs; @@ -55,9 +56,24 @@ public static DotNetCliCommandResult Execute(DotNetCliCommand parameters) } } - internal static string? GetDotNetSdkVersion() + internal static bool DotNetSdkSupportsArtifactsPath(string? customDotNetCliPath) { - using (var process = new Process { StartInfo = BuildStartInfo(customDotNetCliPath: null, workingDirectory: string.Empty, arguments: "--version", redirectStandardError: false) }) + var version = string.IsNullOrEmpty(customDotNetCliPath) + ? HostEnvironmentInfo.GetCurrent().DotNetSdkVersion.Value + : GetDotNetSdkVersion(customDotNetCliPath); + if (string.IsNullOrEmpty(version)) + { + return false; + } + version = CoreRuntime.GetParsableVersionPart(version); + return Version.TryParse(version, out var semVer) && semVer.Major >= 8; + } + + internal static string? GetDotNetSdkVersion() => GetDotNetSdkVersion(null); + + internal static string? GetDotNetSdkVersion(string? customDotNetCliPath) + { + using (var process = new Process { StartInfo = BuildStartInfo(customDotNetCliPath, workingDirectory: string.Empty, arguments: "--version", redirectStandardError: false) }) using (new ConsoleExitHandler(process, NullLogger.Instance)) { try diff --git a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliGenerator.cs b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliGenerator.cs index a432fbf881..efa719e1d8 100644 --- a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliGenerator.cs +++ b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliGenerator.cs @@ -20,6 +20,9 @@ public abstract class DotNetCliGenerator : GeneratorBase protected bool IsNetCore { get; } + // Whether to use ArtifactsPath or IntermediateOutputPath. ArtifactsPath is only supported in dotnet sdk 8+. + private protected readonly bool _useArtifactsPath; + [PublicAPI] protected DotNetCliGenerator(string targetFrameworkMoniker, string cliPath, string packagesPath, bool isNetCore) { @@ -27,6 +30,8 @@ protected DotNetCliGenerator(string targetFrameworkMoniker, string cliPath, stri CliPath = cliPath; PackagesPath = packagesPath; IsNetCore = isNetCore; + + _useArtifactsPath = DotNetCliCommandExecutor.DotNetSdkSupportsArtifactsPath(cliPath); } protected override string GetExecutableExtension() => IsNetCore ? ".dll" : ".exe"; @@ -101,8 +106,8 @@ protected override void CopyAllRequiredFiles(ArtifactsPaths artifactsPaths) protected override void GenerateBuildScript(BuildPartition buildPartition, ArtifactsPaths artifactsPaths) { var content = new StringBuilder(300) - .AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetRestoreCommand(artifactsPaths, buildPartition)}") - .AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetBuildCommand(artifactsPaths, buildPartition)}") + .AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetRestoreCommand(artifactsPaths, buildPartition, _useArtifactsPath)}") + .AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetBuildCommand(artifactsPaths, buildPartition, _useArtifactsPath)}") .ToString(); File.WriteAllText(artifactsPaths.BuildScriptFilePath, content); diff --git a/src/BenchmarkDotNet/Toolchains/GeneratorBase.cs b/src/BenchmarkDotNet/Toolchains/GeneratorBase.cs index 020788cc56..2cdf923fb2 100644 --- a/src/BenchmarkDotNet/Toolchains/GeneratorBase.cs +++ b/src/BenchmarkDotNet/Toolchains/GeneratorBase.cs @@ -44,6 +44,13 @@ public GenerateResult GenerateProject(BuildPartition buildPartition, ILogger log /// [PublicAPI] protected abstract string GetBuildArtifactsDirectoryPath(BuildPartition assemblyLocation, string programName); + /// + /// returns a path where the publish directory should be found after the build (usually \publish) + /// + [PublicAPI] + protected virtual string GetPublishDirectoryPath(string buildArtifactsDirectoryPath, string configuration) + => Path.Combine(buildArtifactsDirectoryPath, "publish"); + /// /// returns a path where executable should be found after the build (usually \bin) /// @@ -137,6 +144,7 @@ private ArtifactsPaths GetArtifactsPaths(BuildPartition buildPartition, string r return new ArtifactsPaths( rootArtifactsFolderPath: rootArtifactsFolderPath, buildArtifactsDirectoryPath: buildArtifactsDirectoryPath, + publishDirectoryPath: GetPublishDirectoryPath(buildArtifactsDirectoryPath, buildPartition.BuildConfiguration), binariesDirectoryPath: binariesDirectoryPath, intermediateDirectoryPath: GetIntermediateDirectoryPath(buildArtifactsDirectoryPath, buildPartition.BuildConfiguration), programCodePath: Path.Combine(buildArtifactsDirectoryPath, $"{programName}{codeFileExtension}"), diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/InProcessEmitArtifactsPath.cs b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/InProcessEmitArtifactsPath.cs index 0686a322ac..9234e0bd97 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/InProcessEmitArtifactsPath.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/InProcessEmitArtifactsPath.cs @@ -11,6 +11,7 @@ public InProcessEmitArtifactsPath( ArtifactsPaths baseArtifacts) : base( baseArtifacts.RootArtifactsFolderPath, baseArtifacts.BuildArtifactsDirectoryPath, + baseArtifacts.PublishDirectoryPath, baseArtifacts.BinariesDirectoryPath, baseArtifacts.IntermediateDirectoryPath, baseArtifacts.ProgramCodePath, diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/InProcessEmitGenerator.cs b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/InProcessEmitGenerator.cs index 18734fcf46..03481e1bce 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/InProcessEmitGenerator.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/InProcessEmitGenerator.cs @@ -46,6 +46,7 @@ private ArtifactsPaths GetArtifactsPaths(BuildPartition buildPartition, string r return new ArtifactsPaths( rootArtifactsFolderPath: rootArtifactsFolderPath, buildArtifactsDirectoryPath: buildArtifactsDirectoryPath, + publishDirectoryPath: null, binariesDirectoryPath: binariesDirectoryPath, intermediateDirectoryPath: null, programCodePath: 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/Toolchains/NativeAot/Generator.cs b/src/BenchmarkDotNet/Toolchains/NativeAot/Generator.cs index f1e32d6c6b..80eb06d2b9 100644 --- a/src/BenchmarkDotNet/Toolchains/NativeAot/Generator.cs +++ b/src/BenchmarkDotNet/Toolchains/NativeAot/Generator.cs @@ -74,8 +74,8 @@ protected override void GenerateBuildScript(BuildPartition buildPartition, Artif string extraArguments = NativeAotToolchain.GetExtraArguments(runtimeIdentifier); var content = new StringBuilder(300) - .AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetRestoreCommand(artifactsPaths, buildPartition, extraArguments)}") - .AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetPublishCommand(artifactsPaths, buildPartition, extraArguments)}") + .AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetRestoreCommand(artifactsPaths, buildPartition, _useArtifactsPath, extraArguments)}") + .AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetPublishCommand(artifactsPaths, buildPartition, _useArtifactsPath, extraArguments)}") .ToString(); File.WriteAllText(artifactsPaths.BuildScriptFilePath, content); From 8fd76249e8e2fdd3c1b831e1fe250379a978f776 Mon Sep 17 00:00:00 2001 From: Tim Cassell Date: Thu, 1 May 2025 21:34:18 -0400 Subject: [PATCH 2/6] Removed IntermediateOutputPath, always use ArtifactsPath. Updated DotNetSdkValidator. --- .../Toolchains/ArtifactsPaths.cs | 11 ++--- .../CsProj/CsProjClassicNetToolchain.cs | 6 +-- .../Toolchains/CsProj/CsProjGenerator.cs | 3 -- .../Toolchains/DotNetCli/DotNetCliCommand.cs | 44 +++++++------------ .../DotNetCli/DotNetCliCommandExecutor.cs | 20 +-------- .../DotNetCli/DotNetCliGenerator.cs | 9 +--- .../Toolchains/GeneratorBase.cs | 16 ++----- .../Emit/InProcessEmitArtifactsPath.cs | 3 +- .../InProcess/Emit/InProcessEmitGenerator.cs | 3 +- .../Toolchains/NativeAot/Generator.cs | 4 +- ...sionValidator.cs => DotNetSdkValidator.cs} | 31 +++++++++---- 11 files changed, 55 insertions(+), 95 deletions(-) rename src/BenchmarkDotNet/Validators/{DotNetSdkVersionValidator.cs => DotNetSdkValidator.cs} (89%) diff --git a/src/BenchmarkDotNet/Toolchains/ArtifactsPaths.cs b/src/BenchmarkDotNet/Toolchains/ArtifactsPaths.cs index f72efc207b..a9467494fa 100644 --- a/src/BenchmarkDotNet/Toolchains/ArtifactsPaths.cs +++ b/src/BenchmarkDotNet/Toolchains/ArtifactsPaths.cs @@ -4,13 +4,12 @@ namespace BenchmarkDotNet.Toolchains { public class ArtifactsPaths { - public static readonly ArtifactsPaths Empty = new ArtifactsPaths("", "", "", "", "", "", "", "", "", "", "", "", ""); + public static readonly ArtifactsPaths Empty = new ArtifactsPaths("", "", "", "", "", "", "", "", "", "", "", ""); [PublicAPI] public string RootArtifactsFolderPath { get; } [PublicAPI] public string BuildArtifactsDirectoryPath { get; } - [PublicAPI] public string PublishDirectoryPath { 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,9 +22,8 @@ public class ArtifactsPaths public ArtifactsPaths( string rootArtifactsFolderPath, string buildArtifactsDirectoryPath, - string publishDirectoryPath, string binariesDirectoryPath, - string intermediateDirectoryPath, + string publishDirectoryPath, string programCodePath, string appConfigPath, string nuGetConfigPath, @@ -37,9 +35,8 @@ public ArtifactsPaths( { RootArtifactsFolderPath = rootArtifactsFolderPath; BuildArtifactsDirectoryPath = buildArtifactsDirectoryPath; - PublishDirectoryPath = publishDirectoryPath; BinariesDirectoryPath = binariesDirectoryPath; - IntermediateDirectoryPath = intermediateDirectoryPath; + PublishDirectoryPath = publishDirectoryPath; ProgramCodePath = programCodePath; AppConfigPath = appConfigPath; NuGetConfigPath = nuGetConfigPath; diff --git a/src/BenchmarkDotNet/Toolchains/CsProj/CsProjClassicNetToolchain.cs b/src/BenchmarkDotNet/Toolchains/CsProj/CsProjClassicNetToolchain.cs index 2761bd7c2c..34b80c86b7 100644 --- a/src/BenchmarkDotNet/Toolchains/CsProj/CsProjClassicNetToolchain.cs +++ b/src/BenchmarkDotNet/Toolchains/CsProj/CsProjClassicNetToolchain.cs @@ -52,12 +52,8 @@ public override IEnumerable Validate(BenchmarkCase benchmarkCas benchmarkCase); yield break; } - else if (DotNetSdkValidator.IsCliPathInvalid(CustomDotNetCliPath, benchmarkCase, out var invalidCliError)) - { - yield return invalidCliError; - } - foreach (var validationError in DotNetSdkValidator.ValidateFrameworkSdks(benchmarkCase)) + foreach (var validationError in DotNetSdkValidator.ValidateFrameworkSdks(CustomDotNetCliPath, benchmarkCase)) { yield return validationError; } 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 3ab4f37932..81f48b4413 100644 --- a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommand.cs +++ b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommand.cs @@ -33,9 +33,6 @@ public class DotNetCliCommand [PublicAPI] public bool LogOutput { get; } - // Whether to use ArtifactsPath or IntermediateOutputPath. ArtifactsPath is only supported in dotnet sdk 8+. - private readonly bool _useArtifactsPath; - public DotNetCliCommand(string cliPath, string arguments, GenerateResult generateResult, ILogger logger, BuildPartition buildPartition, IReadOnlyList environmentVariables, TimeSpan timeout, bool logOutput = false) { @@ -47,8 +44,6 @@ public DotNetCliCommand(string cliPath, string arguments, GenerateResult generat EnvironmentVariables = environmentVariables; Timeout = timeout; LogOutput = logOutput || (buildPartition is not null && buildPartition.LogBuildOutput); - - _useArtifactsPath = DotNetCliCommandExecutor.DotNetSdkSupportsArtifactsPath(cliPath); } public DotNetCliCommand WithArguments(string arguments) @@ -77,12 +72,12 @@ public BuildResult RestoreThenBuild() if (BuildPartition.ForcedNoDependenciesForIntegrationTests) { var restoreResult = DotNetCliCommandExecutor.Execute(WithArguments( - GetRestoreCommand(GenerateResult.ArtifactsPaths, BuildPartition, _useArtifactsPath, $"{Arguments} --no-dependencies", "restore-no-deps", excludeOutput: true))); + GetRestoreCommand(GenerateResult.ArtifactsPaths, BuildPartition, $"{Arguments} --no-dependencies", "restore-no-deps", excludeOutput: true))); if (!restoreResult.IsSuccess) return BuildResult.Failure(GenerateResult, restoreResult.AllInformation); return DotNetCliCommandExecutor.Execute(WithArguments( - GetBuildCommand(GenerateResult.ArtifactsPaths, BuildPartition, _useArtifactsPath, $"{Arguments} --no-restore --no-dependencies", "build-no-restore-no-deps", excludeOutput: true))) + GetBuildCommand(GenerateResult.ArtifactsPaths, BuildPartition, $"{Arguments} --no-restore --no-dependencies", "build-no-restore-no-deps", excludeOutput: true))) .ToBuildResult(GenerateResult); } else @@ -136,30 +131,29 @@ public DotNetCliCommandResult AddPackages() public DotNetCliCommandResult Restore() => DotNetCliCommandExecutor.Execute(WithArguments( - GetRestoreCommand(GenerateResult.ArtifactsPaths, BuildPartition, _useArtifactsPath, Arguments, "restore"))); + GetRestoreCommand(GenerateResult.ArtifactsPaths, BuildPartition, Arguments, "restore"))); public DotNetCliCommandResult Build() => DotNetCliCommandExecutor.Execute(WithArguments( - GetBuildCommand(GenerateResult.ArtifactsPaths, BuildPartition, _useArtifactsPath, Arguments, "build"))); + GetBuildCommand(GenerateResult.ArtifactsPaths, BuildPartition, Arguments, "build"))); public DotNetCliCommandResult BuildNoRestore() => DotNetCliCommandExecutor.Execute(WithArguments( - GetBuildCommand(GenerateResult.ArtifactsPaths, BuildPartition, _useArtifactsPath, $"{Arguments} --no-restore", "build-no-restore"))); + GetBuildCommand(GenerateResult.ArtifactsPaths, BuildPartition, $"{Arguments} --no-restore", "build-no-restore"))); public DotNetCliCommandResult Publish() => DotNetCliCommandExecutor.Execute(WithArguments( - GetPublishCommand(GenerateResult.ArtifactsPaths, BuildPartition, _useArtifactsPath, Arguments, "publish"))); + GetPublishCommand(GenerateResult.ArtifactsPaths, BuildPartition, Arguments, "publish"))); // PublishNoBuildAndNoRestore was removed because we set --output in the build step. We use the implicit build included in the publish command. public DotNetCliCommandResult PublishNoRestore() => DotNetCliCommandExecutor.Execute(WithArguments( - GetPublishCommand(GenerateResult.ArtifactsPaths, BuildPartition, _useArtifactsPath, $"{Arguments} --no-restore", "publish-no-restore"))); + GetPublishCommand(GenerateResult.ArtifactsPaths, BuildPartition, $"{Arguments} --no-restore", "publish-no-restore"))); internal static IEnumerable GetAddPackagesCommands(BuildPartition buildPartition) => GetNuGetAddPackageCommands(buildPartition.RepresentativeBenchmarkCase, buildPartition.Resolver); - internal static string GetRestoreCommand(ArtifactsPaths artifactsPaths, BuildPartition buildPartition, - bool useArtifactsPath, string? extraArguments = null, string? binLogSuffix = null, bool excludeOutput = false) + internal static string GetRestoreCommand(ArtifactsPaths artifactsPaths, BuildPartition buildPartition, string? extraArguments = null, string? binLogSuffix = null, bool excludeOutput = false) => new StringBuilder() .AppendArgument("restore") .AppendArgument(string.IsNullOrEmpty(artifactsPaths.PackagesDirectoryName) ? string.Empty : $"--packages \"{artifactsPaths.PackagesDirectoryName}\"") @@ -167,11 +161,10 @@ internal static string GetRestoreCommand(ArtifactsPaths artifactsPaths, BuildPar .AppendArgument(extraArguments) .AppendArgument(GetMandatoryMsBuildSettings(buildPartition.BuildConfiguration)) .AppendArgument(GetMsBuildBinLogArgument(buildPartition, binLogSuffix)) - .MaybeAppendOutputPaths(artifactsPaths, useArtifactsPath, true, excludeOutput) + .MaybeAppendOutputPaths(artifactsPaths, true, excludeOutput) .ToString(); - internal static string GetBuildCommand(ArtifactsPaths artifactsPaths, BuildPartition buildPartition, - bool useArtifactsPath, string? extraArguments = null, string? binLogSuffix = null, bool excludeOutput = false) + internal static string GetBuildCommand(ArtifactsPaths artifactsPaths, BuildPartition buildPartition, string? extraArguments = null, string? binLogSuffix = null, bool excludeOutput = false) => new StringBuilder() .AppendArgument($"build -c {buildPartition.BuildConfiguration}") // we don't need to specify TFM, our auto-generated project contains always single one .AppendArgument(GetCustomMsBuildArguments(buildPartition.RepresentativeBenchmarkCase, buildPartition.Resolver)) @@ -179,11 +172,10 @@ internal static string GetBuildCommand(ArtifactsPaths artifactsPaths, BuildParti .AppendArgument(GetMandatoryMsBuildSettings(buildPartition.BuildConfiguration)) .AppendArgument(string.IsNullOrEmpty(artifactsPaths.PackagesDirectoryName) ? string.Empty : $"/p:NuGetPackageRoot=\"{artifactsPaths.PackagesDirectoryName}\"") .AppendArgument(GetMsBuildBinLogArgument(buildPartition, binLogSuffix)) - .MaybeAppendOutputPaths(artifactsPaths, useArtifactsPath, excludeOutput: excludeOutput) + .MaybeAppendOutputPaths(artifactsPaths, excludeOutput: excludeOutput) .ToString(); - internal static string GetPublishCommand(ArtifactsPaths artifactsPaths, BuildPartition buildPartition, - bool useArtifactsPath, string? extraArguments = null, string? binLogSuffix = null) + internal static string GetPublishCommand(ArtifactsPaths artifactsPaths, BuildPartition buildPartition, string? extraArguments = null, string? binLogSuffix = null) => new StringBuilder() .AppendArgument($"publish -c {buildPartition.BuildConfiguration}") // we don't need to specify TFM, our auto-generated project contains always single one .AppendArgument(GetCustomMsBuildArguments(buildPartition.RepresentativeBenchmarkCase, buildPartition.Resolver)) @@ -191,7 +183,7 @@ internal static string GetPublishCommand(ArtifactsPaths artifactsPaths, BuildPar .AppendArgument(GetMandatoryMsBuildSettings(buildPartition.BuildConfiguration)) .AppendArgument(string.IsNullOrEmpty(artifactsPaths.PackagesDirectoryName) ? string.Empty : $"/p:NuGetPackageRoot=\"{artifactsPaths.PackagesDirectoryName}\"") .AppendArgument(GetMsBuildBinLogArgument(buildPartition, binLogSuffix)) - .MaybeAppendOutputPaths(artifactsPaths, useArtifactsPath) + .MaybeAppendOutputPaths(artifactsPaths) .ToString(); private static string GetMsBuildBinLogArgument(BuildPartition buildPartition, string suffix) @@ -266,18 +258,12 @@ internal static class DotNetCliCommandExtensions // We force the project to output binaries to a new directory. // Specifying --output and --no-dependencies breaks the build (because the previous build was not done using the custom output path), // so we don't include it if we're building no-deps (only supported for integration tests). - internal static StringBuilder MaybeAppendOutputPaths(this StringBuilder stringBuilder, ArtifactsPaths artifactsPaths, bool useArtifactsPath, bool isRestore = false, bool excludeOutput = false) + internal static StringBuilder MaybeAppendOutputPaths(this StringBuilder stringBuilder, ArtifactsPaths artifactsPaths, bool isRestore = false, bool excludeOutput = false) => excludeOutput ? stringBuilder : stringBuilder // Use AltDirectorySeparatorChar so it's not interpreted as an escaped quote `\"`. - .AppendArgument(useArtifactsPath - // We set ArtifactsPath for dotnet sdk 8+, fallback to IntermediateOutputPath for older sdks. - ? $"/p:ArtifactsPath=\"{artifactsPaths.BuildArtifactsDirectoryPath}{Path.AltDirectorySeparatorChar}\"" - // This is technically incorrect (#2664, #2425), but it's the best we can do for older sdks. - // MSBuild does not support setting BaseIntermediateOutputPath from command line. https://github.com/dotnet/sdk/issues/2003#issuecomment-369408964 - : $"/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}\"") diff --git a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommandExecutor.cs b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommandExecutor.cs index 2c813a4b5c..5cbff89b47 100644 --- a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommandExecutor.cs +++ b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommandExecutor.cs @@ -6,7 +6,6 @@ using System.Text; using System.Text.RegularExpressions; using BenchmarkDotNet.Detectors; -using BenchmarkDotNet.Environments; using BenchmarkDotNet.Extensions; using BenchmarkDotNet.Helpers; using BenchmarkDotNet.Jobs; @@ -56,24 +55,9 @@ public static DotNetCliCommandResult Execute(DotNetCliCommand parameters) } } - internal static bool DotNetSdkSupportsArtifactsPath(string? customDotNetCliPath) + internal static string? GetDotNetSdkVersion() { - var version = string.IsNullOrEmpty(customDotNetCliPath) - ? HostEnvironmentInfo.GetCurrent().DotNetSdkVersion.Value - : GetDotNetSdkVersion(customDotNetCliPath); - if (string.IsNullOrEmpty(version)) - { - return false; - } - version = CoreRuntime.GetParsableVersionPart(version); - return Version.TryParse(version, out var semVer) && semVer.Major >= 8; - } - - internal static string? GetDotNetSdkVersion() => GetDotNetSdkVersion(null); - - internal static string? GetDotNetSdkVersion(string? customDotNetCliPath) - { - using (var process = new Process { StartInfo = BuildStartInfo(customDotNetCliPath, workingDirectory: string.Empty, arguments: "--version", redirectStandardError: false) }) + using (var process = new Process { StartInfo = BuildStartInfo(customDotNetCliPath: null, workingDirectory: string.Empty, arguments: "--version", redirectStandardError: false) }) using (new ConsoleExitHandler(process, NullLogger.Instance)) { try diff --git a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliGenerator.cs b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliGenerator.cs index efa719e1d8..a432fbf881 100644 --- a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliGenerator.cs +++ b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliGenerator.cs @@ -20,9 +20,6 @@ public abstract class DotNetCliGenerator : GeneratorBase protected bool IsNetCore { get; } - // Whether to use ArtifactsPath or IntermediateOutputPath. ArtifactsPath is only supported in dotnet sdk 8+. - private protected readonly bool _useArtifactsPath; - [PublicAPI] protected DotNetCliGenerator(string targetFrameworkMoniker, string cliPath, string packagesPath, bool isNetCore) { @@ -30,8 +27,6 @@ protected DotNetCliGenerator(string targetFrameworkMoniker, string cliPath, stri CliPath = cliPath; PackagesPath = packagesPath; IsNetCore = isNetCore; - - _useArtifactsPath = DotNetCliCommandExecutor.DotNetSdkSupportsArtifactsPath(cliPath); } protected override string GetExecutableExtension() => IsNetCore ? ".dll" : ".exe"; @@ -106,8 +101,8 @@ protected override void CopyAllRequiredFiles(ArtifactsPaths artifactsPaths) protected override void GenerateBuildScript(BuildPartition buildPartition, ArtifactsPaths artifactsPaths) { var content = new StringBuilder(300) - .AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetRestoreCommand(artifactsPaths, buildPartition, _useArtifactsPath)}") - .AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetBuildCommand(artifactsPaths, buildPartition, _useArtifactsPath)}") + .AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetRestoreCommand(artifactsPaths, buildPartition)}") + .AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetBuildCommand(artifactsPaths, buildPartition)}") .ToString(); File.WriteAllText(artifactsPaths.BuildScriptFilePath, content); diff --git a/src/BenchmarkDotNet/Toolchains/GeneratorBase.cs b/src/BenchmarkDotNet/Toolchains/GeneratorBase.cs index 2cdf923fb2..165dedcbba 100644 --- a/src/BenchmarkDotNet/Toolchains/GeneratorBase.cs +++ b/src/BenchmarkDotNet/Toolchains/GeneratorBase.cs @@ -44,13 +44,6 @@ public GenerateResult GenerateProject(BuildPartition buildPartition, ILogger log /// [PublicAPI] protected abstract string GetBuildArtifactsDirectoryPath(BuildPartition assemblyLocation, string programName); - /// - /// returns a path where the publish directory should be found after the build (usually \publish) - /// - [PublicAPI] - protected virtual string GetPublishDirectoryPath(string buildArtifactsDirectoryPath, string configuration) - => Path.Combine(buildArtifactsDirectoryPath, "publish"); - /// /// returns a path where executable should be found after the build (usually \bin) /// @@ -58,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 @@ -144,9 +137,8 @@ private ArtifactsPaths GetArtifactsPaths(BuildPartition buildPartition, string r return new ArtifactsPaths( rootArtifactsFolderPath: rootArtifactsFolderPath, buildArtifactsDirectoryPath: buildArtifactsDirectoryPath, - publishDirectoryPath: GetPublishDirectoryPath(buildArtifactsDirectoryPath, buildPartition.BuildConfiguration), 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 9234e0bd97..ca8ca60974 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/InProcessEmitArtifactsPath.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/InProcessEmitArtifactsPath.cs @@ -11,9 +11,8 @@ public InProcessEmitArtifactsPath( ArtifactsPaths baseArtifacts) : base( baseArtifacts.RootArtifactsFolderPath, baseArtifacts.BuildArtifactsDirectoryPath, - baseArtifacts.PublishDirectoryPath, 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 03481e1bce..5e74d65f12 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/InProcessEmitGenerator.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/InProcessEmitGenerator.cs @@ -46,9 +46,8 @@ private ArtifactsPaths GetArtifactsPaths(BuildPartition buildPartition, string r return new ArtifactsPaths( rootArtifactsFolderPath: rootArtifactsFolderPath, buildArtifactsDirectoryPath: buildArtifactsDirectoryPath, - publishDirectoryPath: null, binariesDirectoryPath: binariesDirectoryPath, - intermediateDirectoryPath: null, + publishDirectoryPath: null, programCodePath: null, appConfigPath: null, nuGetConfigPath: null, diff --git a/src/BenchmarkDotNet/Toolchains/NativeAot/Generator.cs b/src/BenchmarkDotNet/Toolchains/NativeAot/Generator.cs index 80eb06d2b9..f1e32d6c6b 100644 --- a/src/BenchmarkDotNet/Toolchains/NativeAot/Generator.cs +++ b/src/BenchmarkDotNet/Toolchains/NativeAot/Generator.cs @@ -74,8 +74,8 @@ protected override void GenerateBuildScript(BuildPartition buildPartition, Artif string extraArguments = NativeAotToolchain.GetExtraArguments(runtimeIdentifier); var content = new StringBuilder(300) - .AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetRestoreCommand(artifactsPaths, buildPartition, _useArtifactsPath, extraArguments)}") - .AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetPublishCommand(artifactsPaths, buildPartition, _useArtifactsPath, extraArguments)}") + .AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetRestoreCommand(artifactsPaths, buildPartition, extraArguments)}") + .AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetPublishCommand(artifactsPaths, buildPartition, extraArguments)}") .ToString(); File.WriteAllText(artifactsPaths.BuildScriptFilePath, content); diff --git a/src/BenchmarkDotNet/Validators/DotNetSdkVersionValidator.cs b/src/BenchmarkDotNet/Validators/DotNetSdkValidator.cs similarity index 89% rename from src/BenchmarkDotNet/Validators/DotNetSdkVersionValidator.cs rename to src/BenchmarkDotNet/Validators/DotNetSdkValidator.cs index 17331b49f8..c9a5e05609 100644 --- a/src/BenchmarkDotNet/Validators/DotNetSdkVersionValidator.cs +++ b/src/BenchmarkDotNet/Validators/DotNetSdkValidator.cs @@ -20,30 +20,45 @@ 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)) + if (!TryGetSdkVersion(benchmark, out Version 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 break; + } + + 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); + } + else if (requiredSdkVersion.Major < 8) + { + yield return new ValidationError(false, ".Net SDK versions older than 8.0 may produce builds with incorrect bindings.", benchmark); } } - public static IEnumerable ValidateFrameworkSdks(BenchmarkCase benchmark) + public static IEnumerable ValidateFrameworkSdks(string? customDotNetCliPath, BenchmarkCase benchmark) { + if (IsCliPathInvalid(customDotNetCliPath, benchmark, out ValidationError? cliPathError)) + { + yield return cliPathError; + yield break; + } if (!TryGetSdkVersion(benchmark, out Version requiredSdkVersion)) { yield break; } 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); } + else if (requiredSdkVersion.Major < 8) + { + yield return new ValidationError(false, ".Net SDK versions older than 8.0 may produce builds with incorrect bindings.", benchmark); + } } public static bool IsCliPathInvalid(string customDotNetCliPath, BenchmarkCase benchmarkCase, out ValidationError? validationError) From d4990ee39ad98086d259904380c185aea59a85f2 Mon Sep 17 00:00:00 2001 From: Tim Cassell Date: Thu, 1 May 2025 22:24:41 -0400 Subject: [PATCH 3/6] Fix BenchmarkRunnerClean skip non-critical validation errors. --- src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs b/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs index 4ff49156b8..01bfc82a50 100644 --- a/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs +++ b/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs @@ -644,7 +644,7 @@ private static (BenchmarkRunInfo[], List) GetSupportedBenchmark validationErrors.AddRange(errors); - return errors.Length == 0; + return !errors.Any(error => error.IsCritical); }) .ToArray(); From 861cd2f7f0f4a70cb30320a9dba8c8b9df1f2174 Mon Sep 17 00:00:00 2001 From: Tim Cassell Date: Sat, 3 May 2025 02:44:29 -0400 Subject: [PATCH 4/6] Force older DotNetCli partitions to build sequentially. Revert sdk validator. --- .../Running/BenchmarkRunnerClean.cs | 17 ++++++---- .../CsProj/CsProjClassicNetToolchain.cs | 6 +++- ...idator.cs => DotNetSdkVersionValidator.cs} | 31 +++++-------------- 3 files changed, 24 insertions(+), 30 deletions(-) rename src/BenchmarkDotNet/Validators/{DotNetSdkValidator.cs => DotNetSdkVersionValidator.cs} (89%) diff --git a/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs b/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs index 01bfc82a50..7d8ea85551 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); - var parallelBuildBenchmarks = supportedBenchmarks.Where(x => !x.Config.Options.IsSet(ConfigOptions.DisableParallelBuild)).ToArray(); - var parallelBuildPartitions = BenchmarkPartitioner.CreateForBuild(parallelBuildBenchmarks, resolver); + static bool ShouldBuildSequential(BuildPartition partition) + => partition.RepresentativeBenchmarkCase.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 < RuntimeMoniker.Net80); - var sequentialBuildBenchmarks = supportedBenchmarks.Where(x => x.Config.Options.IsSet(ConfigOptions.DisableParallelBuild)).ToArray(); - var sequentialBuildPartitions = BenchmarkPartitioner.CreateForBuild(sequentialBuildBenchmarks, resolver); + var parallelBuildPartitions = buildPartitions.Where(x => !ShouldBuildSequential(x)).ToArray(); + var sequentialBuildPartitions = buildPartitions.Where(ShouldBuildSequential).ToArray(); eventProcessor.OnStartBuildStage([.. parallelBuildPartitions, .. sequentialBuildPartitions]); 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) diff --git a/src/BenchmarkDotNet/Toolchains/CsProj/CsProjClassicNetToolchain.cs b/src/BenchmarkDotNet/Toolchains/CsProj/CsProjClassicNetToolchain.cs index 34b80c86b7..2761bd7c2c 100644 --- a/src/BenchmarkDotNet/Toolchains/CsProj/CsProjClassicNetToolchain.cs +++ b/src/BenchmarkDotNet/Toolchains/CsProj/CsProjClassicNetToolchain.cs @@ -52,8 +52,12 @@ public override IEnumerable Validate(BenchmarkCase benchmarkCas benchmarkCase); yield break; } + else if (DotNetSdkValidator.IsCliPathInvalid(CustomDotNetCliPath, benchmarkCase, out var invalidCliError)) + { + yield return invalidCliError; + } - foreach (var validationError in DotNetSdkValidator.ValidateFrameworkSdks(CustomDotNetCliPath, benchmarkCase)) + foreach (var validationError in DotNetSdkValidator.ValidateFrameworkSdks(benchmarkCase)) { yield return validationError; } diff --git a/src/BenchmarkDotNet/Validators/DotNetSdkValidator.cs b/src/BenchmarkDotNet/Validators/DotNetSdkVersionValidator.cs similarity index 89% rename from src/BenchmarkDotNet/Validators/DotNetSdkValidator.cs rename to src/BenchmarkDotNet/Validators/DotNetSdkVersionValidator.cs index c9a5e05609..17331b49f8 100644 --- a/src/BenchmarkDotNet/Validators/DotNetSdkValidator.cs +++ b/src/BenchmarkDotNet/Validators/DotNetSdkVersionValidator.cs @@ -20,45 +20,30 @@ public static IEnumerable ValidateCoreSdks(string? customDotNet if (IsCliPathInvalid(customDotNetCliPath, benchmark, out ValidationError? cliPathError)) { yield return cliPathError; - yield break; } - if (!TryGetSdkVersion(benchmark, out Version requiredSdkVersion)) + else if (TryGetSdkVersion(benchmark, out Version requiredSdkVersion)) { - yield break; - } - - 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); - } - else if (requiredSdkVersion.Major < 8) - { - yield return new ValidationError(false, ".Net SDK versions older than 8.0 may produce builds with incorrect bindings.", benchmark); + 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); + } } } - public static IEnumerable ValidateFrameworkSdks(string? customDotNetCliPath, BenchmarkCase benchmark) + public static IEnumerable ValidateFrameworkSdks(BenchmarkCase benchmark) { - if (IsCliPathInvalid(customDotNetCliPath, benchmark, out ValidationError? cliPathError)) - { - yield return cliPathError; - yield break; - } if (!TryGetSdkVersion(benchmark, out Version requiredSdkVersion)) { yield break; } 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); } - else if (requiredSdkVersion.Major < 8) - { - yield return new ValidationError(false, ".Net SDK versions older than 8.0 may produce builds with incorrect bindings.", benchmark); - } } public static bool IsCliPathInvalid(string customDotNetCliPath, BenchmarkCase benchmarkCase, out ValidationError? validationError) From 5feb748f10d5d9a7e28eb5af73b47d86c3aa91eb Mon Sep 17 00:00:00 2001 From: Tim Cassell Date: Sat, 3 May 2025 02:53:10 -0400 Subject: [PATCH 5/6] Use `DotNetSdkValidator.GetSdkVersionFromMoniker`. --- src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs | 3 ++- .../{DotNetSdkVersionValidator.cs => DotNetSdkValidator.cs} | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) rename src/BenchmarkDotNet/Validators/{DotNetSdkVersionValidator.cs => DotNetSdkValidator.cs} (99%) diff --git a/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs b/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs index 7d8ea85551..2b846f239a 100644 --- a/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs +++ b/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs @@ -88,7 +88,8 @@ static bool ShouldBuildSequential(BuildPartition partition) => partition.RepresentativeBenchmarkCase.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 < RuntimeMoniker.Net80); + || (partition.RepresentativeBenchmarkCase.GetToolchain().Generator is DotNetCliGenerator + && DotNetSdkValidator.GetSdkVersionFromMoniker(partition.RepresentativeBenchmarkCase.GetRuntime().RuntimeMoniker).Major < 8); var parallelBuildPartitions = buildPartitions.Where(x => !ShouldBuildSequential(x)).ToArray(); var sequentialBuildPartitions = buildPartitions.Where(ShouldBuildSequential).ToArray(); diff --git a/src/BenchmarkDotNet/Validators/DotNetSdkVersionValidator.cs b/src/BenchmarkDotNet/Validators/DotNetSdkValidator.cs similarity index 99% rename from src/BenchmarkDotNet/Validators/DotNetSdkVersionValidator.cs rename to src/BenchmarkDotNet/Validators/DotNetSdkValidator.cs index 17331b49f8..931eef9edb 100644 --- a/src/BenchmarkDotNet/Validators/DotNetSdkVersionValidator.cs +++ b/src/BenchmarkDotNet/Validators/DotNetSdkValidator.cs @@ -199,7 +199,7 @@ private static string CheckFor45PlusVersion(int releaseKey) return ""; } - private static Version GetSdkVersionFromMoniker(RuntimeMoniker runtimeMoniker) + internal static Version GetSdkVersionFromMoniker(RuntimeMoniker runtimeMoniker) { return runtimeMoniker switch { From cefa86ddf1b693c2aefc44a1e2aaf41f145f84e3 Mon Sep 17 00:00:00 2001 From: Tim Cassell Date: Sat, 3 May 2025 04:18:06 -0400 Subject: [PATCH 6/6] Moved `DotNetSdkValidator.GetSdkVersionFromMoniker` to `RuntimeMonikerExtensions.GetRuntimeVersion`. Check all configs of each partition instead of only representative. --- .../Extensions/RuntimeMonikerExtensions.cs | 46 +++++++++++ .../Running/BenchmarkRunnerClean.cs | 4 +- .../Validators/DotNetSdkValidator.cs | 78 ++----------------- 3 files changed, 54 insertions(+), 74 deletions(-) diff --git a/src/BenchmarkDotNet/Extensions/RuntimeMonikerExtensions.cs b/src/BenchmarkDotNet/Extensions/RuntimeMonikerExtensions.cs index 49c26320b3..695ac152ca 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($"SDK version check not implemented for {runtimeMoniker}") + }; } } diff --git a/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs b/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs index 2b846f239a..7052a67e17 100644 --- a/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs +++ b/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs @@ -85,11 +85,11 @@ internal static Summary[] Run(BenchmarkRunInfo[] benchmarkRunInfos) var buildPartitions = BenchmarkPartitioner.CreateForBuild(supportedBenchmarks, resolver); static bool ShouldBuildSequential(BuildPartition partition) - => partition.RepresentativeBenchmarkCase.Config.Options.IsSet(ConfigOptions.DisableParallelBuild) + => 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 - && DotNetSdkValidator.GetSdkVersionFromMoniker(partition.RepresentativeBenchmarkCase.GetRuntime().RuntimeMoniker).Major < 8); + && partition.RepresentativeBenchmarkCase.GetRuntime().RuntimeMoniker.GetRuntimeVersion().Major < 8); var parallelBuildPartitions = buildPartitions.Where(x => !ShouldBuildSequential(x)).ToArray(); var sequentialBuildPartitions = buildPartitions.Where(ShouldBuildSequential).ToArray(); diff --git a/src/BenchmarkDotNet/Validators/DotNetSdkValidator.cs b/src/BenchmarkDotNet/Validators/DotNetSdkValidator.cs index 931eef9edb..66b6a35aca 100644 --- a/src/BenchmarkDotNet/Validators/DotNetSdkValidator.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 ""; } - - internal 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