diff --git a/src/BenchmarkDotNet/Diagnosers/PerfCollectProfiler.cs b/src/BenchmarkDotNet/Diagnosers/PerfCollectProfiler.cs
index f1b584ce09..d99f4c3acd 100644
--- a/src/BenchmarkDotNet/Diagnosers/PerfCollectProfiler.cs
+++ b/src/BenchmarkDotNet/Diagnosers/PerfCollectProfiler.cs
@@ -229,6 +229,7 @@ private void EnsureSymbolsForNativeRuntime(DiagnoserActionParameters parameters)
// We install the tool in a dedicated directory in order to always use latest version and avoid issues with broken existing configs.
string toolPath = Path.Combine(Path.GetTempPath(), "BenchmarkDotNet", "symbols");
DotNetCliCommand cliCommand = new (
+ projPath: string.Empty,
cliPath: cliPath,
arguments: $"tool install dotnet-symbol --tool-path \"{toolPath}\"",
generateResult: null,
diff --git a/src/BenchmarkDotNet/Toolchains/ArtifactsPaths.cs b/src/BenchmarkDotNet/Toolchains/ArtifactsPaths.cs
index 0c097f5bb0..e082d126ee 100644
--- a/src/BenchmarkDotNet/Toolchains/ArtifactsPaths.cs
+++ b/src/BenchmarkDotNet/Toolchains/ArtifactsPaths.cs
@@ -4,7 +4,7 @@ namespace BenchmarkDotNet.Toolchains
{
public class ArtifactsPaths
{
- public static readonly ArtifactsPaths Empty = new ArtifactsPaths("", "", "", "", "", "", "", "", "", "", "", "");
+ public static readonly ArtifactsPaths Empty = new ("", "", "", "", "", "", "", "", "", "", "", "", "");
[PublicAPI] public string RootArtifactsFolderPath { get; }
[PublicAPI] public string BuildArtifactsDirectoryPath { get; }
@@ -13,6 +13,7 @@ public class ArtifactsPaths
[PublicAPI] public string ProgramCodePath { get; }
[PublicAPI] public string AppConfigPath { get; }
[PublicAPI] public string NuGetConfigPath { get; }
+ [PublicAPI] public string BuildForReferencesProjectFilePath { get; }
[PublicAPI] public string ProjectFilePath { get; }
[PublicAPI] public string BuildScriptFilePath { get; }
[PublicAPI] public string ExecutablePath { get; }
@@ -27,6 +28,7 @@ public ArtifactsPaths(
string programCodePath,
string appConfigPath,
string nuGetConfigPath,
+ string buildForReferencesProjectFilePath,
string projectFilePath,
string buildScriptFilePath,
string executablePath,
@@ -40,6 +42,7 @@ public ArtifactsPaths(
ProgramCodePath = programCodePath;
AppConfigPath = appConfigPath;
NuGetConfigPath = nuGetConfigPath;
+ BuildForReferencesProjectFilePath = buildForReferencesProjectFilePath;
ProjectFilePath = projectFilePath;
BuildScriptFilePath = buildScriptFilePath;
ExecutablePath = executablePath;
diff --git a/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs b/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs
index 7e91c36ff4..0f95dea835 100644
--- a/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs
+++ b/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs
@@ -61,6 +61,9 @@ protected override string GetBuildArtifactsDirectoryPath(BuildPartition buildPar
protected override string GetProjectFilePath(string buildArtifactsDirectoryPath)
=> Path.Combine(buildArtifactsDirectoryPath, "BenchmarkDotNet.Autogenerated.csproj");
+ protected override string GetProjectFilePathForReferences(string buildArtifactsDirectoryPath)
+ => Path.Combine(buildArtifactsDirectoryPath, "BenchmarkDotNet.Autogenerated.ForReferences.csproj");
+
protected override string GetBinariesDirectoryPath(string buildArtifactsDirectoryPath, string configuration)
=> Path.Combine(buildArtifactsDirectoryPath, "bin", configuration, TargetFrameworkMoniker);
@@ -68,30 +71,54 @@ protected override string GetIntermediateDirectoryPath(string buildArtifactsDire
=> Path.Combine(buildArtifactsDirectoryPath, "obj", configuration, TargetFrameworkMoniker);
[SuppressMessage("ReSharper", "StringLiteralTypo")] // R# complains about $variables$
- protected override void GenerateProject(BuildPartition buildPartition, ArtifactsPaths artifactsPaths, ILogger logger)
- {
- var benchmark = buildPartition.RepresentativeBenchmarkCase;
- var projectFile = GetProjectFilePath(benchmark.Descriptor.Type, logger);
-
- var xmlDoc = new XmlDocument();
- xmlDoc.Load(projectFile.FullName);
- var (customProperties, sdkName) = GetSettingsThatNeedToBeCopied(xmlDoc, projectFile);
-
- var content = new StringBuilder(ResourceHelper.LoadTemplate("CsProj.txt"))
+ private string LoadCsProj(BuildPartition buildPartition, ArtifactsPaths artifactsPaths, string projectFile, string customProperties, string sdkName)
+ => new StringBuilder(ResourceHelper.LoadTemplate("CsProj.txt"))
.Replace("$PLATFORM$", buildPartition.Platform.ToConfig())
.Replace("$CODEFILENAME$", Path.GetFileName(artifactsPaths.ProgramCodePath))
- .Replace("$CSPROJPATH$", projectFile.FullName)
+ .Replace("$CSPROJPATH$", projectFile)
.Replace("$TFM$", TargetFrameworkMoniker)
.Replace("$PROGRAMNAME$", artifactsPaths.ProgramName)
- .Replace("$RUNTIMESETTINGS$", GetRuntimeSettings(benchmark.Job.Environment.Gc, buildPartition.Resolver))
+ .Replace("$RUNTIMESETTINGS$", GetRuntimeSettings(buildPartition.RepresentativeBenchmarkCase.Job.Environment.Gc, buildPartition.Resolver))
.Replace("$COPIEDSETTINGS$", customProperties)
.Replace("$CONFIGURATIONNAME$", buildPartition.BuildConfiguration)
.Replace("$SDKNAME$", sdkName)
.ToString();
+ protected override void GenerateProject(BuildPartition buildPartition, ArtifactsPaths artifactsPaths, ILogger logger)
+ {
+ var projectFile = GetProjectFilePath(buildPartition.RepresentativeBenchmarkCase.Descriptor.Type, logger);
+
+ var xmlDoc = new XmlDocument();
+ xmlDoc.Load(projectFile.FullName);
+ var (customProperties, sdkName) = GetSettingsThatNeedToBeCopied(xmlDoc, projectFile);
+
+ GenerateBuildForReferencesProject(buildPartition, artifactsPaths, projectFile.FullName, customProperties, sdkName);
+
+ var content = LoadCsProj(buildPartition, artifactsPaths, projectFile.FullName, customProperties, sdkName);
+
File.WriteAllText(artifactsPaths.ProjectFilePath, content);
}
+ protected void GenerateBuildForReferencesProject(BuildPartition buildPartition, ArtifactsPaths artifactsPaths, string projectFile, string customProperties, string sdkName)
+ {
+ var content = LoadCsProj(buildPartition, artifactsPaths, projectFile, customProperties, sdkName);
+
+ // We don't include the generated .notcs file when building the reference dlls, only in the final build.
+ var xmlDoc = new XmlDocument();
+ xmlDoc.Load(new StringReader(content));
+ XmlElement projectElement = xmlDoc.DocumentElement;
+ projectElement.RemoveChild(projectElement.SelectSingleNode("ItemGroup/Compile").ParentNode);
+
+ var startupObjectElement = projectElement.SelectSingleNode("PropertyGroup/StartupObject");
+ startupObjectElement.ParentNode.RemoveChild(startupObjectElement);
+
+ // We need to change the output type to library since we're only compiling for dlls.
+ var outputTypeElement = projectElement.SelectSingleNode("PropertyGroup/OutputType");
+ outputTypeElement.InnerText = "Library";
+
+ xmlDoc.Save(artifactsPaths.BuildForReferencesProjectFilePath);
+ }
+
///
/// returns an MSBuild string that defines Runtime settings
///
diff --git a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliBuilder.cs b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliBuilder.cs
index 7a7afc3184..fa8e05e9c7 100644
--- a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliBuilder.cs
+++ b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliBuilder.cs
@@ -1,4 +1,6 @@
using System;
+using System.IO;
+using System.Xml;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Running;
@@ -25,16 +27,34 @@ public DotNetCliBuilder(string targetFrameworkMoniker, string? customDotNetCliPa
public BuildResult Build(GenerateResult generateResult, BuildPartition buildPartition, ILogger logger)
{
- BuildResult buildResult = new DotNetCliCommand(
- CustomDotNetCliPath,
- string.Empty,
- generateResult,
- logger,
- buildPartition,
- Array.Empty(),
- buildPartition.Timeout,
- logOutput: LogOutput)
+ var cliCommand = new DotNetCliCommand(
+ generateResult.ArtifactsPaths.BuildForReferencesProjectFilePath,
+ CustomDotNetCliPath,
+ string.Empty,
+ generateResult,
+ logger,
+ buildPartition,
+ Array.Empty(),
+ buildPartition.Timeout,
+ logOutput: LogOutput);
+
+ BuildResult buildResult;
+ // Integration tests are built without dependencies, so we skip the first step.
+ if (!buildPartition.ForcedNoDependenciesForIntegrationTests)
+ {
+ // We build the original project first to obtain all dlls.
+ buildResult = cliCommand.RestoreThenBuild();
+
+ if (!buildResult.IsBuildSuccess)
+ return buildResult;
+
+ // After the dlls are built, we gather the assembly references, then build the benchmark project.
+ GatherReferences(generateResult.ArtifactsPaths);
+ }
+
+ buildResult = cliCommand.WithProjPath(generateResult.ArtifactsPaths.ProjectFilePath)
.RestoreThenBuild();
+
if (buildResult.IsBuildSuccess &&
buildPartition.RepresentativeBenchmarkCase.Job.Environment.LargeAddressAware)
{
@@ -42,5 +62,34 @@ public BuildResult Build(GenerateResult generateResult, BuildPartition buildPart
}
return buildResult;
}
+
+ internal static void GatherReferences(ArtifactsPaths artifactsPaths)
+ {
+ var xmlDoc = new XmlDocument();
+ xmlDoc.Load(artifactsPaths.ProjectFilePath);
+ XmlElement projectElement = xmlDoc.DocumentElement;
+
+ // Add reference to every dll.
+ var itemGroup = xmlDoc.CreateElement("ItemGroup");
+ projectElement.AppendChild(itemGroup);
+ foreach (var assemblyFile in Directory.GetFiles(artifactsPaths.BinariesDirectoryPath, "*.dll"))
+ {
+ var assemblyName = Path.GetFileNameWithoutExtension(assemblyFile);
+ // The dummy csproj was used to build the original project, but it also outputs a dll for itself which we need to ignore because it's not valid.
+ if (assemblyName == artifactsPaths.ProgramName)
+ {
+ continue;
+ }
+ var referenceElement = xmlDoc.CreateElement("Reference");
+ itemGroup.AppendChild(referenceElement);
+ referenceElement.SetAttribute("Include", assemblyName);
+ var hintPath = xmlDoc.CreateElement("HintPath");
+ referenceElement.AppendChild(hintPath);
+ var locationNode = xmlDoc.CreateTextNode(assemblyFile);
+ hintPath.AppendChild(locationNode);
+ }
+
+ xmlDoc.Save(artifactsPaths.ProjectFilePath);
+ }
}
}
diff --git a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommand.cs b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommand.cs
index e9078f293f..9648a86637 100644
--- a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommand.cs
+++ b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommand.cs
@@ -16,6 +16,8 @@ namespace BenchmarkDotNet.Toolchains.DotNetCli
{
public class DotNetCliCommand
{
+ [PublicAPI] public string ProjPath { get; }
+
[PublicAPI] public string CliPath { get; }
[PublicAPI] public string Arguments { get; }
@@ -32,9 +34,10 @@ public class DotNetCliCommand
[PublicAPI] public bool LogOutput { get; }
- public DotNetCliCommand(string cliPath, string arguments, GenerateResult generateResult, ILogger logger,
+ public DotNetCliCommand(string projPath, string cliPath, string arguments, GenerateResult generateResult, ILogger logger,
BuildPartition buildPartition, IReadOnlyList environmentVariables, TimeSpan timeout, bool logOutput = false)
{
+ ProjPath = projPath;
CliPath = cliPath ?? DotNetCliCommandExecutor.DefaultDotNetCliPath.Value;
Arguments = arguments;
GenerateResult = generateResult;
@@ -46,10 +49,13 @@ public DotNetCliCommand(string cliPath, string arguments, GenerateResult generat
}
public DotNetCliCommand WithArguments(string arguments)
- => new (CliPath, arguments, GenerateResult, Logger, BuildPartition, EnvironmentVariables, Timeout, logOutput: LogOutput);
+ => new (ProjPath, CliPath, arguments, GenerateResult, Logger, BuildPartition, EnvironmentVariables, Timeout, logOutput: LogOutput);
+
+ public DotNetCliCommand WithProjPath(string projPath)
+ => new (projPath, CliPath, Arguments, GenerateResult, Logger, BuildPartition, EnvironmentVariables, Timeout, logOutput: LogOutput);
public DotNetCliCommand WithCliPath(string cliPath)
- => new (cliPath, Arguments, GenerateResult, Logger, BuildPartition, EnvironmentVariables, Timeout, logOutput: LogOutput);
+ => new (ProjPath, cliPath, Arguments, GenerateResult, Logger, BuildPartition, EnvironmentVariables, Timeout, logOutput: LogOutput);
[PublicAPI]
public BuildResult RestoreThenBuild()
@@ -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, ProjPath, $"{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, ProjPath, $"{Arguments} --no-restore --no-dependencies", "build-no-restore-no-deps", excludeOutput: true)))
.ToBuildResult(GenerateResult);
}
else
@@ -118,7 +124,7 @@ public DotNetCliCommandResult AddPackages()
{
var executionTime = new TimeSpan(0);
var stdOutput = new StringBuilder();
- foreach (var cmd in GetAddPackagesCommands(BuildPartition))
+ foreach (var cmd in GetAddPackagesCommands(BuildPartition, ProjPath))
{
var result = DotNetCliCommandExecutor.Execute(WithArguments(cmd));
if (!result.IsSuccess) return result;
@@ -130,31 +136,32 @@ public DotNetCliCommandResult AddPackages()
public DotNetCliCommandResult Restore()
=> DotNetCliCommandExecutor.Execute(WithArguments(
- GetRestoreCommand(GenerateResult.ArtifactsPaths, BuildPartition, Arguments, "restore")));
+ GetRestoreCommand(GenerateResult.ArtifactsPaths, BuildPartition, ProjPath, Arguments, "restore")));
public DotNetCliCommandResult Build()
=> DotNetCliCommandExecutor.Execute(WithArguments(
- GetBuildCommand(GenerateResult.ArtifactsPaths, BuildPartition, Arguments, "build")));
+ GetBuildCommand(GenerateResult.ArtifactsPaths, BuildPartition, ProjPath, Arguments, "build")));
public DotNetCliCommandResult BuildNoRestore()
=> DotNetCliCommandExecutor.Execute(WithArguments(
- GetBuildCommand(GenerateResult.ArtifactsPaths, BuildPartition, $"{Arguments} --no-restore", "build-no-restore")));
+ GetBuildCommand(GenerateResult.ArtifactsPaths, BuildPartition, ProjPath, $"{Arguments} --no-restore", "build-no-restore")));
public DotNetCliCommandResult Publish()
=> DotNetCliCommandExecutor.Execute(WithArguments(
- GetPublishCommand(GenerateResult.ArtifactsPaths, BuildPartition, Arguments, "publish")));
+ GetPublishCommand(GenerateResult.ArtifactsPaths, BuildPartition, ProjPath, 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, ProjPath, $"{Arguments} --no-restore", "publish-no-restore")));
- internal static IEnumerable GetAddPackagesCommands(BuildPartition buildPartition)
- => GetNuGetAddPackageCommands(buildPartition.RepresentativeBenchmarkCase, buildPartition.Resolver);
+ internal static IEnumerable GetAddPackagesCommands(BuildPartition buildPartition, string projPath)
+ => GetNuGetAddPackageCommands(buildPartition.RepresentativeBenchmarkCase, buildPartition.Resolver, projPath);
- 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, string projPath, string? extraArguments = null, string? binLogSuffix = null, bool excludeOutput = false)
=> new StringBuilder()
.AppendArgument("restore")
+ .AppendArgument(string.IsNullOrEmpty(projPath) ? string.Empty : $"\"{projPath}\"")
.AppendArgument(string.IsNullOrEmpty(artifactsPaths.PackagesDirectoryName) ? string.Empty : $"--packages \"{artifactsPaths.PackagesDirectoryName}\"")
.AppendArgument(GetCustomMsBuildArguments(buildPartition.RepresentativeBenchmarkCase, buildPartition.Resolver))
.AppendArgument(extraArguments)
@@ -163,9 +170,11 @@ internal static string GetRestoreCommand(ArtifactsPaths artifactsPaths, BuildPar
.MaybeAppendOutputPaths(artifactsPaths, 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, string projPath, 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("build")
+ .AppendArgument(string.IsNullOrEmpty(projPath) ? string.Empty : $"\"{projPath}\"")
+ .AppendArgument($"-c {buildPartition.BuildConfiguration}") // we don't need to specify TFM, our auto-generated project contains always single one
.AppendArgument(GetCustomMsBuildArguments(buildPartition.RepresentativeBenchmarkCase, buildPartition.Resolver))
.AppendArgument(extraArguments)
.AppendArgument(GetMandatoryMsBuildSettings(buildPartition.BuildConfiguration))
@@ -174,9 +183,11 @@ internal static string GetBuildCommand(ArtifactsPaths artifactsPaths, BuildParti
.MaybeAppendOutputPaths(artifactsPaths, 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, string projPath, 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("publish")
+ .AppendArgument(string.IsNullOrEmpty(projPath) ? string.Empty : $"\"{projPath}\"")
+ .AppendArgument($"-c {buildPartition.BuildConfiguration}") // we don't need to specify TFM, our auto-generated project contains always single one
.AppendArgument(GetCustomMsBuildArguments(buildPartition.RepresentativeBenchmarkCase, buildPartition.Resolver))
.AppendArgument(extraArguments)
.AppendArgument(GetMandatoryMsBuildSettings(buildPartition.BuildConfiguration))
@@ -203,14 +214,14 @@ private static string GetCustomMsBuildArguments(BenchmarkCase benchmarkCase, IRe
return string.Join(" ", msBuildArguments.Select(arg => arg.TextRepresentation));
}
- private static IEnumerable GetNuGetAddPackageCommands(BenchmarkCase benchmarkCase, IResolver resolver)
+ private static IEnumerable GetNuGetAddPackageCommands(BenchmarkCase benchmarkCase, IResolver resolver, string projPath)
{
if (!benchmarkCase.Job.HasValue(InfrastructureMode.NuGetReferencesCharacteristic))
return Enumerable.Empty();
var nuGetRefs = benchmarkCase.Job.ResolveValue(InfrastructureMode.NuGetReferencesCharacteristic, resolver);
- return nuGetRefs.Select(BuildAddPackageCommand);
+ return nuGetRefs.Select(nr => BuildAddPackageCommand(nr, projPath));
}
private static string GetMandatoryMsBuildSettings(string buildConfiguration)
@@ -228,11 +239,13 @@ private static string GetMandatoryMsBuildSettings(string buildConfiguration)
return $"{NoMsBuildZombieProcesses} {EnforceOptimizations}";
}
- private static string BuildAddPackageCommand(NuGetReference reference)
+ private static string BuildAddPackageCommand(NuGetReference reference, string projPath)
{
- var commandBuilder = new StringBuilder();
- commandBuilder.AppendArgument("add package");
- commandBuilder.AppendArgument(reference.PackageName);
+ var commandBuilder = new StringBuilder()
+ .AppendArgument("add")
+ .AppendArgument(string.IsNullOrEmpty(projPath) ? string.Empty : $"\"{projPath}\"")
+ .AppendArgument("package")
+ .AppendArgument(reference.PackageName);
if (!string.IsNullOrWhiteSpace(reference.PackageVersion))
{
commandBuilder.AppendArgument("-v");
diff --git a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommandExecutor.cs b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommandExecutor.cs
index 5cbff89b47..3b12ef84f2 100644
--- a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommandExecutor.cs
+++ b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommandExecutor.cs
@@ -158,6 +158,7 @@ private static string GetDefaultDotNetCliPath()
internal static string GetSdkPath(string cliPath)
{
DotNetCliCommand cliCommand = new (
+ projPath: string.Empty,
cliPath: cliPath,
arguments: "--info",
generateResult: null,
diff --git a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliGenerator.cs b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliGenerator.cs
index a432fbf881..ddf609d8df 100644
--- a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliGenerator.cs
+++ b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliGenerator.cs
@@ -101,8 +101,10 @@ 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, artifactsPaths.BuildForReferencesProjectFilePath)}")
+ .AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetBuildCommand(artifactsPaths, buildPartition, artifactsPaths.BuildForReferencesProjectFilePath)}")
+ .AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetRestoreCommand(artifactsPaths, buildPartition, artifactsPaths.ProjectFilePath)}")
+ .AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetBuildCommand(artifactsPaths, buildPartition, artifactsPaths.ProjectFilePath)}")
.ToString();
File.WriteAllText(artifactsPaths.BuildScriptFilePath, content);
diff --git a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliPublisher.cs b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliPublisher.cs
index 83158e0b1e..72dc14aecc 100644
--- a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliPublisher.cs
+++ b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliPublisher.cs
@@ -25,14 +25,27 @@ public DotNetCliPublisher(
private IReadOnlyList? EnvironmentVariables { get; }
public BuildResult Build(GenerateResult generateResult, BuildPartition buildPartition, ILogger logger)
- => new DotNetCliCommand(
- CustomDotNetCliPath,
- ExtraArguments,
- generateResult,
- logger,
- buildPartition,
- EnvironmentVariables,
- buildPartition.Timeout)
+ {
+ var cliCommand = new DotNetCliCommand(
+ generateResult.ArtifactsPaths.BuildForReferencesProjectFilePath,
+ CustomDotNetCliPath,
+ ExtraArguments,
+ generateResult,
+ logger,
+ buildPartition,
+ EnvironmentVariables,
+ buildPartition.Timeout);
+
+ // We build the original project first to obtain all dlls.
+ var buildResult = cliCommand.RestoreThenBuild();
+
+ if (!buildResult.IsBuildSuccess)
+ return buildResult;
+
+ // After the dlls are built, we gather the assembly references, then build the benchmark project.
+ DotNetCliBuilder.GatherReferences(generateResult.ArtifactsPaths);
+ return cliCommand.WithProjPath(generateResult.ArtifactsPaths.ProjectFilePath)
.RestoreThenBuildThenPublish();
+ }
}
}
diff --git a/src/BenchmarkDotNet/Toolchains/GeneratorBase.cs b/src/BenchmarkDotNet/Toolchains/GeneratorBase.cs
index 020788cc56..b1cb0f2837 100644
--- a/src/BenchmarkDotNet/Toolchains/GeneratorBase.cs
+++ b/src/BenchmarkDotNet/Toolchains/GeneratorBase.cs
@@ -69,6 +69,13 @@ [PublicAPI] protected virtual string GetExecutableExtension()
[PublicAPI] protected virtual string GetProjectFilePath(string buildArtifactsDirectoryPath)
=> string.Empty;
+ ///
+ /// returns a path to the auto-generated .csproj file that is used to build the reference dlls
+ ///
+ [PublicAPI]
+ protected virtual string GetProjectFilePathForReferences(string buildArtifactsDirectoryPath)
+ => string.Empty;
+
///
/// returns a list of artifacts that should be removed after running the benchmarks
///
@@ -143,6 +150,7 @@ private ArtifactsPaths GetArtifactsPaths(BuildPartition buildPartition, string r
appConfigPath: $"{executablePath}.config",
nuGetConfigPath: Path.Combine(buildArtifactsDirectoryPath, "NuGet.config"),
projectFilePath: GetProjectFilePath(buildArtifactsDirectoryPath),
+ buildForReferencesProjectFilePath: GetProjectFilePathForReferences(buildArtifactsDirectoryPath),
buildScriptFilePath: Path.Combine(buildArtifactsDirectoryPath, $"{programName}{OsDetector.ScriptFileExtension}"),
executablePath: executablePath,
programName: programName,
diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/InProcessEmitArtifactsPath.cs b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/InProcessEmitArtifactsPath.cs
index 0686a322ac..9a32366989 100644
--- a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/InProcessEmitArtifactsPath.cs
+++ b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/InProcessEmitArtifactsPath.cs
@@ -17,6 +17,7 @@ public InProcessEmitArtifactsPath(
baseArtifacts.AppConfigPath,
baseArtifacts.NuGetConfigPath,
baseArtifacts.ProjectFilePath,
+ baseArtifacts.BuildForReferencesProjectFilePath,
baseArtifacts.BuildScriptFilePath,
baseArtifacts.ExecutablePath,
baseArtifacts.ProgramName,
diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/InProcessEmitGenerator.cs b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/InProcessEmitGenerator.cs
index 18734fcf46..f4cd5bcc23 100644
--- a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/InProcessEmitGenerator.cs
+++ b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/InProcessEmitGenerator.cs
@@ -52,6 +52,7 @@ private ArtifactsPaths GetArtifactsPaths(BuildPartition buildPartition, string r
appConfigPath: null,
nuGetConfigPath: null,
projectFilePath: null,
+ buildForReferencesProjectFilePath: null,
buildScriptFilePath: null,
executablePath: executablePath,
programName: programName,
diff --git a/src/BenchmarkDotNet/Toolchains/Mono/MonoPublisher.cs b/src/BenchmarkDotNet/Toolchains/Mono/MonoPublisher.cs
index ff98540cbf..9ee3b54e82 100644
--- a/src/BenchmarkDotNet/Toolchains/Mono/MonoPublisher.cs
+++ b/src/BenchmarkDotNet/Toolchains/Mono/MonoPublisher.cs
@@ -25,14 +25,30 @@ public MonoPublisher(string customDotNetCliPath)
private IReadOnlyList EnvironmentVariables { get; }
public BuildResult Build(GenerateResult generateResult, BuildPartition buildPartition, ILogger logger)
- => new DotNetCliCommand(
- CustomDotNetCliPath,
- ExtraArguments,
- generateResult,
- logger,
- buildPartition,
- EnvironmentVariables,
- buildPartition.Timeout)
- .Publish().ToBuildResult(generateResult);
+ {
+ var cliCommand = new DotNetCliCommand(
+ generateResult.ArtifactsPaths.BuildForReferencesProjectFilePath,
+ CustomDotNetCliPath,
+ string.Empty,
+ generateResult,
+ logger,
+ buildPartition,
+ EnvironmentVariables,
+ buildPartition.Timeout);
+
+ // We build the original project first to obtain all dlls.
+ var buildResult = cliCommand.RestoreThenBuild();
+
+ if (!buildResult.IsBuildSuccess)
+ return buildResult;
+
+ // After the dlls are built, we gather the assembly references, then build the benchmark project.
+ DotNetCliBuilder.GatherReferences(generateResult.ArtifactsPaths);
+ return cliCommand
+ .WithArguments(ExtraArguments)
+ .WithProjPath(generateResult.ArtifactsPaths.ProjectFilePath)
+ .Publish()
+ .ToBuildResult(generateResult);
+ }
}
}
diff --git a/src/BenchmarkDotNet/Toolchains/MonoAotLLVM/MonoAotLLVMGenerator.cs b/src/BenchmarkDotNet/Toolchains/MonoAotLLVM/MonoAotLLVMGenerator.cs
index de3cef53d3..e12e83f049 100644
--- a/src/BenchmarkDotNet/Toolchains/MonoAotLLVM/MonoAotLLVMGenerator.cs
+++ b/src/BenchmarkDotNet/Toolchains/MonoAotLLVM/MonoAotLLVMGenerator.cs
@@ -36,6 +36,8 @@ protected override void GenerateProject(BuildPartition buildPartition, Artifacts
xmlDoc.Load(projectFile.FullName);
var (customProperties, sdkName) = GetSettingsThatNeedToBeCopied(xmlDoc, projectFile);
+ GenerateBuildForReferencesProject(buildPartition, artifactsPaths, projectFile.FullName, customProperties, sdkName);
+
string content = new StringBuilder(ResourceHelper.LoadTemplate("MonoAOTLLVMCsProj.txt"))
.Replace("$PLATFORM$", buildPartition.Platform.ToConfig())
.Replace("$CODEFILENAME$", Path.GetFileName(artifactsPaths.ProgramCodePath))
diff --git a/src/BenchmarkDotNet/Toolchains/MonoWasm/WasmGenerator.cs b/src/BenchmarkDotNet/Toolchains/MonoWasm/WasmGenerator.cs
index 7c9aa8826f..3e138c94fc 100644
--- a/src/BenchmarkDotNet/Toolchains/MonoWasm/WasmGenerator.cs
+++ b/src/BenchmarkDotNet/Toolchains/MonoWasm/WasmGenerator.cs
@@ -49,6 +49,8 @@ protected void GenerateProjectFile(BuildPartition buildPartition, ArtifactsPaths
xmlDoc.Load(projectFile.FullName);
var (customProperties, sdkName) = GetSettingsThatNeedToBeCopied(xmlDoc, projectFile);
+ GenerateBuildForReferencesProject(buildPartition, artifactsPaths, projectFile.FullName, customProperties, sdkName);
+
string content = new StringBuilder(ResourceHelper.LoadTemplate("WasmCsProj.txt"))
.Replace("$PLATFORM$", buildPartition.Platform.ToConfig())
.Replace("$CODEFILENAME$", Path.GetFileName(artifactsPaths.ProgramCodePath))
diff --git a/src/BenchmarkDotNet/Toolchains/NativeAot/Generator.cs b/src/BenchmarkDotNet/Toolchains/NativeAot/Generator.cs
index 061b64a9a8..43d2a9e027 100644
--- a/src/BenchmarkDotNet/Toolchains/NativeAot/Generator.cs
+++ b/src/BenchmarkDotNet/Toolchains/NativeAot/Generator.cs
@@ -74,8 +74,10 @@ 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, artifactsPaths.BuildForReferencesProjectFilePath, extraArguments)}")
+ .AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetPublishCommand(artifactsPaths, buildPartition, artifactsPaths.BuildForReferencesProjectFilePath, extraArguments)}")
+ .AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetRestoreCommand(artifactsPaths, buildPartition, artifactsPaths.ProjectFilePath, extraArguments)}")
+ .AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetPublishCommand(artifactsPaths, buildPartition, artifactsPaths.ProjectFilePath, extraArguments)}")
.ToString();
File.WriteAllText(artifactsPaths.BuildScriptFilePath, content);
@@ -112,6 +114,14 @@ protected override void GenerateNuGetConfig(ArtifactsPaths artifactsPaths)
protected override void GenerateProject(BuildPartition buildPartition, ArtifactsPaths artifactsPaths, ILogger logger)
{
+ var projectFile = GetProjectFilePath(buildPartition.RepresentativeBenchmarkCase.Descriptor.Type, logger);
+
+ var xmlDoc = new XmlDocument();
+ xmlDoc.Load(projectFile.FullName);
+ var (customProperties, sdkName) = GetSettingsThatNeedToBeCopied(xmlDoc, projectFile);
+
+ GenerateBuildForReferencesProject(buildPartition, artifactsPaths, projectFile.FullName, customProperties, sdkName);
+
File.WriteAllText(artifactsPaths.ProjectFilePath, GenerateProjectForNuGetBuild(buildPartition, artifactsPaths, logger));
GenerateReflectionFile(artifactsPaths);
}