Skip to content

Commit d7dd87d

Browse files
committed
Split builds into 2 steps to gather assembly references.
1 parent 55ce92d commit d7dd87d

15 files changed

+218
-68
lines changed

src/BenchmarkDotNet/Diagnosers/PerfCollectProfiler.cs

+1
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ private void EnsureSymbolsForNativeRuntime(DiagnoserActionParameters parameters)
228228
// We install the tool in a dedicated directory in order to always use latest version and avoid issues with broken existing configs.
229229
string toolPath = Path.Combine(Path.GetTempPath(), "BenchmarkDotNet", "symbols");
230230
DotNetCliCommand cliCommand = new (
231+
csProjPath: string.Empty,
231232
cliPath: cliPath,
232233
arguments: $"tool install dotnet-symbol --tool-path \"{toolPath}\"",
233234
generateResult: null,

src/BenchmarkDotNet/Toolchains/ArtifactsPaths.cs

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

99
[PublicAPI] public string RootArtifactsFolderPath { get; }
1010
[PublicAPI] public string BuildArtifactsDirectoryPath { get; }
@@ -13,6 +13,7 @@ public class ArtifactsPaths
1313
[PublicAPI] public string ProgramCodePath { get; }
1414
[PublicAPI] public string AppConfigPath { get; }
1515
[PublicAPI] public string NuGetConfigPath { get; }
16+
[PublicAPI] public string BuildForReferencesProjectFilePath { get; }
1617
[PublicAPI] public string ProjectFilePath { get; }
1718
[PublicAPI] public string BuildScriptFilePath { get; }
1819
[PublicAPI] public string ExecutablePath { get; }
@@ -27,6 +28,7 @@ public ArtifactsPaths(
2728
string programCodePath,
2829
string appConfigPath,
2930
string nuGetConfigPath,
31+
string buildForReferencesProjectFilePath,
3032
string projectFilePath,
3133
string buildScriptFilePath,
3234
string executablePath,
@@ -40,6 +42,7 @@ public ArtifactsPaths(
4042
ProgramCodePath = programCodePath;
4143
AppConfigPath = appConfigPath;
4244
NuGetConfigPath = nuGetConfigPath;
45+
BuildForReferencesProjectFilePath = buildForReferencesProjectFilePath;
4346
ProjectFilePath = projectFilePath;
4447
BuildScriptFilePath = buildScriptFilePath;
4548
ExecutablePath = executablePath;

src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs

+40-13
Original file line numberDiff line numberDiff line change
@@ -61,37 +61,64 @@ protected override string GetBuildArtifactsDirectoryPath(BuildPartition buildPar
6161
protected override string GetProjectFilePath(string buildArtifactsDirectoryPath)
6262
=> Path.Combine(buildArtifactsDirectoryPath, "BenchmarkDotNet.Autogenerated.csproj");
6363

64+
protected override string GetProjectFilePathForReferences(string buildArtifactsDirectoryPath)
65+
=> Path.Combine(buildArtifactsDirectoryPath, "BenchmarkDotNet.Autogenerated.ForReferences.csproj");
66+
6467
protected override string GetBinariesDirectoryPath(string buildArtifactsDirectoryPath, string configuration)
6568
=> Path.Combine(buildArtifactsDirectoryPath, "bin", configuration, TargetFrameworkMoniker);
6669

6770
protected override string GetIntermediateDirectoryPath(string buildArtifactsDirectoryPath, string configuration)
6871
=> Path.Combine(buildArtifactsDirectoryPath, "obj", configuration, TargetFrameworkMoniker);
6972

70-
[SuppressMessage("ReSharper", "StringLiteralTypo")] // R# complains about $variables$
71-
protected override void GenerateProject(BuildPartition buildPartition, ArtifactsPaths artifactsPaths, ILogger logger)
72-
{
73-
var benchmark = buildPartition.RepresentativeBenchmarkCase;
74-
var projectFile = GetProjectFilePath(benchmark.Descriptor.Type, logger);
75-
76-
var xmlDoc = new XmlDocument();
77-
xmlDoc.Load(projectFile.FullName);
78-
var (customProperties, sdkName) = GetSettingsThatNeedToBeCopied(xmlDoc, projectFile);
79-
80-
var content = new StringBuilder(ResourceHelper.LoadTemplate("CsProj.txt"))
73+
private string LoadCsProj(BuildPartition buildPartition, ArtifactsPaths artifactsPaths, string projectFile, string customProperties, string sdkName)
74+
=> new StringBuilder(ResourceHelper.LoadTemplate("CsProj.txt"))
8175
.Replace("$PLATFORM$", buildPartition.Platform.ToConfig())
8276
.Replace("$CODEFILENAME$", Path.GetFileName(artifactsPaths.ProgramCodePath))
83-
.Replace("$CSPROJPATH$", projectFile.FullName)
77+
.Replace("$CSPROJPATH$", projectFile)
8478
.Replace("$TFM$", TargetFrameworkMoniker)
8579
.Replace("$PROGRAMNAME$", artifactsPaths.ProgramName)
86-
.Replace("$RUNTIMESETTINGS$", GetRuntimeSettings(benchmark.Job.Environment.Gc, buildPartition.Resolver))
80+
.Replace("$RUNTIMESETTINGS$", GetRuntimeSettings(buildPartition.RepresentativeBenchmarkCase.Job.Environment.Gc, buildPartition.Resolver))
8781
.Replace("$COPIEDSETTINGS$", customProperties)
8882
.Replace("$CONFIGURATIONNAME$", buildPartition.BuildConfiguration)
8983
.Replace("$SDKNAME$", sdkName)
9084
.ToString();
9185

86+
[SuppressMessage("ReSharper", "StringLiteralTypo")] // R# complains about $variables$
87+
protected override void GenerateProject(BuildPartition buildPartition, ArtifactsPaths artifactsPaths, ILogger logger)
88+
{
89+
var projectFile = GetProjectFilePath(buildPartition.RepresentativeBenchmarkCase.Descriptor.Type, logger);
90+
91+
var xmlDoc = new XmlDocument();
92+
xmlDoc.Load(projectFile.FullName);
93+
var (customProperties, sdkName) = GetSettingsThatNeedToBeCopied(xmlDoc, projectFile);
94+
95+
GenerateBuildForReferencesProject(buildPartition, artifactsPaths, projectFile.FullName, customProperties, sdkName);
96+
97+
var content = LoadCsProj(buildPartition, artifactsPaths, projectFile.FullName, customProperties, sdkName);
98+
9299
File.WriteAllText(artifactsPaths.ProjectFilePath, content);
93100
}
94101

102+
protected void GenerateBuildForReferencesProject(BuildPartition buildPartition, ArtifactsPaths artifactsPaths, string projectFile, string customProperties, string sdkName)
103+
{
104+
var content = LoadCsProj(buildPartition, artifactsPaths, projectFile, customProperties, sdkName);
105+
106+
// We don't include the generated .notcs file when building the reference dlls, only in the final build.
107+
var xmlDoc = new XmlDocument();
108+
xmlDoc.Load(new StringReader(content));
109+
XmlElement projectElement = xmlDoc.DocumentElement;
110+
projectElement.RemoveChild(projectElement.SelectSingleNode("ItemGroup/Compile").ParentNode);
111+
112+
var startupObjectElement = projectElement.SelectSingleNode("PropertyGroup/StartupObject");
113+
startupObjectElement.ParentNode.RemoveChild(startupObjectElement);
114+
115+
// We need to change the output type to library since we're only compiling for dlls.
116+
var outputTypeElement = projectElement.SelectSingleNode("PropertyGroup/OutputType");
117+
outputTypeElement.InnerText = "Library";
118+
119+
xmlDoc.Save(artifactsPaths.BuildForReferencesProjectFilePath);
120+
}
121+
95122
/// <summary>
96123
/// returns an MSBuild string that defines Runtime settings
97124
/// </summary>

src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliBuilder.cs

+58-9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using System;
2+
using System.IO;
3+
using System.Xml;
24
using BenchmarkDotNet.Jobs;
35
using BenchmarkDotNet.Loggers;
46
using BenchmarkDotNet.Running;
@@ -25,22 +27,69 @@ public DotNetCliBuilder(string targetFrameworkMoniker, string? customDotNetCliPa
2527

2628
public BuildResult Build(GenerateResult generateResult, BuildPartition buildPartition, ILogger logger)
2729
{
28-
BuildResult buildResult = new DotNetCliCommand(
29-
CustomDotNetCliPath,
30-
string.Empty,
31-
generateResult,
32-
logger,
33-
buildPartition,
34-
Array.Empty<EnvironmentVariable>(),
35-
buildPartition.Timeout,
36-
logOutput: LogOutput)
30+
var cliCommand = new DotNetCliCommand(
31+
generateResult.ArtifactsPaths.BuildForReferencesProjectFilePath,
32+
CustomDotNetCliPath,
33+
string.Empty,
34+
generateResult,
35+
logger,
36+
buildPartition,
37+
Array.Empty<EnvironmentVariable>(),
38+
buildPartition.Timeout,
39+
logOutput: LogOutput);
40+
41+
BuildResult buildResult;
42+
// Integration tests are built without dependencies, so we skip the first step.
43+
if (!buildPartition.ForcedNoDependenciesForIntegrationTests)
44+
{
45+
// We build the original project first to obtain all dlls.
46+
buildResult = cliCommand.RestoreThenBuild();
47+
48+
if (!buildResult.IsBuildSuccess)
49+
return buildResult;
50+
51+
// After the dlls are built, we gather the assembly references, then build the benchmark project.
52+
GatherReferences(generateResult.ArtifactsPaths);
53+
}
54+
55+
buildResult = cliCommand.WithCsProjPath(generateResult.ArtifactsPaths.ProjectFilePath)
3756
.RestoreThenBuild();
57+
3858
if (buildResult.IsBuildSuccess &&
3959
buildPartition.RepresentativeBenchmarkCase.Job.Environment.LargeAddressAware)
4060
{
4161
LargeAddressAware.SetLargeAddressAware(generateResult.ArtifactsPaths.ExecutablePath);
4262
}
4363
return buildResult;
4464
}
65+
66+
internal static void GatherReferences(ArtifactsPaths artifactsPaths)
67+
{
68+
var xmlDoc = new XmlDocument();
69+
xmlDoc.Load(artifactsPaths.ProjectFilePath);
70+
XmlElement projectElement = xmlDoc.DocumentElement;
71+
72+
// Add reference to every dll.
73+
var itemGroup = xmlDoc.CreateElement("ItemGroup");
74+
projectElement.AppendChild(itemGroup);
75+
foreach (var assemblyFile in Directory.GetFiles(artifactsPaths.BinariesDirectoryPath, "*.dll"))
76+
{
77+
var assemblyName = Path.GetFileNameWithoutExtension(assemblyFile);
78+
// 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.
79+
if (assemblyName == artifactsPaths.ProgramName)
80+
{
81+
continue;
82+
}
83+
var referenceElement = xmlDoc.CreateElement("Reference");
84+
itemGroup.AppendChild(referenceElement);
85+
referenceElement.SetAttribute("Include", assemblyName);
86+
var hintPath = xmlDoc.CreateElement("HintPath");
87+
referenceElement.AppendChild(hintPath);
88+
var locationNode = xmlDoc.CreateTextNode(assemblyFile);
89+
hintPath.AppendChild(locationNode);
90+
}
91+
92+
xmlDoc.Save(artifactsPaths.ProjectFilePath);
93+
}
4594
}
4695
}

0 commit comments

Comments
 (0)