| File: Utils\EnvironmentChecker\DeprecatedWorkloadCheck.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 Microsoft.Extensions.Logging; namespace Aspire.Cli.Utils.EnvironmentChecker; /// <summary> /// Checks if the deprecated 'aspire' workload is installed. /// </summary> /// <remarks> /// The 'aspire' workload has been deprecated and should be uninstalled. /// Users with this workload installed may encounter conflicts or confusion. /// </remarks> internal sealed class DeprecatedWorkloadCheck(ILogger<DeprecatedWorkloadCheck> logger) : IEnvironmentCheck { private static readonly TimeSpan s_processTimeout = TimeSpan.FromSeconds(10); public int Order => 32; // After SDK check (30), before dev certs (35) public async Task<IReadOnlyList<EnvironmentCheckResult>> CheckAsync(CancellationToken cancellationToken = default) { try { var processInfo = new ProcessStartInfo { FileName = "dotnet", Arguments = "workload list", RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true }; using var process = Process.Start(processInfo); if (process is null) { logger.LogDebug("Failed to start dotnet workload list process"); // Don't fail the check if we can't run the command - the SDK check will catch SDK issues return []; } using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); timeoutCts.CancelAfter(s_processTimeout); string output; try { output = await process.StandardOutput.ReadToEndAsync(timeoutCts.Token); await process.WaitForExitAsync(timeoutCts.Token); } catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) { process.Kill(); logger.LogDebug("dotnet workload list timed out"); return []; } if (process.ExitCode != 0) { logger.LogDebug("dotnet workload list exited with code {ExitCode}", process.ExitCode); return []; } // Check if the deprecated 'aspire' workload is installed // The output format is typically: // Installed Workload Id Manifest Version Installation Source // -------------------------------------------------------------------- // aspire 8.0.0/8.0.100 SDK 8.0.100 if (IsAspireWorkloadInstalled(output)) { return [new EnvironmentCheckResult { Category = "sdk", Name = "aspire-workload", Status = EnvironmentCheckStatus.Fail, Message = "Deprecated 'aspire' workload is installed", Details = "The 'aspire' workload has been deprecated and causes conflicts with modern Aspire projects.", Fix = "Run: dotnet workload uninstall aspire", Link = "https://aka.ms/aspire-prerequisites" }]; } // Workload not installed - this is the expected state, no need to report anything return []; } catch (Exception ex) { logger.LogDebug(ex, "Error checking for aspire workload"); // Don't fail the check if we can't verify - just skip it return []; } } /// <summary> /// Checks if the 'aspire' workload is present in the output of 'dotnet workload list'. /// </summary> internal static bool IsAspireWorkloadInstalled(string output) { if (string.IsNullOrWhiteSpace(output)) { return false; } // Parse each line looking for the 'aspire' workload // The format is whitespace-separated columns, with the first column being the workload ID var lines = output.Split('\n', StringSplitOptions.RemoveEmptyEntries); foreach (var line in lines) { var trimmedLine = line.Trim(); // Skip header lines, separator lines, and informational lines if (trimmedLine.StartsWith("Installed", StringComparison.OrdinalIgnoreCase) || trimmedLine.StartsWith("Workload version:", StringComparison.OrdinalIgnoreCase) || trimmedLine.StartsWith("---") || trimmedLine.StartsWith("Use", StringComparison.OrdinalIgnoreCase) || string.IsNullOrWhiteSpace(trimmedLine)) { continue; } // The workload ID is the first column var columns = trimmedLine.Split([' ', '\t'], StringSplitOptions.RemoveEmptyEntries); if (columns.Length > 0 && string.Equals(columns[0], "aspire", StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } }