| File: Utils\EnvironmentChecker\DotNetSdkCheck.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 Aspire.Cli.DotNet; using Aspire.Cli.Projects; using Microsoft.Extensions.Logging; namespace Aspire.Cli.Utils.EnvironmentChecker; /// <summary> /// Checks if the .NET SDK is installed and meets the minimum version requirement. /// </summary> /// <remarks> /// This check is skipped when the detected AppHost is a non-.NET project (e.g., TypeScript, Python, Go), /// since .NET SDK is not required for polyglot scenarios. /// </remarks> internal sealed class DotNetSdkCheck( IDotNetSdkInstaller sdkInstaller, IProjectLocator projectLocator, ILanguageDiscovery languageDiscovery, CliExecutionContext executionContext, ILogger<DotNetSdkCheck> logger) : IEnvironmentCheck { public int Order => 30; // File system check - slightly more expensive public async Task<IReadOnlyList<EnvironmentCheckResult>> CheckAsync(CancellationToken cancellationToken = default) { try { if (!await IsDotNetAppHostAsync(cancellationToken)) { logger.LogDebug("Skipping .NET SDK check because no .NET AppHost was detected"); return []; } var (success, highestVersion, minimumRequiredVersion) = await sdkInstaller.CheckAsync(cancellationToken); if (!success) { // Parse major version from string like "10.0.100" -> 10 var majorVersion = 10; if (Version.TryParse(minimumRequiredVersion, out var parsedVersion)) { majorVersion = parsedVersion.Major; } return [new EnvironmentCheckResult { Category = "sdk", Name = "dotnet-sdk", Status = EnvironmentCheckStatus.Fail, Message = highestVersion is null ? ".NET SDK not found" : $".NET {highestVersion} found but {minimumRequiredVersion} or higher required", Fix = $"Download .NET SDK from: https://dotnet.microsoft.com/download/dotnet/{majorVersion}.0", Link = $"https://dotnet.microsoft.com/download/dotnet/{majorVersion}.0" }]; } var architecture = System.Runtime.InteropServices.RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant(); return [new EnvironmentCheckResult { Category = "sdk", Name = "dotnet-sdk", Status = EnvironmentCheckStatus.Pass, Message = $".NET {highestVersion} installed ({architecture})" }]; } catch (Exception ex) { logger.LogDebug(ex, "Error checking .NET SDK"); return [new EnvironmentCheckResult { Category = "sdk", Name = "dotnet-sdk", Status = EnvironmentCheckStatus.Fail, Message = "Error checking .NET SDK", Details = ex.Message }]; } } /// <summary> /// Determines whether a .NET AppHost is positively detected, meaning the .NET SDK check should run. /// Only returns <c>true</c> when a settings file is found and the apphost is a .NET project. /// When no settings file exists or the apphost is non-.NET, the check is skipped. /// </summary> private async Task<bool> IsDotNetAppHostAsync(CancellationToken cancellationToken) { try { // Use the silent settings-only lookup to find the apphost without // emitting interaction output or performing recursive filesystem scans. var appHostFile = await projectLocator.GetAppHostFromSettingsAsync(cancellationToken); if (appHostFile is not null && languageDiscovery.GetLanguageByFile(appHostFile) is { } language) { return language.LanguageId.Value.Equals(KnownLanguageId.CSharp, StringComparison.OrdinalIgnoreCase); } // No apphost configured in settings. Look for possible .NET app hosts on file system (projects or apphost.cs) // This doesn't guarantee the apphost is .NET, but it's a signal that it might be and worth checking for .NET SDK. var csharp = languageDiscovery.GetLanguageById(KnownLanguageId.CSharp); if (csharp is null) { return false; } // Scan file system directly instead of using ProjectLocator for performance. // Limit recursive scan to avoid expensive file system operations in large workspaces. // We don't want a complete list of all possible project files, just a quick signal that a .NET apphost is probably present. var match = FileSystemHelper.FindFirstFile(executionContext.WorkingDirectory.FullName, recurseLimit: 5, csharp.DetectionPatterns); return match is not null; } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { throw; } catch (Exception ex) { logger.LogDebug(ex, "Error detecting AppHost language, skipping .NET SDK check"); return false; } } }