Skip to content

Commit 0493c10

Browse files
committed
Split builds into 2 steps to gather assembly references.
1 parent ca5dfdf commit 0493c10

15 files changed

+217
-67
lines changed

src/BenchmarkDotNet/Diagnosers/PerfCollectProfiler.cs

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

+39-12
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

7073
[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"))
74+
private string LoadCsProj(BuildPartition buildPartition, ArtifactsPaths artifactsPaths, string projectFile, string customProperties, string sdkName)
75+
=> new StringBuilder(ResourceHelper.LoadTemplate("CsProj.txt"))
8176
.Replace("$PLATFORM$", buildPartition.Platform.ToConfig())
8277
.Replace("$CODEFILENAME$", Path.GetFileName(artifactsPaths.ProgramCodePath))
83-
.Replace("$CSPROJPATH$", projectFile.FullName)
78+
.Replace("$CSPROJPATH$", projectFile)
8479
.Replace("$TFM$", TargetFrameworkMoniker)
8580
.Replace("$PROGRAMNAME$", artifactsPaths.ProgramName)
86-
.Replace("$RUNTIMESETTINGS$", GetRuntimeSettings(benchmark.Job.Environment.Gc, buildPartition.Resolver))
81+
.Replace("$RUNTIMESETTINGS$", GetRuntimeSettings(buildPartition.RepresentativeBenchmarkCase.Job.Environment.Gc, buildPartition.Resolver))
8782
.Replace("$COPIEDSETTINGS$", customProperties)
8883
.Replace("$CONFIGURATIONNAME$", buildPartition.BuildConfiguration)
8984
.Replace("$SDKNAME$", sdkName)
9085
.ToString();
9186

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)