Skip to content

Commit bd5ec34

Browse files
authored
Gather assembly references (#2508)
* Split builds into 2 steps to gather assembly references. * Build source project directly. * Use <TargetFrameworks> property in templates. * Re-add MonoPublisher. * Look for TargetFrameworkAttribute for CoreRuntime. * Simplify TargetFrameworkAttribute checks. * Fail GatherReferences gracefully. * Build again with actual tfm if the first build failed. * Added TODO comment. * Add platform to fallback tfm. * Fix compile errors. * Remove unused method.
1 parent ed24f0f commit bd5ec34

22 files changed

+323
-174
lines changed

src/BenchmarkDotNet/Diagnosers/PerfCollectProfiler.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,11 +230,13 @@ private void EnsureSymbolsForNativeRuntime(DiagnoserActionParameters parameters)
230230
string toolPath = Path.Combine(Path.GetTempPath(), "BenchmarkDotNet", "symbols");
231231
DotNetCliCommand cliCommand = new(
232232
cliPath: cliPath,
233+
filePath: string.Empty,
234+
tfm: string.Empty,
233235
arguments: $"tool install dotnet-symbol --tool-path \"{toolPath}\"",
234236
generateResult: null,
235237
logger: logger,
236238
buildPartition: null,
237-
environmentVariables: Array.Empty<EnvironmentVariable>(),
239+
environmentVariables: [],
238240
timeout: TimeSpan.FromMinutes(3),
239241
logOutput: true); // the following commands might take a while and fail, let's log them
240242

src/BenchmarkDotNet/Environments/Runtimes/CoreRuntime.cs

Lines changed: 44 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
using System;
22
using System.Diagnostics;
3+
using System.Diagnostics.CodeAnalysis;
34
using System.IO;
45
using System.Linq;
56
using System.Reflection;
67
using System.Runtime.Versioning;
8+
using BenchmarkDotNet.Helpers;
79
using BenchmarkDotNet.Extensions;
810
using BenchmarkDotNet.Jobs;
911
using BenchmarkDotNet.Portability;
@@ -47,6 +49,13 @@ public static CoreRuntime CreateForNewVersion(string msBuildMoniker, string disp
4749
return new CoreRuntime(RuntimeMoniker.NotRecognized, msBuildMoniker, displayName);
4850
}
4951

52+
internal static CoreRuntime GetTargetOrCurrentVersion(Assembly? assembly)
53+
// Try to determine the version that the assembly was compiled for.
54+
=> FrameworkVersionHelper.GetTargetCoreVersion(assembly) is { } version
55+
? FromVersion(version, assembly)
56+
// Fallback to the current running version.
57+
: GetCurrentVersion();
58+
5059
internal static CoreRuntime GetCurrentVersion()
5160
{
5261
if (!RuntimeInformation.IsNetCore)
@@ -59,28 +68,24 @@ internal static CoreRuntime GetCurrentVersion()
5968
throw new NotSupportedException("Unable to recognize .NET Core version, please report a bug at https://github.com/dotnet/BenchmarkDotNet");
6069
}
6170

62-
return FromVersion(version);
71+
return FromVersion(version, null);
6372
}
6473

65-
internal static CoreRuntime FromVersion(Version version)
74+
internal static CoreRuntime FromVersion(Version version, Assembly? assembly = null) => version switch
6675
{
67-
switch (version)
68-
{
69-
case Version v when v.Major == 2 && v.Minor == 0: return Core20;
70-
case Version v when v.Major == 2 && v.Minor == 1: return Core21;
71-
case Version v when v.Major == 2 && v.Minor == 2: return Core22;
72-
case Version v when v.Major == 3 && v.Minor == 0: return Core30;
73-
case Version v when v.Major == 3 && v.Minor == 1: return Core31;
74-
case Version v when v.Major == 5 && v.Minor == 0: return GetPlatformSpecific(Core50);
75-
case Version v when v.Major == 6 && v.Minor == 0: return GetPlatformSpecific(Core60);
76-
case Version v when v.Major == 7 && v.Minor == 0: return GetPlatformSpecific(Core70);
77-
case Version v when v.Major == 8 && v.Minor == 0: return GetPlatformSpecific(Core80);
78-
case Version v when v.Major == 9 && v.Minor == 0: return GetPlatformSpecific(Core90);
79-
case Version v when v.Major == 10 && v.Minor == 0: return GetPlatformSpecific(Core10_0);
80-
default:
81-
return CreateForNewVersion($"net{version.Major}.{version.Minor}", $".NET {version.Major}.{version.Minor}");
82-
}
83-
}
76+
{ Major: 2, Minor: 0 } => Core20,
77+
{ Major: 2, Minor: 1 } => Core21,
78+
{ Major: 2, Minor: 2 } => Core22,
79+
{ Major: 3, Minor: 0 } => Core30,
80+
{ Major: 3, Minor: 1 } => Core31,
81+
{ Major: 5 } => GetPlatformSpecific(Core50, assembly),
82+
{ Major: 6 } => GetPlatformSpecific(Core60, assembly),
83+
{ Major: 7 } => GetPlatformSpecific(Core70, assembly),
84+
{ Major: 8 } => GetPlatformSpecific(Core80, assembly),
85+
{ Major: 9 } => GetPlatformSpecific(Core90, assembly),
86+
{ Major: 10 } => GetPlatformSpecific(Core10_0, assembly),
87+
_ => CreateForNewVersion($"net{version.Major}.{version.Minor}", $".NET {version.Major}.{version.Minor}"),
88+
};
8489

8590
internal static bool TryGetVersion(out Version? version)
8691
{
@@ -220,29 +225,36 @@ internal static bool TryGetVersionFromFrameworkName(string frameworkName, out Ve
220225
// Version.TryParse does not handle thing like 3.0.0-WORD
221226
internal static string GetParsableVersionPart(string fullVersionName) => new string(fullVersionName.TakeWhile(c => char.IsDigit(c) || c == '.').ToArray());
222227

223-
private static CoreRuntime GetPlatformSpecific(CoreRuntime fallback)
228+
private static CoreRuntime GetPlatformSpecific(CoreRuntime fallback, Assembly? assembly)
229+
=> TryGetTargetPlatform(assembly ?? Assembly.GetEntryAssembly(), out var platform)
230+
? new CoreRuntime(fallback.RuntimeMoniker, $"{fallback.MsBuildMoniker}-{platform}", fallback.Name)
231+
: fallback;
232+
233+
internal static bool TryGetTargetPlatform(Assembly? assembly, [NotNullWhen(true)] out string? platform)
224234
{
225-
// TargetPlatformAttribute is not part of .NET Standard 2.0 so as usuall we have to use some reflection hacks...
235+
platform = null;
236+
237+
if (assembly is null)
238+
return false;
239+
240+
// TargetPlatformAttribute is not part of .NET Standard 2.0 so as usual we have to use some reflection hacks.
226241
var targetPlatformAttributeType = typeof(object).Assembly.GetType("System.Runtime.Versioning.TargetPlatformAttribute", throwOnError: false);
227242
if (targetPlatformAttributeType is null) // an old preview version of .NET 5
228-
return fallback;
229-
230-
var exe = Assembly.GetEntryAssembly();
231-
if (exe is null)
232-
return fallback;
243+
return false;
233244

234-
var attributeInstance = exe.GetCustomAttribute(targetPlatformAttributeType);
245+
var attributeInstance = assembly.GetCustomAttribute(targetPlatformAttributeType);
235246
if (attributeInstance is null)
236-
return fallback;
247+
return false;
237248

238249
var platformNameProperty = targetPlatformAttributeType.GetProperty("PlatformName");
239250
if (platformNameProperty is null)
240-
return fallback;
251+
return false;
241252

242-
if (!(platformNameProperty.GetValue(attributeInstance) is string platformName))
243-
return fallback;
253+
if (platformNameProperty.GetValue(attributeInstance) is not string platformName)
254+
return false;
244255

245-
return new CoreRuntime(fallback.RuntimeMoniker, $"{fallback.MsBuildMoniker}-{platformName}", fallback.Name);
256+
platform = platformName;
257+
return true;
246258
}
247259
}
248260
}

src/BenchmarkDotNet/Helpers/FrameworkVersionHelper.cs

Lines changed: 67 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
using System;
2+
using System.Diagnostics.CodeAnalysis;
23
using System.IO;
34
using System.Linq;
45
using System.Reflection;
56
using System.Runtime.Versioning;
7+
using BenchmarkDotNet.Environments;
68
using Microsoft.Win32;
79

810
namespace BenchmarkDotNet.Helpers
@@ -23,28 +25,38 @@ private static readonly (int minReleaseNumber, string version)[] FrameworkVersio
2325
];
2426

2527
internal static string? GetTargetFrameworkVersion(Assembly? assembly)
26-
{
27-
if (assembly is null)
28+
// Look for a TargetFrameworkAttribute with a supported Framework version.
29+
=> assembly?.GetCustomAttribute<TargetFrameworkAttribute>()?.FrameworkName switch
2830
{
29-
return null;
30-
}
31+
".NETFramework,Version=v4.6.1" => "4.6.1",
32+
".NETFramework,Version=v4.6.2" => "4.6.2",
33+
".NETFramework,Version=v4.7" => "4.7",
34+
".NETFramework,Version=v4.7.1" => "4.7.1",
35+
".NETFramework,Version=v4.7.2" => "4.7.2",
36+
".NETFramework,Version=v4.8" => "4.8",
37+
".NETFramework,Version=v4.8.1" => "4.8.1",
38+
// Null assembly, or TargetFrameworkAttribute not found, or the assembly targeted a version older than we support,
39+
// or the assembly targeted a non-framework tfm (like netstandard2.0).
40+
_ => null,
41+
};
42+
43+
internal static Version? GetTargetCoreVersion(Assembly? assembly)
44+
{
45+
//.NETCoreApp,Version=vX.Y
46+
const string FrameworkPrefix = ".NETCoreApp,Version=v";
3147

3248
// Look for a TargetFrameworkAttribute with a supported Framework version.
33-
foreach (var attribute in assembly.GetCustomAttributes<TargetFrameworkAttribute>())
49+
string? framework = assembly?.GetCustomAttribute<TargetFrameworkAttribute>()?.FrameworkName;
50+
if (framework?.StartsWith(FrameworkPrefix) == true
51+
&& Version.TryParse(framework[FrameworkPrefix.Length..], out var version)
52+
// We don't support netcoreapp1.X
53+
&& version.Major >= 2)
3454
{
35-
switch (attribute.FrameworkName)
36-
{
37-
case ".NETFramework,Version=v4.6.1": return "4.6.1";
38-
case ".NETFramework,Version=v4.6.2": return "4.6.2";
39-
case ".NETFramework,Version=v4.7": return "4.7";
40-
case ".NETFramework,Version=v4.7.1": return "4.7.1";
41-
case ".NETFramework,Version=v4.7.2": return "4.7.2";
42-
case ".NETFramework,Version=v4.8": return "4.8";
43-
case ".NETFramework,Version=v4.8.1": return "4.8.1";
44-
}
55+
return version;
4556
}
4657

47-
// TargetFrameworkAttribute not found, or the assembly targeted a version older than we support.
58+
// Null assembly, or TargetFrameworkAttribute not found, or the assembly targeted a version older than we support,
59+
// or the assembly targeted a non-core tfm (like netstandard2.0).
4860
return null;
4961
}
5062

@@ -112,5 +124,44 @@ private static bool IsDeveloperPackInstalled(string version) => Directory.Exists
112124
Environment.Is64BitOperatingSystem
113125
? Environment.SpecialFolder.ProgramFilesX86
114126
: 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+
}
115166
}
116167
}

src/BenchmarkDotNet/Portability/RuntimeInformation.cs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -171,9 +171,18 @@ string GetDetailedVersion()
171171
}
172172

173173
internal static Runtime GetTargetOrCurrentRuntime(Assembly? assembly)
174-
=> !IsMono && !IsWasm && IsFullFramework // Match order of checks in GetCurrentRuntime().
175-
? ClrRuntime.GetTargetOrCurrentVersion(assembly)
176-
: GetCurrentRuntime();
174+
{
175+
// Match order of checks in GetCurrentRuntime().
176+
if (!IsMono && !IsWasm)
177+
{
178+
if (IsFullFramework)
179+
return ClrRuntime.GetTargetOrCurrentVersion(assembly);
180+
// 99% of the time the core runtime is the same as the target framework, but the runtime could roll forward if it's not self-contained.
181+
if (IsNetCore)
182+
return CoreRuntime.GetTargetOrCurrentVersion(assembly);
183+
}
184+
return GetCurrentRuntime();
185+
}
177186

178187
internal static Runtime GetCurrentRuntime()
179188
{

src/BenchmarkDotNet/Templates/CsProj.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<ImportDirectoryBuildProps>false</ImportDirectoryBuildProps>
55
<ImportDirectoryBuildTargets>false</ImportDirectoryBuildTargets>
66
<AssemblyTitle>$PROGRAMNAME$</AssemblyTitle>
7-
<TargetFramework>$TFM$</TargetFramework>
7+
<TargetFrameworks>$TFM$</TargetFrameworks>
88
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
99
<PlatformTarget>$PLATFORM$</PlatformTarget>
1010
<AssemblyName>$PROGRAMNAME$</AssemblyName>

src/BenchmarkDotNet/Templates/MonoAOTLLVMCsProj.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
<PropertyGroup>
1111
<OutputType>Exe</OutputType>
12-
<TargetFramework>$TFM$</TargetFramework>
12+
<TargetFrameworks>$TFM$</TargetFrameworks>
1313
<MicrosoftNetCoreAppRuntimePackDir>$RUNTIMEPACK$</MicrosoftNetCoreAppRuntimePackDir>
1414
<RuntimeIdentifier>$RUNTIMEIDENTIFIER$</RuntimeIdentifier>
1515
<EnableTargetingPackDownload>false</EnableTargetingPackDownload>

src/BenchmarkDotNet/Templates/WasmCsProj.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<OutputType>Exe</OutputType>
1414
<RuntimeConfig>Release</RuntimeConfig>
1515
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
16-
<TargetFramework>$TFM$</TargetFramework>
16+
<TargetFrameworks>$TFM$</TargetFrameworks>
1717
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
1818
<AppDir>$(PublishDir)</AppDir>
1919
<AssemblyName>$PROGRAMNAME$</AssemblyName>

src/BenchmarkDotNet/Toolchains/ArtifactsPaths.cs

Lines changed: 1 addition & 1 deletion
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; }

src/BenchmarkDotNet/Toolchains/CoreRun/CoreRunPublisher.cs

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,11 @@
99

1010
namespace BenchmarkDotNet.Toolchains.CoreRun
1111
{
12-
public class CoreRunPublisher : IBuilder
12+
public class CoreRunPublisher(string tfm, FileInfo coreRun, FileInfo? customDotNetCliPath = null) : DotNetCliPublisher(tfm, customDotNetCliPath?.FullName)
1313
{
14-
public CoreRunPublisher(FileInfo coreRun, FileInfo? customDotNetCliPath = null)
14+
public override BuildResult Build(GenerateResult generateResult, BuildPartition buildPartition, ILogger logger)
1515
{
16-
CoreRun = coreRun;
17-
DotNetCliPublisher = new DotNetCliPublisher(customDotNetCliPath?.FullName);
18-
}
19-
20-
private FileInfo CoreRun { get; }
21-
22-
private DotNetCliPublisher DotNetCliPublisher { get; }
23-
24-
public BuildResult Build(GenerateResult generateResult, BuildPartition buildPartition, ILogger logger)
25-
{
26-
var buildResult = DotNetCliPublisher.Build(generateResult, buildPartition, logger);
16+
var buildResult = base.Build(generateResult, buildPartition, logger);
2717

2818
if (buildResult.IsBuildSuccess)
2919
UpdateDuplicatedDependencies(buildResult.ArtifactsPaths, logger);
@@ -37,7 +27,7 @@ public BuildResult Build(GenerateResult generateResult, BuildPartition buildPart
3727
private void UpdateDuplicatedDependencies(ArtifactsPaths artifactsPaths, ILogger logger)
3828
{
3929
var publishedDirectory = new DirectoryInfo(artifactsPaths.BinariesDirectoryPath);
40-
var coreRunDirectory = CoreRun.Directory;
30+
var coreRunDirectory = coreRun.Directory;
4131

4232
foreach (var publishedDependency in publishedDirectory
4333
.EnumerateFileSystemInfos()

src/BenchmarkDotNet/Toolchains/CoreRun/CoreRunToolchain.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public CoreRunToolchain(FileInfo coreRun, bool createCopy = true,
3434

3535
Name = displayName;
3636
Generator = new CoreRunGenerator(SourceCoreRun, CopyCoreRun, targetFrameworkMoniker, customDotNetCliPath?.FullName, restorePath?.FullName);
37-
Builder = new CoreRunPublisher(CopyCoreRun, customDotNetCliPath);
37+
Builder = new CoreRunPublisher(targetFrameworkMoniker, CopyCoreRun, customDotNetCliPath);
3838
Executor = new DotNetCliExecutor(customDotNetCliPath: CopyCoreRun.FullName); // instead of executing "dotnet $pathToDll" we do "CoreRun $pathToDll"
3939
}
4040

0 commit comments

Comments
 (0)