Skip to content
Merged
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
9 changes: 3 additions & 6 deletions src/BenchmarkDotNet/Environments/Runtimes/CoreRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ private static CoreRuntime GetPlatformSpecific(CoreRuntime fallback, Assembly? a
? new CoreRuntime(fallback.RuntimeMoniker, $"{fallback.MsBuildMoniker}-{platform}", fallback.Name)
: fallback;

internal static bool TryGetTargetPlatform(Assembly? assembly, [NotNullWhen(true)] out string? platform)
private static bool TryGetTargetPlatform(Assembly? assembly, [NotNullWhen(true)] out string? platform)
{
platform = null;

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

if (platformNameProperty.GetValue(attributeInstance) is not string platformName)
return false;

platform = platformName;
return true;
platform = platformNameProperty.GetValue(attributeInstance) as string;
return platform.IsNotBlank();
}
}
}
41 changes: 0 additions & 41 deletions src/BenchmarkDotNet/Helpers/FrameworkVersionHelper.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Versioning;
using BenchmarkDotNet.Environments;
using Microsoft.Win32;

namespace BenchmarkDotNet.Helpers
Expand Down Expand Up @@ -124,44 +122,5 @@ private static bool IsDeveloperPackInstalled(string version) => Directory.Exists
Environment.Is64BitOperatingSystem
? Environment.SpecialFolder.ProgramFilesX86
: Environment.SpecialFolder.ProgramFiles);

internal static string? GetTfm(Assembly assembly)
{
// We don't support exotic frameworks like Silverlight, WindowsPhone, Xamarin.Mac, etc.
const string CorePrefix = ".NETCoreApp,Version=v";
const string FrameworkPrefix = ".NETFramework,Version=v";
const string StandardPrefix = ".NETStandard,Version=v";

// Look for a TargetFrameworkAttribute with a supported Framework version.
string? framework = assembly.GetCustomAttribute<TargetFrameworkAttribute>()?.FrameworkName;
if (TryParseVersion(CorePrefix, out var version))
{
return version.Major < 5
? $"netcoreapp{version.Major}.{version.Minor}"
: CoreRuntime.TryGetTargetPlatform(assembly, out var platform)
? $"net{version.Major}.{version.Minor}-{platform}"
: $"net{version.Major}.{version.Minor}";
}
if (TryParseVersion(FrameworkPrefix, out version))
{
return version.Build > 0
? $"net{version.Major}{version.Minor}{version.Build}"
: $"net{version.Major}{version.Minor}";
}
if (!TryParseVersion(StandardPrefix, out version))
{
return $"netstandard{version.Major}.{version.Minor}";
}

// TargetFrameworkAttribute not found, or the assembly targeted a framework we don't support.
return null;

bool TryParseVersion(string prefix, [NotNullWhen(true)] out Version? version)
{
version = null;
return framework?.StartsWith(prefix) == true
&& Version.TryParse(framework[prefix.Length..], out version);
}
}
}
}
110 changes: 65 additions & 45 deletions src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Toolchains.DotNetCli;
using BenchmarkDotNet.Toolchains.Mono;
using BenchmarkDotNet.Toolchains.Results;
using JetBrains.Annotations;

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

var content = new StringBuilder(300)
.AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetRestoreCommand(artifactsPaths, buildPartition, projectFilePath)}")
.AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetPublishCommand(artifactsPaths, buildPartition, projectFilePath, TargetFrameworkMoniker)}")
.AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetBuildCommand(artifactsPaths, buildPartition, projectFilePath, TargetFrameworkMoniker)}")
.AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetRestoreCommand(artifactsPaths, buildPartition, artifactsPaths.ProjectFilePath)}")
.AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetPublishCommand(artifactsPaths, buildPartition, artifactsPaths.ProjectFilePath, TargetFrameworkMoniker)}")
.AppendLine($"call {CliPath ?? "dotnet"} {DotNetCliCommand.GetBuildCommand(artifactsPaths, buildPartition, artifactsPaths.ProjectFilePath, TargetFrameworkMoniker)}")
.ToString();

File.WriteAllText(artifactsPaths.BuildScriptFilePath, content);
}

[SuppressMessage("ReSharper", "StringLiteralTypo")] // R# complains about $variables$
protected override void GenerateProject(BuildPartition buildPartition, ArtifactsPaths artifactsPaths, ILogger logger)
{
File.WriteAllText(artifactsPaths.ProjectFilePath,
GenerateBuildProject(buildPartition, artifactsPaths, logger)
);

// Integration tests are built without dependencies, so we skip gathering dlls.
if (!buildPartition.ForcedNoDependenciesForIntegrationTests)
{
GatherReferences(buildPartition, artifactsPaths, logger);
}
}

private string GenerateBuildProject(BuildPartition buildPartition, ArtifactsPaths artifactsPaths, ILogger logger)
{
var benchmark = buildPartition.RepresentativeBenchmarkCase;
var projectFile = GetProjectFilePath(benchmark.Descriptor.Type, logger);
Expand All @@ -89,7 +103,7 @@ protected override void GenerateProject(BuildPartition buildPartition, Artifacts
xmlDoc.Load(projectFile.FullName);
var (customProperties, sdkName) = GetSettingsThatNeedToBeCopied(xmlDoc, projectFile);

var content = new StringBuilder(ResourceHelper.LoadTemplate("CsProj.txt"))
return new StringBuilder(ResourceHelper.LoadTemplate("CsProj.txt"))
.Replace("$PLATFORM$", buildPartition.Platform.ToConfig())
.Replace("$CODEFILENAME$", Path.GetFileName(artifactsPaths.ProgramCodePath))
.Replace("$CSPROJPATH$", projectFile.FullName)
Expand All @@ -99,43 +113,62 @@ protected override void GenerateProject(BuildPartition buildPartition, Artifacts
.Replace("$COPIEDSETTINGS$", customProperties)
.Replace("$SDKNAME$", sdkName)
.ToString();

File.WriteAllText(artifactsPaths.ProjectFilePath, content);

// Integration tests are built without dependencies, so we skip gathering dlls.
if (!buildPartition.ForcedNoDependenciesForIntegrationTests)
{
GatherReferences(projectFile.FullName, buildPartition, artifactsPaths, logger);
}
}

protected void GatherReferences(string projectFilePath, BuildPartition buildPartition, ArtifactsPaths artifactsPaths, ILogger logger)
private static string GetDllGathererPath(string filePath)
=> Path.Combine(Path.GetDirectoryName(filePath), $"DllGatherer{Path.GetExtension(filePath)}");

protected void GatherReferences(BuildPartition buildPartition, ArtifactsPaths artifactsPaths, ILogger logger)
{
// Build the original project then reference all of the built dlls.
BuildResult buildResult = BuildProject(TargetFrameworkMoniker);
// Create a project using the default template to build the original project for all necessary runtime dlls.
// 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.
var xmlDoc = new XmlDocument();
xmlDoc.LoadXml(GenerateBuildProject(buildPartition, artifactsPaths, logger));
var projectElement = xmlDoc.DocumentElement;

// Replace the default C# file with an empty Main method to satisfy the exe build.
var compileNode = projectElement.SelectSingleNode("ItemGroup/Compile");
string emptyMainFile = GetDllGathererPath(artifactsPaths.ProgramCodePath);
compileNode.Attributes["Include"].Value = emptyMainFile;
string gathererProject = GetDllGathererPath(artifactsPaths.ProjectFilePath);
xmlDoc.Save(gathererProject);

File.WriteAllText(emptyMainFile, """
namespace BenchmarkDotNet.Autogenerated
{
public class UniqueProgramName
{
public static int Main(string[] args)
{
return 0;
}
}
}
""");

// The build could fail because the project doesn't have a tfm that matches the runtime, e.g. netstandard2.0 vs net10.0,
// So we try to get the actual tfm of the assembly and build again.
if (!buildResult.IsBuildSuccess
&& FrameworkVersionHelper.GetTfm(buildPartition.RepresentativeBenchmarkCase.Descriptor.Type.Assembly) is { } actualTfm
&& actualTfm != TargetFrameworkMoniker)
{
buildResult = BuildProject(actualTfm);
}
// Build the original project then reference all of the built dlls.
BuildResult buildResult = new DotNetCliCommand(
CliPath,
gathererProject,
TargetFrameworkMoniker,
null,
GenerateResult.Success(artifactsPaths, []),
logger,
buildPartition,
[],
buildPartition.Timeout
).RestoreThenBuild();

if (!buildResult.IsBuildSuccess)
{
if (!buildResult.TryToExplainFailureReason(out string reason))
{
reason = buildResult.ErrorMessage;
}
logger.WriteLineWarning($"Failed to build source project to obtain dll references. Moving forward without it. Reason: {reason}");
return;
throw buildResult.TryToExplainFailureReason(out string reason)
? new Exception(reason)
: new Exception(buildResult.ErrorMessage);
}

var xmlDoc = new XmlDocument();
xmlDoc = new XmlDocument();
xmlDoc.Load(artifactsPaths.ProjectFilePath);
XmlElement projectElement = xmlDoc.DocumentElement;
projectElement = xmlDoc.DocumentElement;
var itemGroup = xmlDoc.CreateElement("ItemGroup");
projectElement.AppendChild(itemGroup);
foreach (var assemblyFile in Directory.GetFiles(artifactsPaths.BinariesDirectoryPath, "*.dll"))
Expand All @@ -151,26 +184,13 @@ protected void GatherReferences(string projectFilePath, BuildPartition buildPart
}

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

xmlDoc.Save(artifactsPaths.ProjectFilePath);

BuildResult BuildProject(string tfm)
=> new DotNetCliCommand(
CliPath,
projectFilePath,
tfm,
null,
GenerateResult.Success(artifactsPaths, []),
logger,
buildPartition,
[],
buildPartition.Timeout
)
.RestoreThenBuild();
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ protected override void GenerateProject(BuildPartition buildPartition, Artifacts

File.WriteAllText(artifactsPaths.ProjectFilePath, content);

GatherReferences(projectFile.FullName, buildPartition, artifactsPaths, logger);
GatherReferences(buildPartition, artifactsPaths, logger);
}

protected override string GetPublishDirectoryPath(string buildArtifactsDirectoryPath, string configuration)
Expand Down
2 changes: 1 addition & 1 deletion src/BenchmarkDotNet/Toolchains/MonoWasm/WasmGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ protected void GenerateProjectFile(BuildPartition buildPartition, ArtifactsPaths

File.WriteAllText(artifactsPaths.ProjectFilePath, content);

GatherReferences(projectFile.FullName, buildPartition, artifactsPaths, logger);
GatherReferences(buildPartition, artifactsPaths, logger);
}

protected void GenerateLinkerDescriptionFile(ArtifactsPaths artifactsPaths)
Expand Down
2 changes: 1 addition & 1 deletion src/BenchmarkDotNet/Toolchains/NativeAot/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ protected override void GenerateProject(BuildPartition buildPartition, Artifacts

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

GatherReferences(projectFile, buildPartition, artifactsPaths, logger);
GatherReferences(buildPartition, artifactsPaths, logger);
GenerateReflectionFile(artifactsPaths);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,14 @@ private IConfig CreateConfig(Jit jit, Platform platform, IToolchain toolchain, I
.AddJob(Job.Dry.WithJit(jit)
.WithPlatform(platform)
.WithToolchain(toolchain)
.WithStrategy(runStrategy))
.WithStrategy(runStrategy)
// Ensure the build goes through the full process and doesn't build without dependencies like most of the integration tests do.
#if RELEASE
.WithCustomBuildConfiguration("Release")
#else
.WithCustomBuildConfiguration("Debug")
#endif
)
.AddLogger(DefaultConfig.Instance.GetLoggers().ToArray())
.AddColumnProvider(DefaultColumnProviders.Instance)
.AddDiagnoser(disassemblyDiagnoser)
Expand Down