diff --git a/src/Installer/dnup/Commands/Sdk/Install/SdkInstallCommand.cs b/src/Installer/dnup/Commands/Sdk/Install/SdkInstallCommand.cs index 680f10c3a70a..590eab9f3f48 100644 --- a/src/Installer/dnup/Commands/Sdk/Install/SdkInstallCommand.cs +++ b/src/Installer/dnup/Commands/Sdk/Install/SdkInstallCommand.cs @@ -210,76 +210,36 @@ public override int Execute() } + // TODO: Implement transaction / rollback? + // TODO: Use Mutex to avoid concurrent installs? + SpectreAnsiConsole.MarkupInterpolated($"Installing .NET SDK [blue]{resolvedChannelVersion}[/] to [blue]{resolvedInstallPath}[/]..."); - string downloadLink = "https://builds.dotnet.microsoft.com/dotnet/Sdk/9.0.303/dotnet-sdk-9.0.303-win-x64.exe"; + SpectreAnsiConsole.Progress() + .Start(ctx => + { + _dotnetInstaller.InstallSdks(resolvedInstallPath, ctx, new[] { resolvedChannelVersion }.Concat(additionalVersionsToInstall)); + }); - // Download the file to a temp path with progress - using (var httpClient = new System.Net.Http.HttpClient()) + if (resolvedSetDefaultInstall == true) { - SpectreAnsiConsole.Progress() - .Start(ctx => - { - var task = ctx.AddTask($"Downloading .NET SDK {resolvedChannelVersion}"); - - List additionalDownloads = additionalVersionsToInstall.Select(version => - { - var additionalTask = ctx.AddTask($"Downloading .NET SDK {version}"); - return (Action)(() => - { - Download(downloadLink, httpClient, additionalTask); - }); - }).ToList(); + _dotnetInstaller.ConfigureInstallType(SdkInstallType.User, resolvedInstallPath); + } - Download(downloadLink, httpClient, task); + if (resolvedUpdateGlobalJson == true) + { + _dotnetInstaller.UpdateGlobalJson(globalJsonInfo!.GlobalJsonPath!, resolvedChannelVersion, globalJsonInfo.AllowPrerelease, globalJsonInfo.RollForward); + } - foreach (var additionalDownload in additionalDownloads) - { - additionalDownload(); - } - }); - } SpectreAnsiConsole.WriteLine($"Complete!"); return 0; } - void Download(string url, HttpClient httpClient, ProgressTask task) - { - //string tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetFileName(url)); - //using (var response = httpClient.GetAsync(url, System.Net.Http.HttpCompletionOption.ResponseHeadersRead).GetAwaiter().GetResult()) - //{ - // response.EnsureSuccessStatusCode(); - // var contentLength = response.Content.Headers.ContentLength ?? 0; - // using (var stream = response.Content.ReadAsStream()) - // using (var fileStream = File.Create(tempFilePath)) - // { - // var buffer = new byte[81920]; - // long totalRead = 0; - // int read; - // while ((read = stream.Read(buffer, 0, buffer.Length)) > 0) - // { - // fileStream.Write(buffer, 0, read); - // totalRead += read; - // if (contentLength > 0) - // { - // task.Value = (double)totalRead / contentLength * 100; - // } - // } - // task.Value = 100; - // } - //} - - for (int i = 0; i < 100; i++) - { - task.Increment(1); - Thread.Sleep(20); // Simulate some work - } - task.Value = 100; - } + string? ResolveChannelFromGlobalJson(string globalJsonPath) { @@ -330,6 +290,72 @@ public SdkInstallType GetConfiguredInstallType(out string? currentInstallPath) } return latestAdminVersion; } + + public void InstallSdks(string dotnetRoot, ProgressContext progressContext, IEnumerable sdkVersions) + { + //var task = progressContext.AddTask($"Downloading .NET SDK {resolvedChannelVersion}"); + using (var httpClient = new System.Net.Http.HttpClient()) + { + List downloads = sdkVersions.Select(version => + { + string downloadLink = "https://builds.dotnet.microsoft.com/dotnet/Sdk/9.0.303/dotnet-sdk-9.0.303-win-x64.exe"; + var task = progressContext.AddTask($"Downloading .NET SDK {version}"); + return (Action)(() => + { + Download(downloadLink, httpClient, task); + }); + }).ToList(); + + + foreach (var download in downloads) + { + download(); + } + } + } + + void Download(string url, HttpClient httpClient, ProgressTask task) + { + //string tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetFileName(url)); + //using (var response = httpClient.GetAsync(url, System.Net.Http.HttpCompletionOption.ResponseHeadersRead).GetAwaiter().GetResult()) + //{ + // response.EnsureSuccessStatusCode(); + // var contentLength = response.Content.Headers.ContentLength ?? 0; + // using (var stream = response.Content.ReadAsStream()) + // using (var fileStream = File.Create(tempFilePath)) + // { + // var buffer = new byte[81920]; + // long totalRead = 0; + // int read; + // while ((read = stream.Read(buffer, 0, buffer.Length)) > 0) + // { + // fileStream.Write(buffer, 0, read); + // totalRead += read; + // if (contentLength > 0) + // { + // task.Value = (double)totalRead / contentLength * 100; + // } + // } + // task.Value = 100; + // } + //} + + for (int i = 0; i < 100; i++) + { + task.Increment(1); + Thread.Sleep(20); // Simulate some work + } + task.Value = 100; + } + + public void UpdateGlobalJson(string globalJsonPath, string? sdkVersion = null, bool? allowPrerelease = null, string? rollForward = null) + { + SpectreAnsiConsole.WriteLine($"Updating {globalJsonPath} to SDK version {sdkVersion} (AllowPrerelease={allowPrerelease}, RollForward={rollForward})"); + } + public void ConfigureInstallType(SdkInstallType installType, string? dotnetRoot = null) + { + SpectreAnsiConsole.WriteLine($"Configuring install type to {installType} (dotnetRoot={dotnetRoot})"); + } } class EnvironmentVariableMockReleaseInfoProvider : IReleaseInfoProvider diff --git a/src/Installer/dnup/DotnetInstaller.cs b/src/Installer/dnup/DotnetInstaller.cs index 34b104dba6cc..5c4a7ccce83f 100644 --- a/src/Installer/dnup/DotnetInstaller.cs +++ b/src/Installer/dnup/DotnetInstaller.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Text.Json; using Microsoft.DotNet.Cli.Utils; +using Spectre.Console; namespace Microsoft.DotNet.Tools.Bootstrapper; @@ -99,4 +100,46 @@ public GlobalJsonInfo GetGlobalJsonInfo(string initialDirectory) // TODO: Implement this return null; } + + public void InstallSdks(string dotnetRoot, ProgressContext progressContext, IEnumerable sdkVersions) => throw new NotImplementedException(); + public void UpdateGlobalJson(string globalJsonPath, string? sdkVersion = null, bool? allowPrerelease = null, string? rollForward = null) => throw new NotImplementedException(); + + public void ConfigureInstallType(SdkInstallType installType, string? dotnetRoot = null) + { + // Get current PATH + var path = Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.User) ?? string.Empty; + var pathEntries = path.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries).ToList(); + string exeName = OperatingSystem.IsWindows() ? "dotnet.exe" : "dotnet"; + // Remove only actual dotnet installation folders from PATH + pathEntries = pathEntries.Where(p => !File.Exists(Path.Combine(p, exeName))).ToList(); + + switch (installType) + { + case SdkInstallType.User: + if (string.IsNullOrEmpty(dotnetRoot)) + throw new ArgumentNullException(nameof(dotnetRoot)); + // Add dotnetRoot to PATH + pathEntries.Insert(0, dotnetRoot); + // Set DOTNET_ROOT + Environment.SetEnvironmentVariable("DOTNET_ROOT", dotnetRoot, EnvironmentVariableTarget.User); + break; + case SdkInstallType.Admin: + if (string.IsNullOrEmpty(dotnetRoot)) + throw new ArgumentNullException(nameof(dotnetRoot)); + // Add dotnetRoot to PATH + pathEntries.Insert(0, dotnetRoot); + // Unset DOTNET_ROOT + Environment.SetEnvironmentVariable("DOTNET_ROOT", null, EnvironmentVariableTarget.User); + break; + case SdkInstallType.None: + // Unset DOTNET_ROOT + Environment.SetEnvironmentVariable("DOTNET_ROOT", null, EnvironmentVariableTarget.User); + break; + default: + throw new ArgumentException($"Unknown install type: {installType}", nameof(installType)); + } + // Update PATH + var newPath = string.Join(Path.PathSeparator, pathEntries); + Environment.SetEnvironmentVariable("PATH", newPath, EnvironmentVariableTarget.User); + } } diff --git a/src/Installer/dnup/IDotnetInstaller.cs b/src/Installer/dnup/IDotnetInstaller.cs index 443c9a12666c..47affcc51390 100644 --- a/src/Installer/dnup/IDotnetInstaller.cs +++ b/src/Installer/dnup/IDotnetInstaller.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Text; +using Spectre.Console; namespace Microsoft.DotNet.Tools.Bootstrapper; @@ -16,6 +17,14 @@ public interface IDotnetInstaller SdkInstallType GetConfiguredInstallType(out string? currentInstallPath); string? GetLatestInstalledAdminVersion(); + + void InstallSdks(string dotnetRoot, ProgressContext progressContext, IEnumerable sdkVersions); + + void UpdateGlobalJson(string globalJsonPath, string? sdkVersion = null, bool? allowPrerelease = null, string? rollForward = null); + + void ConfigureInstallType(SdkInstallType installType, string? dotnetRoot = null); + + } public enum SdkInstallType