Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gather assembly references #2508

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading