Skip to content

Commit fb9c47a

Browse files
committed
Use ArtifactsPath in sdk 8+ instead of IntermediateOutputPath.
Pass PublishDir property to dotnet.
1 parent cd50f7b commit fb9c47a

File tree

10 files changed

+78
-25
lines changed

10 files changed

+78
-25
lines changed

src/BenchmarkDotNet/Templates/WasmCsProj.txt

+3-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
1717
<TargetFramework>$TFM$</TargetFramework>
1818
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
19-
<AppDir>$(MSBuildThisFileDirectory)\bin\$TFM$\browser-wasm\publish</AppDir>
19+
<AppDir>$(PublishDir)</AppDir>
2020
<AssemblyName>$PROGRAMNAME$</AssemblyName>
2121
<RuntimeIdentifier>browser-wasm</RuntimeIdentifier>
2222
<SuppressTrimAnalysisWarnings>true</SuppressTrimAnalysisWarnings>
@@ -49,8 +49,8 @@
4949

5050
<Target Name="PrepareForWasmBuild" AfterTargets="Publish">
5151
<ItemGroup>
52-
<WasmAssembliesToBundle Include="$(TargetDir)publish\*.dll" Condition="'$(RunAOTCompilation)' != 'true'" />
53-
<WasmAssembliesToBundle Include="$(TargetDir)publish\*.dll" Exclude="$(TargetDir)publish\KernelTraceControl.dll" Condition="'$(RunAOTCompilation)' == 'true'" />
52+
<WasmAssembliesToBundle Include="$(PublishDir)*.dll" Condition="'$(RunAOTCompilation)' != 'true'" />
53+
<WasmAssembliesToBundle Include="$(PublishDir)*.dll" Exclude="$(PublishDir)KernelTraceControl.dll" Condition="'$(RunAOTCompilation)' == 'true'" />
5454
</ItemGroup>
5555
</Target>
5656

src/BenchmarkDotNet/Toolchains/ArtifactsPaths.cs

+4-1
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ namespace BenchmarkDotNet.Toolchains
44
{
55
public class ArtifactsPaths
66
{
7-
public static readonly ArtifactsPaths Empty = new ArtifactsPaths("", "", "", "", "", "", "", "", "", "", "", "");
7+
public static readonly ArtifactsPaths Empty = new ArtifactsPaths("", "", "", "", "", "", "", "", "", "", "", "", "");
88

99
[PublicAPI] public string RootArtifactsFolderPath { get; }
1010
[PublicAPI] public string BuildArtifactsDirectoryPath { get; }
11+
[PublicAPI] public string PublishDirectoryPath { get; }
1112
[PublicAPI] public string BinariesDirectoryPath { get; }
1213
[PublicAPI] public string IntermediateDirectoryPath { get; }
1314
[PublicAPI] public string ProgramCodePath { get; }
@@ -22,6 +23,7 @@ public class ArtifactsPaths
2223
public ArtifactsPaths(
2324
string rootArtifactsFolderPath,
2425
string buildArtifactsDirectoryPath,
26+
string publishDirectoryPath,
2527
string binariesDirectoryPath,
2628
string intermediateDirectoryPath,
2729
string programCodePath,
@@ -35,6 +37,7 @@ public ArtifactsPaths(
3537
{
3638
RootArtifactsFolderPath = rootArtifactsFolderPath;
3739
BuildArtifactsDirectoryPath = buildArtifactsDirectoryPath;
40+
PublishDirectoryPath = publishDirectoryPath;
3841
BinariesDirectoryPath = binariesDirectoryPath;
3942
IntermediateDirectoryPath = intermediateDirectoryPath;
4043
ProgramCodePath = programCodePath;

src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommand.cs

+31-15
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Linq;
55
using System.Text;
66
using BenchmarkDotNet.Characteristics;
7+
using BenchmarkDotNet.Environments;
78
using BenchmarkDotNet.Extensions;
89
using BenchmarkDotNet.Jobs;
910
using BenchmarkDotNet.Loggers;
@@ -32,6 +33,9 @@ public class DotNetCliCommand
3233

3334
[PublicAPI] public bool LogOutput { get; }
3435

36+
// Whether to use ArtifactsPath or IntermediateOutputPath. ArtifactsPath is only supported in dotnet sdk 8+.
37+
private readonly bool _useArtifactsPath;
38+
3539
public DotNetCliCommand(string cliPath, string arguments, GenerateResult generateResult, ILogger logger,
3640
BuildPartition buildPartition, IReadOnlyList<EnvironmentVariable> environmentVariables, TimeSpan timeout, bool logOutput = false)
3741
{
@@ -43,6 +47,8 @@ public DotNetCliCommand(string cliPath, string arguments, GenerateResult generat
4347
EnvironmentVariables = environmentVariables;
4448
Timeout = timeout;
4549
LogOutput = logOutput || (buildPartition is not null && buildPartition.LogBuildOutput);
50+
51+
_useArtifactsPath = DotNetCliCommandExecutor.DotNetSdkSupportsArtifactsPath(cliPath);
4652
}
4753

4854
public DotNetCliCommand WithArguments(string arguments)
@@ -71,12 +77,12 @@ public BuildResult RestoreThenBuild()
7177
if (BuildPartition.ForcedNoDependenciesForIntegrationTests)
7278
{
7379
var restoreResult = DotNetCliCommandExecutor.Execute(WithArguments(
74-
GetRestoreCommand(GenerateResult.ArtifactsPaths, BuildPartition, $"{Arguments} --no-dependencies", "restore-no-deps", excludeOutput: true)));
80+
GetRestoreCommand(GenerateResult.ArtifactsPaths, BuildPartition, _useArtifactsPath, $"{Arguments} --no-dependencies", "restore-no-deps", excludeOutput: true)));
7581
if (!restoreResult.IsSuccess)
7682
return BuildResult.Failure(GenerateResult, restoreResult.AllInformation);
7783

7884
return DotNetCliCommandExecutor.Execute(WithArguments(
79-
GetBuildCommand(GenerateResult.ArtifactsPaths, BuildPartition, $"{Arguments} --no-restore --no-dependencies", "build-no-restore-no-deps", excludeOutput: true)))
85+
GetBuildCommand(GenerateResult.ArtifactsPaths, BuildPartition, _useArtifactsPath, $"{Arguments} --no-restore --no-dependencies", "build-no-restore-no-deps", excludeOutput: true)))
8086
.ToBuildResult(GenerateResult);
8187
}
8288
else
@@ -130,59 +136,62 @@ public DotNetCliCommandResult AddPackages()
130136

131137
public DotNetCliCommandResult Restore()
132138
=> DotNetCliCommandExecutor.Execute(WithArguments(
133-
GetRestoreCommand(GenerateResult.ArtifactsPaths, BuildPartition, Arguments, "restore")));
139+
GetRestoreCommand(GenerateResult.ArtifactsPaths, BuildPartition, _useArtifactsPath, Arguments, "restore")));
134140

135141
public DotNetCliCommandResult Build()
136142
=> DotNetCliCommandExecutor.Execute(WithArguments(
137-
GetBuildCommand(GenerateResult.ArtifactsPaths, BuildPartition, Arguments, "build")));
143+
GetBuildCommand(GenerateResult.ArtifactsPaths, BuildPartition, _useArtifactsPath, Arguments, "build")));
138144

139145
public DotNetCliCommandResult BuildNoRestore()
140146
=> DotNetCliCommandExecutor.Execute(WithArguments(
141-
GetBuildCommand(GenerateResult.ArtifactsPaths, BuildPartition, $"{Arguments} --no-restore", "build-no-restore")));
147+
GetBuildCommand(GenerateResult.ArtifactsPaths, BuildPartition, _useArtifactsPath, $"{Arguments} --no-restore", "build-no-restore")));
142148

143149
public DotNetCliCommandResult Publish()
144150
=> DotNetCliCommandExecutor.Execute(WithArguments(
145-
GetPublishCommand(GenerateResult.ArtifactsPaths, BuildPartition, Arguments, "publish")));
151+
GetPublishCommand(GenerateResult.ArtifactsPaths, BuildPartition, _useArtifactsPath, Arguments, "publish")));
146152

147153
// PublishNoBuildAndNoRestore was removed because we set --output in the build step. We use the implicit build included in the publish command.
148154
public DotNetCliCommandResult PublishNoRestore()
149155
=> DotNetCliCommandExecutor.Execute(WithArguments(
150-
GetPublishCommand(GenerateResult.ArtifactsPaths, BuildPartition, $"{Arguments} --no-restore", "publish-no-restore")));
156+
GetPublishCommand(GenerateResult.ArtifactsPaths, BuildPartition, _useArtifactsPath, $"{Arguments} --no-restore", "publish-no-restore")));
151157

152158
internal static IEnumerable<string> GetAddPackagesCommands(BuildPartition buildPartition)
153159
=> GetNuGetAddPackageCommands(buildPartition.RepresentativeBenchmarkCase, buildPartition.Resolver);
154160

155-
internal static string GetRestoreCommand(ArtifactsPaths artifactsPaths, BuildPartition buildPartition, string? extraArguments = null, string? binLogSuffix = null, bool excludeOutput = false)
161+
internal static string GetRestoreCommand(ArtifactsPaths artifactsPaths, BuildPartition buildPartition,
162+
bool useArtifactsPath, string? extraArguments = null, string? binLogSuffix = null, bool excludeOutput = false)
156163
=> new StringBuilder()
157164
.AppendArgument("restore")
158165
.AppendArgument(string.IsNullOrEmpty(artifactsPaths.PackagesDirectoryName) ? string.Empty : $"--packages \"{artifactsPaths.PackagesDirectoryName}\"")
159166
.AppendArgument(GetCustomMsBuildArguments(buildPartition.RepresentativeBenchmarkCase, buildPartition.Resolver))
160167
.AppendArgument(extraArguments)
161168
.AppendArgument(GetMandatoryMsBuildSettings(buildPartition.BuildConfiguration))
162169
.AppendArgument(GetMsBuildBinLogArgument(buildPartition, binLogSuffix))
163-
.MaybeAppendOutputPaths(artifactsPaths, true, excludeOutput)
170+
.MaybeAppendOutputPaths(artifactsPaths, useArtifactsPath, true, excludeOutput)
164171
.ToString();
165172

166-
internal static string GetBuildCommand(ArtifactsPaths artifactsPaths, BuildPartition buildPartition, string? extraArguments = null, string? binLogSuffix = null, bool excludeOutput = false)
173+
internal static string GetBuildCommand(ArtifactsPaths artifactsPaths, BuildPartition buildPartition,
174+
bool useArtifactsPath, string? extraArguments = null, string? binLogSuffix = null, bool excludeOutput = false)
167175
=> new StringBuilder()
168176
.AppendArgument($"build -c {buildPartition.BuildConfiguration}") // we don't need to specify TFM, our auto-generated project contains always single one
169177
.AppendArgument(GetCustomMsBuildArguments(buildPartition.RepresentativeBenchmarkCase, buildPartition.Resolver))
170178
.AppendArgument(extraArguments)
171179
.AppendArgument(GetMandatoryMsBuildSettings(buildPartition.BuildConfiguration))
172180
.AppendArgument(string.IsNullOrEmpty(artifactsPaths.PackagesDirectoryName) ? string.Empty : $"/p:NuGetPackageRoot=\"{artifactsPaths.PackagesDirectoryName}\"")
173181
.AppendArgument(GetMsBuildBinLogArgument(buildPartition, binLogSuffix))
174-
.MaybeAppendOutputPaths(artifactsPaths, excludeOutput: excludeOutput)
182+
.MaybeAppendOutputPaths(artifactsPaths, useArtifactsPath, excludeOutput: excludeOutput)
175183
.ToString();
176184

177-
internal static string GetPublishCommand(ArtifactsPaths artifactsPaths, BuildPartition buildPartition, string? extraArguments = null, string? binLogSuffix = null)
185+
internal static string GetPublishCommand(ArtifactsPaths artifactsPaths, BuildPartition buildPartition,
186+
bool useArtifactsPath, string? extraArguments = null, string? binLogSuffix = null)
178187
=> new StringBuilder()
179188
.AppendArgument($"publish -c {buildPartition.BuildConfiguration}") // we don't need to specify TFM, our auto-generated project contains always single one
180189
.AppendArgument(GetCustomMsBuildArguments(buildPartition.RepresentativeBenchmarkCase, buildPartition.Resolver))
181190
.AppendArgument(extraArguments)
182191
.AppendArgument(GetMandatoryMsBuildSettings(buildPartition.BuildConfiguration))
183192
.AppendArgument(string.IsNullOrEmpty(artifactsPaths.PackagesDirectoryName) ? string.Empty : $"/p:NuGetPackageRoot=\"{artifactsPaths.PackagesDirectoryName}\"")
184193
.AppendArgument(GetMsBuildBinLogArgument(buildPartition, binLogSuffix))
185-
.MaybeAppendOutputPaths(artifactsPaths)
194+
.MaybeAppendOutputPaths(artifactsPaths, useArtifactsPath)
186195
.ToString();
187196

188197
private static string GetMsBuildBinLogArgument(BuildPartition buildPartition, string suffix)
@@ -257,15 +266,22 @@ internal static class DotNetCliCommandExtensions
257266
// We force the project to output binaries to a new directory.
258267
// Specifying --output and --no-dependencies breaks the build (because the previous build was not done using the custom output path),
259268
// so we don't include it if we're building no-deps (only supported for integration tests).
260-
internal static StringBuilder MaybeAppendOutputPaths(this StringBuilder stringBuilder, ArtifactsPaths artifactsPaths, bool isRestore = false, bool excludeOutput = false)
269+
internal static StringBuilder MaybeAppendOutputPaths(this StringBuilder stringBuilder, ArtifactsPaths artifactsPaths, bool useArtifactsPath, bool isRestore = false, bool excludeOutput = false)
261270
=> excludeOutput
262271
? stringBuilder
263272
: stringBuilder
264273
// Use AltDirectorySeparatorChar so it's not interpreted as an escaped quote `\"`.
265-
.AppendArgument($"/p:IntermediateOutputPath=\"{artifactsPaths.IntermediateDirectoryPath}{Path.AltDirectorySeparatorChar}\"")
274+
.AppendArgument(useArtifactsPath
275+
// We set ArtifactsPath for dotnet sdk 8+, fallback to IntermediateOutputPath for older sdks.
276+
? $"/p:ArtifactsPath=\"{artifactsPaths.BuildArtifactsDirectoryPath}{Path.AltDirectorySeparatorChar}\""
277+
// This is technically incorrect (#2664, #2425), but it's the best we can do for older sdks.
278+
// MSBuild does not support setting BaseIntermediateOutputPath from command line. https://github.com/dotnet/sdk/issues/2003#issuecomment-369408964
279+
: $"/p:IntermediateOutputPath=\"{artifactsPaths.IntermediateDirectoryPath}{Path.AltDirectorySeparatorChar}\""
280+
)
266281
.AppendArgument($"/p:OutDir=\"{artifactsPaths.BinariesDirectoryPath}{Path.AltDirectorySeparatorChar}\"")
267282
// OutputPath is legacy, per-project version of OutDir. We set both just in case. https://github.com/dotnet/msbuild/issues/87
268283
.AppendArgument($"/p:OutputPath=\"{artifactsPaths.BinariesDirectoryPath}{Path.AltDirectorySeparatorChar}\"")
284+
.AppendArgument($"/p:PublishDir=\"{artifactsPaths.PublishDirectoryPath}{Path.AltDirectorySeparatorChar}\"")
269285
.AppendArgument(isRestore ? string.Empty : $"--output \"{artifactsPaths.BinariesDirectoryPath}{Path.AltDirectorySeparatorChar}\"");
270286
}
271287
}

src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommandExecutor.cs

+18-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Text;
77
using System.Text.RegularExpressions;
88
using BenchmarkDotNet.Detectors;
9+
using BenchmarkDotNet.Environments;
910
using BenchmarkDotNet.Extensions;
1011
using BenchmarkDotNet.Helpers;
1112
using BenchmarkDotNet.Jobs;
@@ -55,9 +56,24 @@ public static DotNetCliCommandResult Execute(DotNetCliCommand parameters)
5556
}
5657
}
5758

58-
internal static string? GetDotNetSdkVersion()
59+
internal static bool DotNetSdkSupportsArtifactsPath(string? customDotNetCliPath)
5960
{
60-
using (var process = new Process { StartInfo = BuildStartInfo(customDotNetCliPath: null, workingDirectory: string.Empty, arguments: "--version", redirectStandardError: false) })
61+
var version = string.IsNullOrEmpty(customDotNetCliPath)
62+
? HostEnvironmentInfo.GetCurrent().DotNetSdkVersion.Value
63+
: GetDotNetSdkVersion(customDotNetCliPath);
64+
if (string.IsNullOrEmpty(version))
65+
{
66+
return false;
67+
}
68+
version = CoreRuntime.GetParsableVersionPart(version);
69+
return Version.TryParse(version, out var semVer) && semVer.Major >= 8;
70+
}
71+
72+
internal static string? GetDotNetSdkVersion() => GetDotNetSdkVersion(null);
73+
74+
internal static string? GetDotNetSdkVersion(string? customDotNetCliPath)
75+
{
76+
using (var process = new Process { StartInfo = BuildStartInfo(customDotNetCliPath, workingDirectory: string.Empty, arguments: "--version", redirectStandardError: false) })
6177
using (new ConsoleExitHandler(process, NullLogger.Instance))
6278
{
6379
try

src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliGenerator.cs

+7-2
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,18 @@ public abstract class DotNetCliGenerator : GeneratorBase
2020

2121
protected bool IsNetCore { get; }
2222

23+
// Whether to use ArtifactsPath or IntermediateOutputPath. ArtifactsPath is only supported in dotnet sdk 8+.
24+
private protected readonly bool _useArtifactsPath;
25+
2326
[PublicAPI]
2427
protected DotNetCliGenerator(string targetFrameworkMoniker, string cliPath, string packagesPath, bool isNetCore)
2528
{
2629
TargetFrameworkMoniker = targetFrameworkMoniker;
2730
CliPath = cliPath;
2831
PackagesPath = packagesPath;
2932
IsNetCore = isNetCore;
33+
34+
_useArtifactsPath = DotNetCliCommandExecutor.DotNetSdkSupportsArtifactsPath(cliPath);
3035
}
3136

3237
protected override string GetExecutableExtension() => IsNetCore ? ".dll" : ".exe";
@@ -101,8 +106,8 @@ protected override void CopyAllRequiredFiles(ArtifactsPaths artifactsPaths)
101106
protected override void GenerateBuildScript(BuildPartition buildPartition, ArtifactsPaths artifactsPaths)
102107
{
103108
var content = new StringBuilder(300)
104-
.AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetRestoreCommand(artifactsPaths, buildPartition)}")
105-
.AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetBuildCommand(artifactsPaths, buildPartition)}")
109+
.AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetRestoreCommand(artifactsPaths, buildPartition, _useArtifactsPath)}")
110+
.AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetBuildCommand(artifactsPaths, buildPartition, _useArtifactsPath)}")
106111
.ToString();
107112

108113
File.WriteAllText(artifactsPaths.BuildScriptFilePath, content);

src/BenchmarkDotNet/Toolchains/GeneratorBase.cs

+8
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@ public GenerateResult GenerateProject(BuildPartition buildPartition, ILogger log
4444
/// </summary>
4545
[PublicAPI] protected abstract string GetBuildArtifactsDirectoryPath(BuildPartition assemblyLocation, string programName);
4646

47+
/// <summary>
48+
/// returns a path where the publish directory should be found after the build (usually \publish)
49+
/// </summary>
50+
[PublicAPI]
51+
protected virtual string GetPublishDirectoryPath(string buildArtifactsDirectoryPath, string configuration)
52+
=> Path.Combine(buildArtifactsDirectoryPath, "publish");
53+
4754
/// <summary>
4855
/// returns a path where executable should be found after the build (usually \bin)
4956
/// </summary>
@@ -137,6 +144,7 @@ private ArtifactsPaths GetArtifactsPaths(BuildPartition buildPartition, string r
137144
return new ArtifactsPaths(
138145
rootArtifactsFolderPath: rootArtifactsFolderPath,
139146
buildArtifactsDirectoryPath: buildArtifactsDirectoryPath,
147+
publishDirectoryPath: GetPublishDirectoryPath(buildArtifactsDirectoryPath, buildPartition.BuildConfiguration),
140148
binariesDirectoryPath: binariesDirectoryPath,
141149
intermediateDirectoryPath: GetIntermediateDirectoryPath(buildArtifactsDirectoryPath, buildPartition.BuildConfiguration),
142150
programCodePath: Path.Combine(buildArtifactsDirectoryPath, $"{programName}{codeFileExtension}"),

src/BenchmarkDotNet/Toolchains/InProcess/Emit/InProcessEmitArtifactsPath.cs

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public InProcessEmitArtifactsPath(
1111
ArtifactsPaths baseArtifacts) : base(
1212
baseArtifacts.RootArtifactsFolderPath,
1313
baseArtifacts.BuildArtifactsDirectoryPath,
14+
baseArtifacts.PublishDirectoryPath,
1415
baseArtifacts.BinariesDirectoryPath,
1516
baseArtifacts.IntermediateDirectoryPath,
1617
baseArtifacts.ProgramCodePath,

src/BenchmarkDotNet/Toolchains/InProcess/Emit/InProcessEmitGenerator.cs

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ private ArtifactsPaths GetArtifactsPaths(BuildPartition buildPartition, string r
4646
return new ArtifactsPaths(
4747
rootArtifactsFolderPath: rootArtifactsFolderPath,
4848
buildArtifactsDirectoryPath: buildArtifactsDirectoryPath,
49+
publishDirectoryPath: null,
4950
binariesDirectoryPath: binariesDirectoryPath,
5051
intermediateDirectoryPath: null,
5152
programCodePath: null,

0 commit comments

Comments
 (0)