Skip to content

Commit

Permalink
Split builds into 2 steps to gather assembly references.
Browse files Browse the repository at this point in the history
  • Loading branch information
timcassell committed Aug 30, 2024
1 parent ca5dfdf commit 0493c10
Show file tree
Hide file tree
Showing 15 changed files with 217 additions and 67 deletions.
1 change: 1 addition & 0 deletions src/BenchmarkDotNet/Diagnosers/PerfCollectProfiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
csProjPath: string.Empty,
cliPath: cliPath,
arguments: $"tool install dotnet-symbol --tool-path \"{toolPath}\"",
generateResult: null,
Expand Down
5 changes: 4 additions & 1 deletion src/BenchmarkDotNet/Toolchains/ArtifactsPaths.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand All @@ -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; }
Expand All @@ -27,6 +28,7 @@ public ArtifactsPaths(
string programCodePath,
string appConfigPath,
string nuGetConfigPath,
string buildForReferencesProjectFilePath,
string projectFilePath,
string buildScriptFilePath,
string executablePath,
Expand All @@ -40,6 +42,7 @@ public ArtifactsPaths(
ProgramCodePath = programCodePath;
AppConfigPath = appConfigPath;
NuGetConfigPath = nuGetConfigPath;
BuildForReferencesProjectFilePath = buildForReferencesProjectFilePath;
ProjectFilePath = projectFilePath;
BuildScriptFilePath = buildScriptFilePath;
ExecutablePath = executablePath;
Expand Down
51 changes: 39 additions & 12 deletions src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,37 +61,64 @@ 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);

protected override string GetIntermediateDirectoryPath(string buildArtifactsDirectoryPath, string configuration)
=> 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);
}

/// <summary>
/// returns an MSBuild string that defines Runtime settings
/// </summary>
Expand Down
67 changes: 58 additions & 9 deletions src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.IO;
using System.Xml;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Running;
Expand All @@ -25,22 +27,69 @@ 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<EnvironmentVariable>(),
buildPartition.Timeout,
logOutput: LogOutput)
var cliCommand = new DotNetCliCommand(
generateResult.ArtifactsPaths.BuildForReferencesProjectFilePath,
CustomDotNetCliPath,
string.Empty,
generateResult,
logger,
buildPartition,
Array.Empty<EnvironmentVariable>(),
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.WithCsProjPath(generateResult.ArtifactsPaths.ProjectFilePath)
.RestoreThenBuild();

if (buildResult.IsBuildSuccess &&
buildPartition.RepresentativeBenchmarkCase.Job.Environment.LargeAddressAware)
{
LargeAddressAware.SetLargeAddressAware(generateResult.ArtifactsPaths.ExecutablePath);
}
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);
}
}
}
Loading

0 comments on commit 0493c10

Please sign in to comment.