Skip to content

Commit e1256e8

Browse files
committed
chore: add validation for net8 sdk as minimum requirements
1 parent f3ac0c1 commit e1256e8

3 files changed

Lines changed: 70 additions & 31 deletions

File tree

src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -99,14 +99,7 @@ private static async ValueTask<Summary[]> RunCore(BenchmarkRunInfo[] benchmarkRu
9999
var buildPartitions = BenchmarkPartitioner.CreateForBuild(supportedBenchmarks, resolver);
100100
eventProcessor.OnStartBuildStage(buildPartitions);
101101

102-
var sequentialBuildPartitions = buildPartitions.Where(partition =>
103-
partition.Benchmarks.Any(x => x.Config.Options.IsSet(ConfigOptions.DisableParallelBuild))
104-
// .Net SDK 8+ supports ArtifactsPath for proper parallel builds.
105-
// Older SDKs may produce builds with incorrect bindings if more than 1 partition is built in parallel.
106-
|| (partition.RepresentativeBenchmarkCase.GetToolchain().Generator is DotNetCliGenerator
107-
&& partition.RepresentativeBenchmarkCase.GetRuntime().RuntimeMoniker.GetRuntimeVersion().Major < 8)
108-
)
109-
.ToArray();
102+
var sequentialBuildPartitions = buildPartitions.Where(partition => partition.Benchmarks.Any(x => x.Config.Options.IsSet(ConfigOptions.DisableParallelBuild))).ToArray();
110103
var parallelBuildPartitions = buildPartitions.Except(sequentialBuildPartitions).ToArray();
111104

112105
Dictionary<BuildPartition, BuildResult> buildResults = parallelBuildPartitions.Length > 0

src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommand.cs

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -148,52 +148,52 @@ public Task<DotNetCliCommandResult> PublishNoRestoreAsync(CancellationToken canc
148148
cancellationToken);
149149

150150
internal static string GetRestoreCommand(ArtifactsPaths artifactsPaths, BuildPartition buildPartition, string filePath, string? extraArguments = null, string? binLogSuffix = null, bool excludeOutput = false)
151-
=> new StringBuilder()
151+
=> new StringBuilder(256)
152152
.AppendArgument("restore")
153-
.AppendArgument($"\"{filePath}\"")
153+
.AppendArgument($"\"{filePath.ToRelativePath(artifactsPaths)}\"")
154154
// restore doesn't support -f argument.
155155
.AppendArgument(artifactsPaths.PackagesDirectoryName.IsBlank() ? string.Empty : $"--packages \"{artifactsPaths.PackagesDirectoryName}\"")
156156
.AppendArgument(GetCustomMsBuildArguments(buildPartition.RepresentativeBenchmarkCase, buildPartition.Resolver))
157157
.AppendArgument(extraArguments)
158158
.AppendArgument(GetMandatoryMsBuildSettings(buildPartition.BuildConfiguration))
159159
.AppendArgument(GetMsBuildBinLogArgument(buildPartition, binLogSuffix))
160-
.MaybeAppendOutputPaths(artifactsPaths, true, excludeOutput)
160+
.MaybeAppendExtraArguments(artifactsPaths, isRestore: true, excludeOutput: excludeOutput)
161161
.ToString();
162162

163163
internal static string GetBuildCommand(ArtifactsPaths artifactsPaths, BuildPartition buildPartition, string filePath, string tfm, string? extraArguments = null, string? binLogSuffix = null, bool excludeOutput = false)
164-
=> new StringBuilder()
164+
=> new StringBuilder(256)
165165
.AppendArgument("build")
166-
.AppendArgument($"\"{filePath}\"")
166+
.AppendArgument($"\"{filePath.ToRelativePath(artifactsPaths)}\"")
167167
.AppendArgument($"-f {tfm}")
168168
.AppendArgument($"-c {buildPartition.BuildConfiguration}")
169169
.AppendArgument(GetCustomMsBuildArguments(buildPartition.RepresentativeBenchmarkCase, buildPartition.Resolver))
170170
.AppendArgument(extraArguments)
171171
.AppendArgument(GetMandatoryMsBuildSettings(buildPartition.BuildConfiguration))
172172
.AppendArgument(artifactsPaths.PackagesDirectoryName.IsBlank() ? string.Empty : $"/p:NuGetPackageRoot=\"{artifactsPaths.PackagesDirectoryName}\"")
173173
.AppendArgument(GetMsBuildBinLogArgument(buildPartition, binLogSuffix))
174-
.MaybeAppendOutputPaths(artifactsPaths, excludeOutput: excludeOutput)
174+
.MaybeAppendExtraArguments(artifactsPaths, isBuild: true, excludeOutput: excludeOutput)
175175
.ToString();
176176

177177
internal static string GetPublishCommand(ArtifactsPaths artifactsPaths, BuildPartition buildPartition, string filePath, string tfm, string? extraArguments = null, string? binLogSuffix = null)
178-
=> new StringBuilder()
178+
=> new StringBuilder(256)
179179
.AppendArgument("publish")
180-
.AppendArgument($"\"{filePath}\"")
180+
.AppendArgument($"\"{filePath.ToRelativePath(artifactsPaths)}\"")
181181
.AppendArgument($"-f {tfm}")
182182
.AppendArgument($"-c {buildPartition.BuildConfiguration}")
183183
.AppendArgument(GetCustomMsBuildArguments(buildPartition.RepresentativeBenchmarkCase, buildPartition.Resolver))
184184
.AppendArgument(extraArguments)
185185
.AppendArgument(GetMandatoryMsBuildSettings(buildPartition.BuildConfiguration))
186186
.AppendArgument(artifactsPaths.PackagesDirectoryName.IsBlank() ? string.Empty : $"/p:NuGetPackageRoot=\"{artifactsPaths.PackagesDirectoryName}\"")
187187
.AppendArgument(GetMsBuildBinLogArgument(buildPartition, binLogSuffix))
188-
.MaybeAppendOutputPaths(artifactsPaths)
188+
.MaybeAppendExtraArguments(artifactsPaths, isPublish: true)
189189
.ToString();
190190

191191
private static string GetMsBuildBinLogArgument(BuildPartition buildPartition, string? suffix)
192192
{
193193
if (!buildPartition.GenerateMSBuildBinLog || suffix.IsBlank())
194194
return string.Empty;
195195

196-
return $"\"-bl:{buildPartition.ProgramName}-{suffix}.binlog\"";
196+
return $"-bl:\"{buildPartition.ProgramName}-{suffix}.binlog\"";
197197
}
198198

199199
private static string GetCustomMsBuildArguments(BenchmarkCase benchmarkCase, IResolver resolver)
@@ -228,18 +228,34 @@ internal static class DotNetCliCommandExtensions
228228
// We force the project to output binaries to a new directory.
229229
// Specifying --output and --no-dependencies breaks the build (because the previous build was not done using the custom output path),
230230
// so we don't include it if we're building no-deps (only supported for integration tests).
231-
internal static StringBuilder MaybeAppendOutputPaths(this StringBuilder stringBuilder, ArtifactsPaths artifactsPaths, bool isRestore = false, bool excludeOutput = false)
232-
=> excludeOutput
233-
? stringBuilder
234-
: stringBuilder
235-
// Use AltDirectorySeparatorChar so it's not interpreted as an escaped quote `\"`.
236-
// Use a subdirectory for ArtifactsPath so that DefaultItemExcludes (which the SDK
237-
// sets to $(ArtifactsPath)/**) doesn't cover project-level files like wwwroot/.
238-
.AppendArgument($"/p:ArtifactsPath=\"{artifactsPaths.BuildArtifactsDirectoryPath}{Path.AltDirectorySeparatorChar}.artifacts{Path.AltDirectorySeparatorChar}\"")
239-
.AppendArgument($"/p:OutDir=\"{artifactsPaths.BinariesDirectoryPath}{Path.AltDirectorySeparatorChar}\"")
240-
// OutputPath is legacy, per-project version of OutDir. We set both just in case. https://github.com/dotnet/msbuild/issues/87
241-
.AppendArgument($"/p:OutputPath=\"{artifactsPaths.BinariesDirectoryPath}{Path.AltDirectorySeparatorChar}\"")
242-
.AppendArgument($"/p:PublishDir=\"{artifactsPaths.PublishDirectoryPath}{Path.AltDirectorySeparatorChar}\"")
243-
.AppendArgument(isRestore ? string.Empty : $"--output \"{artifactsPaths.BinariesDirectoryPath}{Path.AltDirectorySeparatorChar}\"");
231+
internal static StringBuilder MaybeAppendExtraArguments(
232+
this StringBuilder stringBuilder,
233+
ArtifactsPaths artifactsPaths,
234+
bool isRestore = false,
235+
bool isBuild = false,
236+
bool isPublish = false,
237+
bool excludeOutput = false)
238+
{
239+
// Use a subdirectory for ArtifactsPath so that DefaultItemExcludes (which the SDK
240+
// sets to $(ArtifactsPath)/**) doesn't cover project-level files like wwwroot/.
241+
stringBuilder.AppendArgument($"--artifacts-path .artifacts");
242+
243+
if (!isRestore && !excludeOutput)
244+
stringBuilder.AppendArgument($"--output \"{artifactsPaths.BinariesDirectoryPath.ToRelativePath(artifactsPaths)}\"");
245+
246+
if (isPublish)
247+
stringBuilder.AppendArgument($"/p:PublishDir=\"{artifactsPaths.PublishDirectoryPath}\"");
248+
249+
return stringBuilder;
250+
}
251+
252+
internal static string ToRelativePath(this string path, ArtifactsPaths artifactsPaths)
253+
{
254+
var buildArtifactsDirectoryPath = $"{artifactsPaths.BuildArtifactsDirectoryPath}{Path.DirectorySeparatorChar}";
255+
if (path.StartsWith(buildArtifactsDirectoryPath))
256+
return path.Substring(buildArtifactsDirectoryPath.Length);
257+
258+
return path;
259+
}
244260
}
245261
}

src/BenchmarkDotNet/Validators/DotNetSdkValidator.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using BenchmarkDotNet.Environments;
22
using BenchmarkDotNet.Extensions;
3+
using BenchmarkDotNet.Helpers;
34
using BenchmarkDotNet.Jobs;
45
using BenchmarkDotNet.Running;
56
using System.ComponentModel;
@@ -20,10 +21,19 @@ public static IEnumerable<ValidationError> ValidateCoreSdks(string? customDotNet
2021
yield return cliPathError;
2122
yield break;
2223
}
24+
2325
var requiredSdkVersion = benchmark.GetRuntime().RuntimeMoniker.GetRuntimeVersion();
2426
if (!GetInstalledDotNetSdks(customDotNetCliPath).Any(sdk => sdk >= requiredSdkVersion))
2527
{
2628
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);
29+
yield break;
30+
}
31+
32+
// Validate actual .NET SDK version. (.NET 8 SDK is minimum requirement to use ArtifactsPath)
33+
if (TryGetDotNetSdkVersion(customDotNetCliPath, out string rawVersionText, out Version? sdkVersion))
34+
{
35+
if (sdkVersion.Major < 8)
36+
yield return new ValidationError(true, $"The .NET 8 SDK is the minimum requirement for building the project. Resolved SDK version: {rawVersionText}", benchmark);
2737
}
2838
}
2939

@@ -182,5 +192,25 @@ private static string CheckFor45PlusVersion(int releaseKey)
182192

183193
return "";
184194
}
195+
196+
private static bool TryGetDotNetSdkVersion(
197+
string? customDotNetCliPath,
198+
out string rawSdkVersion,
199+
[NotNullWhen(true)] out Version? sdkVersion)
200+
{
201+
string exePath = customDotNetCliPath.IsBlank()
202+
? "dotnet"
203+
: customDotNetCliPath!;
204+
205+
rawSdkVersion = ProcessHelper.RunAndReadOutput(exePath, "--version") ?? "";
206+
207+
// Trim `-preview`/`-rc` part.
208+
var versionText = rawSdkVersion.Split('-')[0];
209+
if (Version.TryParse(versionText, out sdkVersion))
210+
return true;
211+
212+
sdkVersion = null;
213+
return false;
214+
}
185215
}
186216
}

0 commit comments

Comments
 (0)