Skip to content

Sdk Validation Fix #2511

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

Closed
wants to merge 11 commits into from
4 changes: 2 additions & 2 deletions src/BenchmarkDotNet/Diagnosers/PerfCollectProfiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,8 @@ private void EnsureSymbolsForNativeRuntime(DiagnoserActionParameters parameters)

string cliPath = parameters.BenchmarkCase.GetToolchain() switch
{
CsProjCoreToolchain core => core.CustomDotNetCliPath,
NativeAotToolchain nativeAot => nativeAot.CustomDotNetCliPath,
CsProjCoreToolchain core => core.SdkProvider.CustomDotNetCliPath,
NativeAotToolchain nativeAot => nativeAot.SdkProvider.CustomDotNetCliPath,
_ => DotNetCliCommandExecutor.DefaultDotNetCliPath.Value
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ private CsProjClassicNetToolchain(string targetFrameworkMoniker, string name, st
: base(name,
new CsProjGenerator(targetFrameworkMoniker, customDotNetCliPath, packagesPath, runtimeFrameworkVersion: null, isNetCore: false),
new DotNetCliBuilder(targetFrameworkMoniker, customDotNetCliPath),
new Executor())
new Executor(),
new DotNetSdkProvider())
{
CustomDotNetCliPath = customDotNetCliPath;
}
Expand Down
16 changes: 9 additions & 7 deletions src/BenchmarkDotNet/Toolchains/CsProj/CsProjCoreToolchain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,23 @@ public class CsProjCoreToolchain : Toolchain, IEquatable<CsProjCoreToolchain>
[PublicAPI] public static readonly IToolchain NetCoreApp80 = From(NetCoreAppSettings.NetCoreApp80);
[PublicAPI] public static readonly IToolchain NetCoreApp90 = From(NetCoreAppSettings.NetCoreApp90);

internal CsProjCoreToolchain(string name, IGenerator generator, IBuilder builder, IExecutor executor, string customDotNetCliPath)
: base(name, generator, builder, executor)
private readonly ISdkProvider sdkProvider;

internal ISdkProvider SdkProvider => sdkProvider;

internal CsProjCoreToolchain(string name, IGenerator generator, IBuilder builder, IExecutor executor, ISdkProvider sdkProvider)
: base(name, generator, builder, executor, sdkProvider)
{
CustomDotNetCliPath = customDotNetCliPath;
this.sdkProvider = sdkProvider;
}

internal string CustomDotNetCliPath { get; }

[PublicAPI]
public static IToolchain From(NetCoreAppSettings settings)
=> new CsProjCoreToolchain(settings.Name,
new CsProjGenerator(settings.TargetFrameworkMoniker, settings.CustomDotNetCliPath, settings.PackagesPath, settings.RuntimeFrameworkVersion),
new DotNetCliBuilder(settings.TargetFrameworkMoniker, settings.CustomDotNetCliPath),
new DotNetCliExecutor(settings.CustomDotNetCliPath),
settings.CustomDotNetCliPath);
new DotNetSdkProvider() { CustomDotNetCliPath = settings.CustomDotNetCliPath });

public override IEnumerable<ValidationError> Validate(BenchmarkCase benchmarkCase, IResolver resolver)
{
Expand All @@ -49,7 +51,7 @@ public override IEnumerable<ValidationError> Validate(BenchmarkCase benchmarkCas
yield return validationError;
}

if (IsCliPathInvalid(CustomDotNetCliPath, benchmarkCase, out var invalidCliError))
if (IsCliPathInvalid(sdkProvider.CustomDotNetCliPath, benchmarkCase, out var invalidCliError))
{
yield return invalidCliError;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ public InProcessEmitToolchain(TimeSpan timeout, bool logOutput) :
nameof(InProcessEmitToolchain),
new InProcessEmitGenerator(),
new InProcessEmitBuilder(),
new InProcessEmitExecutor(timeout, logOutput))
new InProcessEmitExecutor(timeout, logOutput),
null)
{
}

Expand Down
2 changes: 1 addition & 1 deletion src/BenchmarkDotNet/Toolchains/Mono/MonoAotToolchain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class MonoAotToolchain : Toolchain
public static readonly IToolchain Instance = new MonoAotToolchain();

[PublicAPI]
public MonoAotToolchain() : base("MonoAot", new Generator(), new MonoAotBuilder(), new Executor())
public MonoAotToolchain() : base("MonoAot", new Generator(), new MonoAotBuilder(), new Executor(), new DotNetSdkProvider())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It appears that MonoAotToolchain uses a variant of Roslyn. So it doesn't need the ISdkProvider (unless Roslyn toolchain needs to validate the sdk also, but it seems different than csproj toolchains).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah ok. Good info. I will definitely need a different approach in implementing this validation to just certain toolchains then.

{
}

Expand Down
17 changes: 14 additions & 3 deletions src/BenchmarkDotNet/Toolchains/Mono/MonoToolchain.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using BenchmarkDotNet.Toolchains.CsProj;
using BenchmarkDotNet.Toolchains.DotNetCli;
using BenchmarkDotNet.Validators;
using JetBrains.Annotations;
using System;

Expand All @@ -13,19 +14,29 @@ public class MonoToolchain : CsProjCoreToolchain, IEquatable<MonoToolchain>
[PublicAPI] public static readonly IToolchain Mono80 = From(new NetCoreAppSettings("net8.0", null, "mono80"));
[PublicAPI] public static readonly IToolchain Mono90 = From(new NetCoreAppSettings("net9.0", null, "mono90"));

private MonoToolchain(string name, IGenerator generator, IBuilder builder, IExecutor executor, string customDotNetCliPath)
: base(name, generator, builder, executor, customDotNetCliPath)
private MonoToolchain(string name, IGenerator generator, IBuilder builder, IExecutor executor, ISdkProvider sdkProvider)
: base(name, generator, builder, executor, sdkProvider)
{

}

public static ISdkProvider ConfigureSdkProviderForMono(string customDotNetCliPath)
{
var sdkProvider = new DotNetSdkProvider();
sdkProvider.CustomDotNetCliPath = customDotNetCliPath;
return sdkProvider;
}

[PublicAPI]
public static new IToolchain From(NetCoreAppSettings settings)
{
var sdkProvider = ConfigureSdkProviderForMono(settings.CustomDotNetCliPath);

return new MonoToolchain(settings.Name,
new MonoGenerator(settings.TargetFrameworkMoniker, settings.CustomDotNetCliPath, settings.PackagesPath, settings.RuntimeFrameworkVersion),
new MonoPublisher(settings.CustomDotNetCliPath),
new DotNetCliExecutor(settings.CustomDotNetCliPath),
settings.CustomDotNetCliPath);
sdkProvider);
}

public override bool Equals(object obj) => obj is MonoToolchain typed && Equals(typed);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace BenchmarkDotNet.Toolchains.MonoAotLLVM
public class MonoAotLLVMToolChain : Toolchain
{
public MonoAotLLVMToolChain(string name, IGenerator generator, IBuilder builder, IExecutor executor)
: base(name, generator, builder, executor)
: base(name, generator, builder, executor, null)
{
}

Expand Down
10 changes: 5 additions & 5 deletions src/BenchmarkDotNet/Toolchains/MonoWasm/WasmToolchain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ namespace BenchmarkDotNet.Toolchains.MonoWasm
[PublicAPI]
public class WasmToolchain : Toolchain
{
private string CustomDotNetCliPath { get; }
private readonly string CustomDotNetCliPath;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this field is needed.


private WasmToolchain(string name, IGenerator generator, IBuilder builder, IExecutor executor, string customDotNetCliPath)
: base(name, generator, builder, executor)
private WasmToolchain(string name, IGenerator generator, IBuilder builder, IExecutor executor, ISdkProvider sdkProvider)
: base(name, generator, builder, executor, sdkProvider)
{
CustomDotNetCliPath = customDotNetCliPath;
CustomDotNetCliPath = sdkProvider.CustomDotNetCliPath;
}

public override IEnumerable<ValidationError> Validate(BenchmarkCase benchmarkCase, IResolver resolver)
Expand Down Expand Up @@ -51,6 +51,6 @@ public static IToolchain From(NetCoreAppSettings netCoreAppSettings)
// aot builds can be very slow
logOutput: netCoreAppSettings.AOTCompilerMode == MonoAotLLVM.MonoAotCompilerMode.wasm),
new WasmExecutor(),
netCoreAppSettings.CustomDotNetCliPath);
new DotNetSdkProvider());
}
}
19 changes: 12 additions & 7 deletions src/BenchmarkDotNet/Toolchains/NativeAot/NativeAotToolchain.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using BenchmarkDotNet.Toolchains.DotNetCli;
using BenchmarkDotNet.Validators;

namespace BenchmarkDotNet.Toolchains.NativeAot
{
Expand Down Expand Up @@ -37,26 +38,30 @@ public class NativeAotToolchain : Toolchain
.TargetFrameworkMoniker("net9.0")
.ToToolchain();

#pragma warning disable CS0649
private readonly ISdkProvider sdkProvider;
#pragma warning restore CS0649

internal ISdkProvider SdkProvider => sdkProvider;

internal NativeAotToolchain(string displayName,
string ilCompilerVersion,
string runtimeFrameworkVersion, string targetFrameworkMoniker, string runtimeIdentifier,
string customDotNetCliPath, string packagesRestorePath,
ISdkProvider sdkProvider,
string packagesRestorePath,
Dictionary<string, string> feeds, bool useNuGetClearTag, bool useTempFolderForRestore,
bool rootAllApplicationAssemblies, bool ilcGenerateCompleteTypeMetadata, bool ilcGenerateStackTraceData,
string ilcOptimizationPreference, string ilcInstructionSet)
: base(displayName,
new Generator(ilCompilerVersion, runtimeFrameworkVersion, targetFrameworkMoniker, customDotNetCliPath,
new Generator(ilCompilerVersion, runtimeFrameworkVersion, targetFrameworkMoniker, sdkProvider.CustomDotNetCliPath,
runtimeIdentifier, feeds, useNuGetClearTag, useTempFolderForRestore, packagesRestorePath,
rootAllApplicationAssemblies, ilcGenerateCompleteTypeMetadata, ilcGenerateStackTraceData,
ilcOptimizationPreference, ilcInstructionSet),
new DotNetCliPublisher(customDotNetCliPath, GetExtraArguments(runtimeIdentifier)),
new Executor())
new DotNetCliPublisher(sdkProvider.CustomDotNetCliPath, GetExtraArguments(runtimeIdentifier)),
new Executor(), sdkProvider)
{
CustomDotNetCliPath = customDotNetCliPath;
}

internal string CustomDotNetCliPath { get; }

public static NativeAotToolchainBuilder CreateBuilder() => NativeAotToolchainBuilder.Create();

public static string GetExtraArguments(string runtimeIdentifier) => $"-r {runtimeIdentifier}";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Diagnostics.CodeAnalysis;
using System.IO;
using BenchmarkDotNet.Toolchains.DotNetCli;
using BenchmarkDotNet.Validators;
using JetBrains.Annotations;

namespace BenchmarkDotNet.Toolchains.NativeAot
Expand All @@ -10,6 +11,7 @@ public class NativeAotToolchainBuilder : CustomDotNetCliToolchainBuilder
{
public static NativeAotToolchainBuilder Create() => new NativeAotToolchainBuilder();

private ISdkProvider sdkProvider;
private string ilCompilerVersion;
private string packagesRestorePath;
// we set those default values on purpose https://github.com/dotnet/BenchmarkDotNet/pull/1057#issuecomment-461832612
Expand Down Expand Up @@ -144,19 +146,46 @@ public NativeAotToolchainBuilder IlcInstructionSet(string value)
return this;
}

/// <summary>
/// Configures the SDK provider with a custom path to the .NET CLI.
/// This path is used by the toolchain to locate and use the specified .NET CLI for operations such as building and publishing.
/// </summary>
/// <param name="customDotNetCliPath">The file system path to the custom .NET CLI executable.
/// This path should point to the 'dotnet' executable on your system.</param>
/// <returns>The <see cref="NativeAotToolchainBuilder"/> instance for fluent configuration.</returns>
/// <remarks>
/// This method allows for the configuration of a custom .NET CLI path, which can be useful in environments where multiple versions of the .NET SDK are installed,
/// or when there is a need to use a .NET CLI located outside of the standard installation paths.
/// </remarks>

[PublicAPI]
public NativeAotToolchainBuilder WithCustomDotNetCliPath(string customDotNetCliPath)
{
this.sdkProvider = new DotNetSdkProvider { CustomDotNetCliPath = customDotNetCliPath };

return this;
}


[PublicAPI]
public override IToolchain ToToolchain()
{
if (!isIlCompilerConfigured)
throw new InvalidOperationException("You need to use UseNuGet or UseLocalBuild methods to tell us which ILCompiler to use.");

// Ensure sdkProvider is initialized
if (this.sdkProvider == null)
{
throw new InvalidOperationException("SDK Provider must be set before building the toolchain.");
}

return new NativeAotToolchain(
displayName: displayName,
ilCompilerVersion: ilCompilerVersion,
runtimeFrameworkVersion: runtimeFrameworkVersion,
targetFrameworkMoniker: GetTargetFrameworkMoniker(),
runtimeIdentifier: runtimeIdentifier ?? GetPortableRuntimeIdentifier(),
customDotNetCliPath: customDotNetCliPath,
sdkProvider: sdkProvider,
packagesRestorePath: packagesRestorePath,
feeds: Feeds,
useNuGetClearTag: useNuGetClearTag,
Expand Down
2 changes: 1 addition & 1 deletion src/BenchmarkDotNet/Toolchains/Roslyn/RoslynToolchain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class RoslynToolchain : Toolchain
public static readonly IToolchain Instance = new RoslynToolchain();

[PublicAPI]
public RoslynToolchain() : base("Roslyn", new Generator(), Roslyn.Builder.Instance, new Executor())
public RoslynToolchain() : base("Roslyn", new Generator(), Roslyn.Builder.Instance, new Executor(), null)
{
}

Expand Down
12 changes: 11 additions & 1 deletion src/BenchmarkDotNet/Toolchains/Toolchain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,15 @@ public class Toolchain : IToolchain

public virtual bool IsInProcess => false;

public Toolchain(string name, IGenerator generator, IBuilder builder, IExecutor executor)
private readonly SdkValidator sdkValidator;

public Toolchain(string name, IGenerator generator, IBuilder builder, IExecutor executor, ISdkProvider sdkProvider)
{
Name = name;
Generator = generator;
Builder = builder;
Executor = executor;
sdkValidator = new SdkValidator(sdkProvider);
}

public virtual IEnumerable<ValidationError> Validate(BenchmarkCase benchmarkCase, IResolver resolver)
Expand Down Expand Up @@ -54,6 +57,13 @@ public virtual IEnumerable<ValidationError> Validate(BenchmarkCase benchmarkCase
$"We could not find Mono in provided path ({mono.CustomPath}), benchmark '{benchmarkCase.DisplayInfo}' will not be executed",
benchmarkCase);
}

}

var sdkValidationErrors = sdkValidator.Validate(new ValidationParameters(new[] { benchmarkCase }, null));
foreach (var validationError in sdkValidationErrors)
{
yield return validationError;
}
}

Expand Down
80 changes: 80 additions & 0 deletions src/BenchmarkDotNet/Validators/DotNetSdkProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;

namespace BenchmarkDotNet.Validators
{
public class DotNetSdkProvider : ISdkProvider
{
private string _customDotNetCliPath;

public string CustomDotNetCliPath
{
get => _customDotNetCliPath;
set => _customDotNetCliPath = value;
}
Comment on lines +13 to +17
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just make this a read only property and pass the string into the constructor (can make it an optional argument).

public IEnumerable<string> GetInstalledSdks()
{
var installedDotNetSdks = GetInstalledDotNetSdk();

var installedFrameworkSdks = GetInstalledFrameworkSdks();

return installedDotNetSdks.Concat(installedFrameworkSdks);
}

private IEnumerable<string> GetInstalledDotNetSdk()
{
string dotnetExecutable = string.IsNullOrEmpty(CustomDotNetCliPath) ? "dotnet" : CustomDotNetCliPath;

var startInfo = new ProcessStartInfo(dotnetExecutable, "--list-sdks")
{
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
};

using (var process = Process.Start(startInfo))
{
if (process == null) throw new InvalidOperationException("Failed to start the dotnet process.");

process.WaitForExit();

var output = process.StandardOutput.ReadToEnd();
var lines = output.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);

return lines.Select(line => line.Split(' ')[0]); // The SDK version is the first part of each line.
}
}

private IEnumerable<string> GetInstalledFrameworkSdks()
{
var versions = new List<string>();

// Skip .NET Framework check on macOS and Linux
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
#pragma warning disable CA1416
using (var ndpKey = Microsoft.Win32.RegistryKey.OpenBaseKey(Microsoft.Win32.RegistryHive.LocalMachine, Microsoft.Win32.RegistryView.Registry32)
.OpenSubKey("SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\"))
{
foreach (var versionKeyName in ndpKey.GetSubKeyNames())
{
if (versionKeyName.StartsWith("v"))
{
var versionKey = ndpKey.OpenSubKey(versionKeyName);
var version = versionKey.GetValue("Version", "").ToString();
var sp = versionKey.GetValue("SP", "").ToString();
if (!string.IsNullOrEmpty(version))
versions.Add(version + (string.IsNullOrEmpty(sp) ? "" : $" SP{sp}"));
}
}
}
#pragma warning restore CA1416
}

return versions;
}
}
}
10 changes: 10 additions & 0 deletions src/BenchmarkDotNet/Validators/ISdkProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Collections.Generic;

namespace BenchmarkDotNet.Validators
{
public interface ISdkProvider
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ISdkProvider name implies that a different sdk besides dotnet could be used. Does that make sense? Does RoslynToolchain use an sdk that isn't dotnet?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I think this interface should be moved to BenchmarkDotNet.Toolchains namespace.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes perfect sense. I appreciate the valuable insight.

{
IEnumerable<string> GetInstalledSdks();
string CustomDotNetCliPath { get; }
}
}
Loading