Skip to content

Commit 9368a7d

Browse files
authored
Update gather references (dotnet#2921)
* Use a wrapper project to build the source project. * Use default template to build for references. * Don't remove ProjectReference outside of Mono.
1 parent 545942a commit 9368a7d

File tree

7 files changed

+79
-96
lines changed

7 files changed

+79
-96
lines changed

src/BenchmarkDotNet/Environments/Runtimes/CoreRuntime.cs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ private static CoreRuntime GetPlatformSpecific(CoreRuntime fallback, Assembly? a
232232
? new CoreRuntime(fallback.RuntimeMoniker, $"{fallback.MsBuildMoniker}-{platform}", fallback.Name)
233233
: fallback;
234234

235-
internal static bool TryGetTargetPlatform(Assembly? assembly, [NotNullWhen(true)] out string? platform)
235+
private static bool TryGetTargetPlatform(Assembly? assembly, [NotNullWhen(true)] out string? platform)
236236
{
237237
platform = null;
238238

@@ -252,11 +252,8 @@ internal static bool TryGetTargetPlatform(Assembly? assembly, [NotNullWhen(true)
252252
if (platformNameProperty is null)
253253
return false;
254254

255-
if (platformNameProperty.GetValue(attributeInstance) is not string platformName)
256-
return false;
257-
258-
platform = platformName;
259-
return true;
255+
platform = platformNameProperty.GetValue(attributeInstance) as string;
256+
return platform.IsNotBlank();
260257
}
261258
}
262259
}

src/BenchmarkDotNet/Helpers/FrameworkVersionHelper.cs

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
using System;
2-
using System.Diagnostics.CodeAnalysis;
32
using System.IO;
43
using System.Linq;
54
using System.Reflection;
65
using System.Runtime.Versioning;
7-
using BenchmarkDotNet.Environments;
86
using Microsoft.Win32;
97

108
namespace BenchmarkDotNet.Helpers
@@ -124,44 +122,5 @@ private static bool IsDeveloperPackInstalled(string version) => Directory.Exists
124122
Environment.Is64BitOperatingSystem
125123
? Environment.SpecialFolder.ProgramFilesX86
126124
: Environment.SpecialFolder.ProgramFiles);
127-
128-
internal static string? GetTfm(Assembly assembly)
129-
{
130-
// We don't support exotic frameworks like Silverlight, WindowsPhone, Xamarin.Mac, etc.
131-
const string CorePrefix = ".NETCoreApp,Version=v";
132-
const string FrameworkPrefix = ".NETFramework,Version=v";
133-
const string StandardPrefix = ".NETStandard,Version=v";
134-
135-
// Look for a TargetFrameworkAttribute with a supported Framework version.
136-
string? framework = assembly.GetCustomAttribute<TargetFrameworkAttribute>()?.FrameworkName;
137-
if (TryParseVersion(CorePrefix, out var version))
138-
{
139-
return version.Major < 5
140-
? $"netcoreapp{version.Major}.{version.Minor}"
141-
: CoreRuntime.TryGetTargetPlatform(assembly, out var platform)
142-
? $"net{version.Major}.{version.Minor}-{platform}"
143-
: $"net{version.Major}.{version.Minor}";
144-
}
145-
if (TryParseVersion(FrameworkPrefix, out version))
146-
{
147-
return version.Build > 0
148-
? $"net{version.Major}{version.Minor}{version.Build}"
149-
: $"net{version.Major}{version.Minor}";
150-
}
151-
if (!TryParseVersion(StandardPrefix, out version))
152-
{
153-
return $"netstandard{version.Major}.{version.Minor}";
154-
}
155-
156-
// TargetFrameworkAttribute not found, or the assembly targeted a framework we don't support.
157-
return null;
158-
159-
bool TryParseVersion(string prefix, [NotNullWhen(true)] out Version? version)
160-
{
161-
version = null;
162-
return framework?.StartsWith(prefix) == true
163-
&& Version.TryParse(framework[prefix.Length..], out version);
164-
}
165-
}
166125
}
167126
}

src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs

Lines changed: 65 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using BenchmarkDotNet.Loggers;
1515
using BenchmarkDotNet.Running;
1616
using BenchmarkDotNet.Toolchains.DotNetCli;
17+
using BenchmarkDotNet.Toolchains.Mono;
1718
using BenchmarkDotNet.Toolchains.Results;
1819
using JetBrains.Annotations;
1920

@@ -71,16 +72,29 @@ protected override void GenerateBuildScript(BuildPartition buildPartition, Artif
7172

7273
var content = new StringBuilder(300)
7374
.AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetRestoreCommand(artifactsPaths, buildPartition, projectFilePath)}")
74-
.AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetPublishCommand(artifactsPaths, buildPartition, projectFilePath, TargetFrameworkMoniker)}")
75+
.AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetBuildCommand(artifactsPaths, buildPartition, projectFilePath, TargetFrameworkMoniker)}")
7576
.AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetRestoreCommand(artifactsPaths, buildPartition, artifactsPaths.ProjectFilePath)}")
76-
.AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetPublishCommand(artifactsPaths, buildPartition, artifactsPaths.ProjectFilePath, TargetFrameworkMoniker)}")
77+
.AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetBuildCommand(artifactsPaths, buildPartition, artifactsPaths.ProjectFilePath, TargetFrameworkMoniker)}")
7778
.ToString();
7879

7980
File.WriteAllText(artifactsPaths.BuildScriptFilePath, content);
8081
}
8182

8283
[SuppressMessage("ReSharper", "StringLiteralTypo")] // R# complains about $variables$
8384
protected override void GenerateProject(BuildPartition buildPartition, ArtifactsPaths artifactsPaths, ILogger logger)
85+
{
86+
File.WriteAllText(artifactsPaths.ProjectFilePath,
87+
GenerateBuildProject(buildPartition, artifactsPaths, logger)
88+
);
89+
90+
// Integration tests are built without dependencies, so we skip gathering dlls.
91+
if (!buildPartition.ForcedNoDependenciesForIntegrationTests)
92+
{
93+
GatherReferences(buildPartition, artifactsPaths, logger);
94+
}
95+
}
96+
97+
private string GenerateBuildProject(BuildPartition buildPartition, ArtifactsPaths artifactsPaths, ILogger logger)
8498
{
8599
var benchmark = buildPartition.RepresentativeBenchmarkCase;
86100
var projectFile = GetProjectFilePath(benchmark.Descriptor.Type, logger);
@@ -89,7 +103,7 @@ protected override void GenerateProject(BuildPartition buildPartition, Artifacts
89103
xmlDoc.Load(projectFile.FullName);
90104
var (customProperties, sdkName) = GetSettingsThatNeedToBeCopied(xmlDoc, projectFile);
91105

92-
var content = new StringBuilder(ResourceHelper.LoadTemplate("CsProj.txt"))
106+
return new StringBuilder(ResourceHelper.LoadTemplate("CsProj.txt"))
93107
.Replace("$PLATFORM$", buildPartition.Platform.ToConfig())
94108
.Replace("$CODEFILENAME$", Path.GetFileName(artifactsPaths.ProgramCodePath))
95109
.Replace("$CSPROJPATH$", projectFile.FullName)
@@ -99,43 +113,62 @@ protected override void GenerateProject(BuildPartition buildPartition, Artifacts
99113
.Replace("$COPIEDSETTINGS$", customProperties)
100114
.Replace("$SDKNAME$", sdkName)
101115
.ToString();
102-
103-
File.WriteAllText(artifactsPaths.ProjectFilePath, content);
104-
105-
// Integration tests are built without dependencies, so we skip gathering dlls.
106-
if (!buildPartition.ForcedNoDependenciesForIntegrationTests)
107-
{
108-
GatherReferences(projectFile.FullName, buildPartition, artifactsPaths, logger);
109-
}
110116
}
111117

112-
protected void GatherReferences(string projectFilePath, BuildPartition buildPartition, ArtifactsPaths artifactsPaths, ILogger logger)
118+
private static string GetDllGathererPath(string filePath)
119+
=> Path.Combine(Path.GetDirectoryName(filePath), $"DllGatherer{Path.GetExtension(filePath)}");
120+
121+
protected void GatherReferences(BuildPartition buildPartition, ArtifactsPaths artifactsPaths, ILogger logger)
113122
{
114-
// Build the original project then reference all of the built dlls.
115-
BuildResult buildResult = BuildProject(TargetFrameworkMoniker);
123+
// Create a project using the default template to build the original project for all necessary runtime dlls.
124+
// We can't just build the original project directly because it could be a library project, so we need an exe project to reference it.
125+
var xmlDoc = new XmlDocument();
126+
xmlDoc.LoadXml(GenerateBuildProject(buildPartition, artifactsPaths, logger));
127+
var projectElement = xmlDoc.DocumentElement;
128+
129+
// Replace the default C# file with an empty Main method to satisfy the exe build.
130+
var compileNode = projectElement.SelectSingleNode("ItemGroup/Compile");
131+
string emptyMainFile = GetDllGathererPath(artifactsPaths.ProgramCodePath);
132+
compileNode.Attributes["Include"].Value = emptyMainFile;
133+
string gathererProject = GetDllGathererPath(artifactsPaths.ProjectFilePath);
134+
xmlDoc.Save(gathererProject);
135+
136+
File.WriteAllText(emptyMainFile, """
137+
namespace BenchmarkDotNet.Autogenerated
138+
{
139+
public class UniqueProgramName
140+
{
141+
public static int Main(string[] args)
142+
{
143+
return 0;
144+
}
145+
}
146+
}
147+
""");
116148

117-
// The build could fail because the project doesn't have a tfm that matches the runtime, e.g. netstandard2.0 vs net10.0,
118-
// So we try to get the actual tfm of the assembly and build again.
119-
if (!buildResult.IsBuildSuccess
120-
&& FrameworkVersionHelper.GetTfm(buildPartition.RepresentativeBenchmarkCase.Descriptor.Type.Assembly) is { } actualTfm
121-
&& actualTfm != TargetFrameworkMoniker)
122-
{
123-
buildResult = BuildProject(actualTfm);
124-
}
149+
// Build the original project then reference all of the built dlls.
150+
BuildResult buildResult = new DotNetCliCommand(
151+
CliPath,
152+
gathererProject,
153+
TargetFrameworkMoniker,
154+
null,
155+
GenerateResult.Success(artifactsPaths, []),
156+
logger,
157+
buildPartition,
158+
[],
159+
buildPartition.Timeout
160+
).RestoreThenBuild();
125161

126162
if (!buildResult.IsBuildSuccess)
127163
{
128-
if (!buildResult.TryToExplainFailureReason(out string reason))
129-
{
130-
reason = buildResult.ErrorMessage;
131-
}
132-
logger.WriteLineWarning($"Failed to build source project to obtain dll references. Moving forward without it. Reason: {reason}");
133-
return;
164+
throw buildResult.TryToExplainFailureReason(out string reason)
165+
? new Exception(reason)
166+
: new Exception(buildResult.ErrorMessage);
134167
}
135168

136-
var xmlDoc = new XmlDocument();
169+
xmlDoc = new XmlDocument();
137170
xmlDoc.Load(artifactsPaths.ProjectFilePath);
138-
XmlElement projectElement = xmlDoc.DocumentElement;
171+
projectElement = xmlDoc.DocumentElement;
139172
var itemGroup = xmlDoc.CreateElement("ItemGroup");
140173
projectElement.AppendChild(itemGroup);
141174
foreach (var assemblyFile in Directory.GetFiles(artifactsPaths.BinariesDirectoryPath, "*.dll"))
@@ -151,26 +184,13 @@ protected void GatherReferences(string projectFilePath, BuildPartition buildPart
151184
}
152185

153186
// Mono80IsSupported test fails when BenchmarkDotNet is restored for net9.0 if we don't remove the ProjectReference.
154-
if (XUnitHelper.IsIntegrationTest.Value)
187+
// We still need to preserve the ProjectReference in every other case for disassembly, though.
188+
if (XUnitHelper.IsIntegrationTest.Value && this is MonoGenerator)
155189
{
156190
projectElement.RemoveChild(projectElement.SelectSingleNode("ItemGroup/ProjectReference").ParentNode);
157191
}
158192

159193
xmlDoc.Save(artifactsPaths.ProjectFilePath);
160-
161-
BuildResult BuildProject(string tfm)
162-
=> new DotNetCliCommand(
163-
CliPath,
164-
projectFilePath,
165-
tfm,
166-
null,
167-
GenerateResult.Success(artifactsPaths, []),
168-
logger,
169-
buildPartition,
170-
[],
171-
buildPartition.Timeout
172-
)
173-
.RestoreThenBuild();
174194
}
175195

176196
/// <summary>

src/BenchmarkDotNet/Toolchains/MonoAotLLVM/MonoAotLLVMGenerator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ protected override void GenerateProject(BuildPartition buildPartition, Artifacts
5252

5353
File.WriteAllText(artifactsPaths.ProjectFilePath, content);
5454

55-
GatherReferences(projectFile.FullName, buildPartition, artifactsPaths, logger);
55+
GatherReferences(buildPartition, artifactsPaths, logger);
5656
}
5757

5858
protected override string GetPublishDirectoryPath(string buildArtifactsDirectoryPath, string configuration)

src/BenchmarkDotNet/Toolchains/MonoWasm/WasmGenerator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ protected void GenerateProjectFile(BuildPartition buildPartition, ArtifactsPaths
6363

6464
File.WriteAllText(artifactsPaths.ProjectFilePath, content);
6565

66-
GatherReferences(projectFile.FullName, buildPartition, artifactsPaths, logger);
66+
GatherReferences(buildPartition, artifactsPaths, logger);
6767
}
6868

6969
protected void GenerateLinkerDescriptionFile(ArtifactsPaths artifactsPaths)

src/BenchmarkDotNet/Toolchains/NativeAot/Generator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ protected override void GenerateProject(BuildPartition buildPartition, Artifacts
119119

120120
File.WriteAllText(artifactsPaths.ProjectFilePath, GenerateProjectForNuGetBuild(projectFile, buildPartition, artifactsPaths, logger));
121121

122-
GatherReferences(projectFile, buildPartition, artifactsPaths, logger);
122+
GatherReferences(buildPartition, artifactsPaths, logger);
123123
GenerateReflectionFile(artifactsPaths);
124124
}
125125

tests/BenchmarkDotNet.IntegrationTests/DisassemblyDiagnoserTests.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,14 @@ private IConfig CreateConfig(Jit jit, Platform platform, IToolchain toolchain, I
173173
.AddJob(Job.Dry.WithJit(jit)
174174
.WithPlatform(platform)
175175
.WithToolchain(toolchain)
176-
.WithStrategy(runStrategy))
176+
.WithStrategy(runStrategy)
177+
// Ensure the build goes through the full process and doesn't build without dependencies like most of the integration tests do.
178+
#if RELEASE
179+
.WithCustomBuildConfiguration("Release")
180+
#else
181+
.WithCustomBuildConfiguration("Debug")
182+
#endif
183+
)
177184
.AddLogger(DefaultConfig.Instance.GetLoggers().ToArray())
178185
.AddColumnProvider(DefaultColumnProviders.Instance)
179186
.AddDiagnoser(disassemblyDiagnoser)

0 commit comments

Comments
 (0)