| File: DotNet\DotNetSdkInstaller.cs | Web Access |
| Project: src\src\Aspire.Cli\Aspire.Cli.csproj (aspire) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; using System.Runtime.InteropServices; using Microsoft.Extensions.Configuration; using Semver; namespace Aspire.Cli.DotNet; /// <summary> /// Default implementation of <see cref="IDotNetSdkInstaller"/> that checks for dotnet on the system PATH. /// </summary> internal sealed class DotNetSdkInstaller(IConfiguration configuration) : IDotNetSdkInstaller { /// <summary> /// The minimum .NET SDK version required for Aspire. /// </summary> public const string MinimumSdkVersion = "10.0.100"; /// <inheritdoc /> public async Task<(bool Success, string? HighestDetectedVersion, string MinimumRequiredVersion)> CheckAsync(CancellationToken cancellationToken = default) { var minimumVersion = GetEffectiveMinimumSdkVersion(configuration); try { // Add --arch flag to ensure we only get SDKs that match the current architecture var currentArch = GetCurrentArchitecture(); var arguments = $"--list-sdks --arch {currentArch}"; using var process = new Process { StartInfo = new ProcessStartInfo { FileName = "dotnet", Arguments = arguments, UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true } }; process.Start(); var output = await process.StandardOutput.ReadToEndAsync(cancellationToken); await process.WaitForExitAsync(cancellationToken); if (process.ExitCode != 0) { return (false, null, minimumVersion); } // Parse the minimum version requirement if (!SemVersion.TryParse(minimumVersion, SemVersionStyles.Strict, out var minVersion)) { return (false, null, minimumVersion); } // Parse each line of the output to find SDK versions var lines = output.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries); SemVersion? highestDetectedVersion = null; bool meetsMinimum = false; foreach (var line in lines) { // Each line is in format: "version [path]" var spaceIndex = line.IndexOf(' '); if (spaceIndex > 0) { var versionString = line[..spaceIndex]; if (SemVersion.TryParse(versionString, SemVersionStyles.Strict, out var sdkVersion)) { // Track the highest version if (highestDetectedVersion == null || SemVersion.ComparePrecedence(sdkVersion, highestDetectedVersion) > 0) { highestDetectedVersion = sdkVersion; } // Check if this version meets the minimum requirement if (MeetsMinimumRequirement(sdkVersion, minVersion, minimumVersion)) { meetsMinimum = true; } } } } return (meetsMinimum, highestDetectedVersion?.ToString(), minimumVersion); } catch (Exception ex) when (ex is not OperationCanceledException) // If cancellation is requested let that bubble up. { // If we can't start the process, the SDK is not available return (false, null, minimumVersion); } } /// <summary> /// Gets the current architecture string in the format expected by dotnet --list-sdks --arch. /// </summary> /// <returns>The architecture string (e.g., "x64", "arm64", "x86", "arm").</returns> private static string GetCurrentArchitecture() { return RuntimeInformation.ProcessArchitecture switch { Architecture.X64 => "x64", Architecture.X86 => "x86", Architecture.Arm64 => "arm64", Architecture.Arm => "arm", _ => "x64" // Default to x64 for unknown architectures }; } /// <summary> /// Gets the effective minimum SDK version based on configuration. /// </summary> /// <param name="configuration">The configuration to check for overrides.</param> /// <returns>The minimum SDK version string.</returns> public static string GetEffectiveMinimumSdkVersion(IConfiguration configuration) { // Check for configuration override first var overrideVersion = configuration["overrideMinimumSdkVersion"]; if (!string.IsNullOrEmpty(overrideVersion)) { return overrideVersion; } else { return MinimumSdkVersion; } } /// <summary> /// Checks if an installed SDK version meets the minimum requirement. /// For .NET 10.x requirements, allows any .NET 10.x version including prereleases. /// </summary> /// <param name="installedVersion">The installed SDK version.</param> /// <param name="requiredVersion">The required minimum version (parsed).</param> /// <param name="requiredVersionString">The required version string.</param> /// <returns>True if the installed version meets the requirement.</returns> private static bool MeetsMinimumRequirement(SemVersion installedVersion, SemVersion requiredVersion, string requiredVersionString) { // Special handling for .NET 10 RTM requirement - allow any .NET 10.x version if (requiredVersionString == MinimumSdkVersion) { // If we require 10.0.100, accept any version that is >= 10.0.0 return installedVersion.Major >= 10; } // For all other requirements, use strict version comparison return SemVersion.ComparePrecedence(installedVersion, requiredVersion) >= 0; } }