Skip to content

Commit 3af5e76

Browse files
committed
Split builds into 2 steps to gather assembly references.
1 parent e6fdc6b commit 3af5e76

15 files changed

+210
-54
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

+35
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ 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

@@ -77,6 +80,8 @@ protected override void GenerateProject(BuildPartition buildPartition, Artifacts
7780
xmlDoc.Load(projectFile.FullName);
7881
var (customProperties, sdkName) = GetSettingsThatNeedToBeCopied(xmlDoc, projectFile);
7982

83+
GenerateBuildForReferencesProject(buildPartition, artifactsPaths, projectFile.FullName, customProperties, sdkName);
84+
8085
var content = new StringBuilder(ResourceHelper.LoadTemplate("CsProj.txt"))
8186
.Replace("$PLATFORM$", buildPartition.Platform.ToConfig())
8287
.Replace("$CODEFILENAME$", Path.GetFileName(artifactsPaths.ProgramCodePath))
@@ -92,6 +97,36 @@ protected override void GenerateProject(BuildPartition buildPartition, Artifacts
9297
File.WriteAllText(artifactsPaths.ProjectFilePath, content);
9398
}
9499

100+
protected void GenerateBuildForReferencesProject(BuildPartition buildPartition, ArtifactsPaths artifactsPaths, string projectFile, string customProperties, string sdkName)
101+
{
102+
var content = new StringBuilder(ResourceHelper.LoadTemplate("CsProj.txt"))
103+
.Replace("$PLATFORM$", buildPartition.Platform.ToConfig())
104+
.Replace("$CODEFILENAME$", Path.GetFileName(artifactsPaths.ProgramCodePath))
105+
.Replace("$CSPROJPATH$", projectFile)
106+
.Replace("$TFM$", TargetFrameworkMoniker)
107+
.Replace("$PROGRAMNAME$", artifactsPaths.ProgramName)
108+
.Replace("$RUNTIMESETTINGS$", GetRuntimeSettings(buildPartition.RepresentativeBenchmarkCase.Job.Environment.Gc, buildPartition.Resolver))
109+
.Replace("$COPIEDSETTINGS$", customProperties)
110+
.Replace("$CONFIGURATIONNAME$", buildPartition.BuildConfiguration)
111+
.Replace("$SDKNAME$", sdkName)
112+
.ToString();
113+
114+
// We don't include the generated .notcs file when building the reference dlls, only in the final build.
115+
var xmlDoc = new XmlDocument();
116+
xmlDoc.Load(new StringReader(content));
117+
XmlElement projectElement = xmlDoc.DocumentElement;
118+
projectElement.RemoveChild(projectElement.SelectSingleNode("ItemGroup/Compile").ParentNode);
119+
120+
var startupObjectElement = projectElement.SelectSingleNode("PropertyGroup/StartupObject");
121+
startupObjectElement.ParentNode.RemoveChild(startupObjectElement);
122+
123+
// We need to change the output type to library since we're only compiling for dlls.
124+
var outputTypeElement = projectElement.SelectSingleNode("PropertyGroup/OutputType");
125+
outputTypeElement.InnerText = "Library";
126+
127+
xmlDoc.Save(artifactsPaths.BuildForReferencesProjectFilePath);
128+
}
129+
95130
/// <summary>
96131
/// returns an MSBuild string that defines Runtime settings
97132
/// </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)