diff --git a/src/BenchmarkDotNet/Validators/DotNetSdkVersionValidator.cs b/src/BenchmarkDotNet/Validators/DotNetSdkVersionValidator.cs index 93a3af80dd..df2c9253ea 100644 --- a/src/BenchmarkDotNet/Validators/DotNetSdkVersionValidator.cs +++ b/src/BenchmarkDotNet/Validators/DotNetSdkVersionValidator.cs @@ -21,34 +21,28 @@ public static IEnumerable ValidateCoreSdks(string? customDotNet { yield return cliPathError; } - else if (TryGetSdkVersion(benchmark, out string requiredSdkVersion)) + else if (TryGetSdkVersion(benchmark, out Version requiredSdkVersion)) { var installedSdks = GetInstalledDotNetSdks(customDotNetCliPath); - if (!installedSdks.Any(sdk => sdk.StartsWith(requiredSdkVersion))) + if (!installedSdks.Any(sdk => sdk >= requiredSdkVersion)) { - yield return new ValidationError(true, $"The required .NET Core SDK version {requiredSdkVersion} for runtime moniker {benchmark.Job.Environment.Runtime.RuntimeMoniker} is not installed.", benchmark); + yield return new ValidationError(true, $"The required .NET Core SDK version {requiredSdkVersion} or higher for runtime moniker {benchmark.Job.Environment.Runtime.RuntimeMoniker} is not installed.", benchmark); } } } public static IEnumerable ValidateFrameworkSdks(BenchmarkCase benchmark) { - if (!TryGetSdkVersion(benchmark, out string requiredSdkVersionString)) + if (!TryGetSdkVersion(benchmark, out Version requiredSdkVersion)) { yield break; } - if (!Version.TryParse(requiredSdkVersionString, out var requiredSdkVersion)) - { - yield return new ValidationError(true, $"Invalid .NET Framework SDK version format: {requiredSdkVersionString}", benchmark); - yield break; - } - var installedVersionString = cachedFrameworkSdks.Value.FirstOrDefault(); if (installedVersionString == null || Version.TryParse(installedVersionString, out var installedVersion) && installedVersion < requiredSdkVersion) { - yield return new ValidationError(true, $"The required .NET Framework SDK version {requiredSdkVersionString} or higher is not installed.", benchmark); + yield return new ValidationError(true, $"The required .NET Framework SDK version {requiredSdkVersion} or higher is not installed.", benchmark); } } @@ -77,9 +71,9 @@ public static bool IsCliPathInvalid(string customDotNetCliPath, BenchmarkCase be return false; } - private static bool TryGetSdkVersion(BenchmarkCase benchmark, out string sdkVersion) + private static bool TryGetSdkVersion(BenchmarkCase benchmark, out Version sdkVersion) { - sdkVersion = string.Empty; + sdkVersion = default; if (benchmark?.Job?.Environment?.Runtime?.RuntimeMoniker != null) { sdkVersion = GetSdkVersionFromMoniker(benchmark.Job.Environment.Runtime.RuntimeMoniker); @@ -88,7 +82,7 @@ private static bool TryGetSdkVersion(BenchmarkCase benchmark, out string sdkVers return false; } - private static IEnumerable GetInstalledDotNetSdks(string? customDotNetCliPath) + private static IEnumerable GetInstalledDotNetSdks(string? customDotNetCliPath) { string dotnetExecutable = string.IsNullOrEmpty(customDotNetCliPath) ? "dotnet" : customDotNetCliPath; var startInfo = new ProcessStartInfo(dotnetExecutable, "--list-sdks") @@ -104,7 +98,7 @@ private static IEnumerable GetInstalledDotNetSdks(string? customDotNetCl { if (process == null) { - return Enumerable.Empty(); + return Enumerable.Empty(); } process.WaitForExit(); @@ -113,17 +107,38 @@ private static IEnumerable GetInstalledDotNetSdks(string? customDotNetCl { 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. + + var versions = new List(lines.Count()); + foreach (var line in lines) + { + // Each line will start with the SDK version followed by the SDK path. Since we only support + // targeting SDK versions by the major version, we'll just look at extracting the major and + // minor versions. This is done by looking for the first two dots in the line. + var firstDot = line.IndexOf('.'); + if (firstDot < 0) + continue; + + var secondDot = line.IndexOf('.', firstDot + 1); + if (secondDot < 0) + continue; + + if (Version.TryParse(line.Substring(0, secondDot), out var version)) + { + versions.Add(version); + } + } + + return versions; } else { - return Enumerable.Empty(); + return Enumerable.Empty(); } } } catch (Win32Exception) // dotnet CLI is not installed or not found in the path. { - return Enumerable.Empty(); + return Enumerable.Empty(); } } @@ -193,42 +208,42 @@ private static string CheckFor45PlusVersion(int releaseKey) return ""; } - private static string GetSdkVersionFromMoniker(RuntimeMoniker runtimeMoniker) + private static Version GetSdkVersionFromMoniker(RuntimeMoniker runtimeMoniker) { return runtimeMoniker switch { - RuntimeMoniker.Net461 => "4.6.1", - RuntimeMoniker.Net462 => "4.6.2", - RuntimeMoniker.Net47 => "4.7", - RuntimeMoniker.Net471 => "4.7.1", - RuntimeMoniker.Net472 => "4.7.2", - RuntimeMoniker.Net48 => "4.8", - RuntimeMoniker.Net481 => "4.8.1", - RuntimeMoniker.NetCoreApp31 => "3.1", - RuntimeMoniker.Net50 => "5.0", - RuntimeMoniker.Net60 => "6.0", - RuntimeMoniker.Net70 => "7.0", - RuntimeMoniker.Net80 => "8.0", - RuntimeMoniker.Net90 => "9.0", - RuntimeMoniker.NativeAot60 => "6.0", - RuntimeMoniker.NativeAot70 => "7.0", - RuntimeMoniker.NativeAot80 => "8.0", - RuntimeMoniker.NativeAot90 => "9.0", - RuntimeMoniker.Mono60 => "6.0", - RuntimeMoniker.Mono70 => "7.0", - RuntimeMoniker.Mono80 => "8.0", - RuntimeMoniker.Mono90 => "9.0", - RuntimeMoniker.Wasm => Portability.RuntimeInformation.IsNetCore && CoreRuntime.TryGetVersion(out var version) ? $"{version.Major}.{version.Minor}" : "5.0", - RuntimeMoniker.WasmNet50 => "5.0", - RuntimeMoniker.WasmNet60 => "6.0", - RuntimeMoniker.WasmNet70 => "7.0", - RuntimeMoniker.WasmNet80 => "8.0", - RuntimeMoniker.WasmNet90 => "9.0", - RuntimeMoniker.MonoAOTLLVM => Portability.RuntimeInformation.IsNetCore && CoreRuntime.TryGetVersion(out var version) ? $"{version.Major}.{version.Minor}" : "6.0", - RuntimeMoniker.MonoAOTLLVMNet60 => "6.0", - RuntimeMoniker.MonoAOTLLVMNet70 => "7.0", - RuntimeMoniker.MonoAOTLLVMNet80 => "8.0", - RuntimeMoniker.MonoAOTLLVMNet90 => "9.0", + RuntimeMoniker.Net461 => new Version(4, 6, 1), + RuntimeMoniker.Net462 => new Version(4, 6, 2), + RuntimeMoniker.Net47 => new Version(4, 7), + RuntimeMoniker.Net471 => new Version(4, 7, 1), + RuntimeMoniker.Net472 => new Version(4, 7, 2), + RuntimeMoniker.Net48 => new Version(4, 8), + RuntimeMoniker.Net481 => new Version(4, 8, 1), + RuntimeMoniker.NetCoreApp31 => new Version(3, 1), + RuntimeMoniker.Net50 => new Version(5, 0), + RuntimeMoniker.Net60 => new Version(6, 0), + RuntimeMoniker.Net70 => new Version(7, 0), + RuntimeMoniker.Net80 => new Version(8, 0), + RuntimeMoniker.Net90 => new Version(9, 0), + RuntimeMoniker.NativeAot60 => new Version(6, 0), + RuntimeMoniker.NativeAot70 => new Version(7, 0), + RuntimeMoniker.NativeAot80 => new Version(8, 0), + RuntimeMoniker.NativeAot90 => new Version(9, 0), + RuntimeMoniker.Mono60 => new Version(6, 0), + RuntimeMoniker.Mono70 => new Version(7, 0), + RuntimeMoniker.Mono80 => new Version(8, 0), + RuntimeMoniker.Mono90 => new Version(9, 0), + RuntimeMoniker.Wasm => Portability.RuntimeInformation.IsNetCore && CoreRuntime.TryGetVersion(out var version) ? version : new Version(5, 0), + RuntimeMoniker.WasmNet50 => new Version(5, 0), + RuntimeMoniker.WasmNet60 => new Version(6, 0), + RuntimeMoniker.WasmNet70 => new Version(7, 0), + RuntimeMoniker.WasmNet80 => new Version(8, 0), + RuntimeMoniker.WasmNet90 => new Version(9, 0), + RuntimeMoniker.MonoAOTLLVM => Portability.RuntimeInformation.IsNetCore && CoreRuntime.TryGetVersion(out var version) ? version : new Version(6, 0), + RuntimeMoniker.MonoAOTLLVMNet60 => new Version(6, 0), + RuntimeMoniker.MonoAOTLLVMNet70 => new Version(7, 0), + RuntimeMoniker.MonoAOTLLVMNet80 => new Version(8, 0), + RuntimeMoniker.MonoAOTLLVMNet90 => new Version(9, 0), _ => throw new NotImplementedException($"SDK version check not implemented for {runtimeMoniker}") }; }