diff --git a/Projects/BuildTool/Prerequisites/DotNetSdkManager.cs b/Projects/BuildTool/Prerequisites/DotNetSdkManager.cs index 3659702d9..5ab2ea295 100644 --- a/Projects/BuildTool/Prerequisites/DotNetSdkManager.cs +++ b/Projects/BuildTool/Prerequisites/DotNetSdkManager.cs @@ -9,18 +9,25 @@ namespace BuildTool.Prerequisites; public static partial class DotNetSdkManager { - private const string DotNetDownloadUrl = "https://dotnet.microsoft.com/download"; - private const string InstallScriptUrlWindows = "https://dot.net/v1/dotnet-install.ps1"; - private const string InstallScriptUrlUnix = "https://dot.net/v1/dotnet-install.sh"; + private const string DotNetLinuxInstallUrl = "https://learn.microsoft.com/en-us/dotnet/core/install/linux"; + + /// + /// Returns the .NET download page URL for the required SDK's major.minor channel. + /// + private static string GetDownloadUrl(Version version) => + $"https://dotnet.microsoft.com/download/dotnet/{version.Major}.{version.Minor}"; + + /// + /// Returns the direct installer download URL for a specific SDK version + platform. + /// Windows uses .exe, macOS uses .pkg. + /// + private static string GetInstallerUrl(Version version, string rid, string extension) => + $"https://builds.dotnet.microsoft.com/dotnet/Sdk/{version}/dotnet-sdk-{version}-{rid}.{extension}"; public static PrerequisiteResult CheckSdk(string repoRoot) { - var requiredVersionStr = ReadRequiredVersion(repoRoot) ?? "10.0.201"; - - if (!Version.TryParse(requiredVersionStr, out var requiredVersion)) - { - requiredVersion = new Version(10, 0, 201); - } + var requiredVersion = ReadRequiredVersion(repoRoot); + var downloadUrl = GetDownloadUrl(requiredVersion); // Check if dotnet is on PATH var result = ProcessRunner.RunCaptured("dotnet", "--list-sdks"); @@ -30,8 +37,8 @@ public static partial class DotNetSdkManager { Name = ".NET SDK", Passed = false, - Details = $".NET SDK {requiredVersionStr}+ is not installed", - DownloadUrl = DotNetDownloadUrl + Details = $".NET SDK {requiredVersion}+ is not installed", + DownloadUrl = downloadUrl }; } @@ -67,9 +74,9 @@ public static partial class DotNetSdkManager Name = ".NET SDK", Passed = false, Details = bestVersionStr is not null - ? $".NET SDK {bestVersionStr} found, but {requiredVersionStr}+ is required" - : $".NET SDK {requiredVersionStr}+ is required but no SDK was found", - DownloadUrl = DotNetDownloadUrl + ? $".NET SDK {bestVersionStr} found, but {requiredVersion}+ is required" + : $".NET SDK {requiredVersion}+ is required but no SDK was found", + DownloadUrl = downloadUrl }; } @@ -81,108 +88,113 @@ public static partial class DotNetSdkManager }; } - public static bool OfferInstall(PlatformInfo platform, bool interactive) + public static bool OfferInstall(PlatformInfo platform, string repoRoot, bool interactive) { + var requiredVersion = ReadRequiredVersion(repoRoot); + var downloadUrl = GetDownloadUrl(requiredVersion); + if (!interactive) { - AnsiConsole.MarkupLine("[red]Error:[/] .NET SDK is not installed. Download from:"); - AnsiConsole.MarkupLine($"[link]{DotNetDownloadUrl}[/]"); + AnsiConsole.MarkupLine("[red]Error:[/] .NET SDK is not installed."); + AnsiConsole.MarkupLine($" Download from: [link]{downloadUrl}[/]"); return false; } + if (platform.IsWindows) + { + return OfferWindowsInstall(platform, requiredVersion, downloadUrl); + } + + if (platform.IsMacOS) + { + return OfferMacOsInstall(platform, requiredVersion, downloadUrl); + } + + // Linux — just show instructions + AnsiConsole.MarkupLine($"\n[yellow]Install the .NET SDK using your package manager:[/]"); + AnsiConsole.MarkupLine($" [link]{DotNetLinuxInstallUrl}[/]"); + AnsiConsole.WriteLine(); + return false; + } + + private static bool OfferWindowsInstall(PlatformInfo platform, Version requiredVersion, string downloadUrl) + { + var rid = $"win-{platform.ArchRid}"; + var installerUrl = GetInstallerUrl(requiredVersion, rid, "exe"); + var install = AnsiConsole.Confirm( - "[yellow].NET SDK is required but not found.[/] Would you like to install it now?", + "[yellow].NET SDK is required. Download and run the installer?[/]", defaultValue: true ); if (!install) { - AnsiConsole.MarkupLine($"\nDownload the .NET SDK from: [link]{DotNetDownloadUrl}[/]"); + AnsiConsole.MarkupLine($"\n Download manually from: [link]{downloadUrl}[/]"); return false; } - return RunInstallScript(platform); - } - - private static bool RunInstallScript(PlatformInfo platform) - { - AnsiConsole.MarkupLine("\n[blue]Installing .NET SDK...[/]"); - - if (platform.IsWindows) - { - return RunWindowsInstall(); - } - - return RunUnixInstall(); - } - - private static bool RunWindowsInstall() - { - // Download and run the official Microsoft install script - var result = ProcessRunner.RunPassthrough( - "powershell", - $"-NoProfile -ExecutionPolicy Bypass -Command \"& {{ " + - $"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; " + - $"$script = Invoke-WebRequest -Uri '{InstallScriptUrlWindows}' -UseBasicParsing; " + - $"$scriptBlock = [scriptblock]::Create($script.Content); " + - $"& $scriptBlock -Channel 10.0 }}\"" - ); + AnsiConsole.MarkupLine("\n[blue]Opening .NET SDK installer...[/]"); + var result = ProcessRunner.RunPassthrough("cmd", $"/c start \"\" \"{installerUrl}\""); if (result != 0) { - AnsiConsole.MarkupLine("[red]Failed to install .NET SDK.[/]"); - AnsiConsole.MarkupLine($"Please install manually from: [link]{DotNetDownloadUrl}[/]"); + AnsiConsole.MarkupLine("[red]Failed to open installer.[/]"); + AnsiConsole.MarkupLine($" Download manually from: [link]{downloadUrl}[/]"); return false; } - AnsiConsole.MarkupLine("[green]Successfully installed .NET SDK.[/]"); - AnsiConsole.MarkupLine("[yellow]Note:[/] You may need to restart your terminal for the PATH to update."); - return true; + AnsiConsole.MarkupLine("[yellow]Install the SDK, then restart your terminal and run this tool again.[/]"); + return false; // User needs to restart after installing } - private static bool RunUnixInstall() + private static bool OfferMacOsInstall(PlatformInfo platform, Version requiredVersion, string downloadUrl) { - // Download and run the official Microsoft install script - var result = ProcessRunner.RunPassthrough( - "bash", - $"-c \"curl -fsSL {InstallScriptUrlUnix} | bash -s -- --channel 10.0\"" + var rid = $"osx-{platform.ArchRid}"; + var installerUrl = GetInstallerUrl(requiredVersion, rid, "pkg"); + + var install = AnsiConsole.Confirm( + "[yellow].NET SDK is required. Download and run the installer?[/]", + defaultValue: true ); - if (result != 0) + if (!install) { - AnsiConsole.MarkupLine("[red]Failed to install .NET SDK.[/]"); - AnsiConsole.MarkupLine($"Please install manually from: [link]{DotNetDownloadUrl}[/]"); + AnsiConsole.MarkupLine($"\n Download manually from: [link]{downloadUrl}[/]"); return false; } - AnsiConsole.MarkupLine("[green]Successfully installed .NET SDK.[/]"); - AnsiConsole.MarkupLine("[yellow]Note:[/] You may need to add ~/.dotnet to your PATH or restart your terminal."); - return true; + AnsiConsole.MarkupLine("\n[blue]Opening .NET SDK installer...[/]"); + + var result = ProcessRunner.RunPassthrough("open", installerUrl); + if (result != 0) + { + AnsiConsole.MarkupLine("[red]Failed to open installer.[/]"); + AnsiConsole.MarkupLine($" Download manually from: [link]{downloadUrl}[/]"); + return false; + } + + AnsiConsole.MarkupLine("[yellow]Install the SDK, then restart your terminal and run this tool again.[/]"); + return false; // User needs to restart after installing } - private static string? ReadRequiredVersion(string repoRoot) + /// + /// Reads the required SDK version from global.json. + /// Falls back to 10.0 if global.json is missing or unparseable. + /// + private static Version ReadRequiredVersion(string repoRoot) { var globalJsonPath = Path.Combine(repoRoot, "global.json"); - if (!File.Exists(globalJsonPath)) + if (File.Exists(globalJsonPath)) { - return null; + var json = File.ReadAllText(globalJsonPath); + var globalJson = JsonSerializer.Deserialize(json, GlobalJsonContext.Default.GlobalJson); + if (globalJson?.Sdk?.Version is { } versionStr && Version.TryParse(versionStr, out var version)) + { + return version; + } } - var json = File.ReadAllText(globalJsonPath); - var globalJson = JsonSerializer.Deserialize(json, GlobalJsonContext.Default.GlobalJson); - return globalJson?.Sdk?.Version; - } - - // Kept for reference but no longer used — Version.TryParse handles comparison now - private static int ParseMajorVersion(string version) - { - var dotIndex = version.IndexOf('.'); - if (dotIndex <= 0) - { - return 0; - } - - return int.TryParse(version.AsSpan(0, dotIndex), out var major) ? major : 0; + return new Version(10, 0); } [GeneratedRegex(@"^(\d+\.\d+\.\d+\S*)")] diff --git a/Projects/BuildTool/Prerequisites/PrerequisiteChecker.cs b/Projects/BuildTool/Prerequisites/PrerequisiteChecker.cs index 245bcee4c..c5b4f1305 100644 --- a/Projects/BuildTool/Prerequisites/PrerequisiteChecker.cs +++ b/Projects/BuildTool/Prerequisites/PrerequisiteChecker.cs @@ -69,7 +69,7 @@ public static class PrerequisiteChecker if (!sdkResult.Passed) { - var installed = DotNetSdkManager.OfferInstall(platform, interactive); + var installed = DotNetSdkManager.OfferInstall(platform, repoRoot, interactive); if (!installed) { return false;