using System.Text.Json;
using System.Text.RegularExpressions;
using BuildTool.Json;
using BuildTool.Platform;
using BuildTool.Publishing;
using Spectre.Console;
namespace BuildTool.Prerequisites;
public static partial class DotNetSdkManager
{
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 requiredVersion = ReadRequiredVersion(repoRoot);
var downloadUrl = GetDownloadUrl(requiredVersion);
// Check if dotnet is on PATH
var result = ProcessRunner.RunCaptured("dotnet", "--list-sdks");
if (!result.Success)
{
return new PrerequisiteResult
{
Name = ".NET SDK",
Passed = false,
Details = $".NET SDK {requiredVersion}+ is not installed",
DownloadUrl = downloadUrl
};
}
// Parse installed SDK versions — find the best match
Version? bestVersion = null;
string? bestVersionStr = null;
foreach (var line in result.StandardOutput.Split('\n', StringSplitOptions.RemoveEmptyEntries))
{
var match = SdkVersionRegex().Match(line);
if (!match.Success)
{
continue;
}
var versionStr = match.Groups[1].Value;
if (!Version.TryParse(versionStr, out var version))
{
continue;
}
if (bestVersion is null || version > bestVersion)
{
bestVersion = version;
bestVersionStr = versionStr;
}
}
if (bestVersion is null || bestVersion < requiredVersion)
{
return new PrerequisiteResult
{
Name = ".NET SDK",
Passed = false,
Details = bestVersionStr is not null
? $".NET SDK {bestVersionStr} found, but {requiredVersion}+ is required"
: $".NET SDK {requiredVersion}+ is required but no SDK was found",
DownloadUrl = downloadUrl
};
}
return new PrerequisiteResult
{
Name = ".NET SDK",
Passed = true,
Details = $".NET SDK {bestVersionStr}"
};
}
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.");
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. Download and run the installer?[/]",
defaultValue: true
);
if (!install)
{
AnsiConsole.MarkupLine($"\n Download manually from: [link]{downloadUrl}[/]");
return false;
}
AnsiConsole.MarkupLine("\n[blue]Opening .NET SDK installer...[/]");
var result = ProcessRunner.RunPassthrough("cmd", $"/c start \"\" \"{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 bool OfferMacOsInstall(PlatformInfo platform, Version requiredVersion, string downloadUrl)
{
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 (!install)
{
AnsiConsole.MarkupLine($"\n Download manually from: [link]{downloadUrl}[/]");
return false;
}
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
}
///
/// 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))
{
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;
}
}
return new Version(10, 0);
}
[GeneratedRegex(@"^(\d+\.\d+\.\d+\S*)")]
private static partial Regex SdkVersionRegex();
}