diff --git a/src/Installer/dnup/ArchiveDotnetInstaller.cs b/src/Installer/dnup/ArchiveDotnetInstaller.cs index cc42922366a8..1510a597f220 100644 --- a/src/Installer/dnup/ArchiveDotnetInstaller.cs +++ b/src/Installer/dnup/ArchiveDotnetInstaller.cs @@ -8,38 +8,39 @@ using System.IO.Compression; using System.Linq; using System.Runtime.InteropServices; +using Microsoft.Deployment.DotNet.Releases; namespace Microsoft.DotNet.Tools.Bootstrapper; internal class ArchiveDotnetInstaller : IDotnetInstaller, IDisposable { private readonly DotnetInstallRequest _request; - private readonly DotnetInstall _install; + private readonly ReleaseVersion _resolvedVersion; private string scratchDownloadDirectory; private string? _archivePath; - public ArchiveDotnetInstaller(DotnetInstallRequest request, DotnetInstall version) + public ArchiveDotnetInstaller(DotnetInstallRequest request, ReleaseVersion resolvedVersion) { _request = request; - _install = version; + _resolvedVersion = resolvedVersion; scratchDownloadDirectory = Directory.CreateTempSubdirectory().FullName; } public void Prepare() { using var releaseManifest = new ReleaseManifest(); - var archiveName = $"dotnet-{_install.Id}"; + var archiveName = $"dotnet-{Guid.NewGuid()}"; _archivePath = Path.Combine(scratchDownloadDirectory, archiveName + DnupUtilities.GetArchiveFileExtensionForPlatform()); Spectre.Console.AnsiConsole.Progress() .Start(ctx => { - var downloadTask = ctx.AddTask($"Downloading .NET SDK {_install.FullySpecifiedVersion.Value}", autoStart: true); - var reporter = new SpectreDownloadProgressReporter(downloadTask, $"Downloading .NET SDK {_install.FullySpecifiedVersion.Value}"); - var downloadSuccess = releaseManifest.DownloadArchiveWithVerification(_install, _archivePath, reporter); + var downloadTask = ctx.AddTask($"Downloading .NET SDK {_resolvedVersion}", autoStart: true); + var reporter = new SpectreDownloadProgressReporter(downloadTask, $"Downloading .NET SDK {_resolvedVersion}"); + var downloadSuccess = releaseManifest.DownloadArchiveWithVerification(_request, _resolvedVersion, _archivePath, reporter); if (!downloadSuccess) { - throw new InvalidOperationException($"Failed to download .NET archive for version {_install.FullySpecifiedVersion.Value}"); + throw new InvalidOperationException($"Failed to download .NET archive for version {_resolvedVersion}"); } downloadTask.Value = 100; @@ -83,10 +84,10 @@ internal static string ConstructArchiveName(string? versionString, string rid, s public void Commit() { - Commit(GetExistingSdkVersions(_request.TargetDirectory)); + Commit(GetExistingSdkVersions(_request.InstallRoot)); } - public void Commit(IEnumerable existingSdkVersions) + public void Commit(IEnumerable existingSdkVersions) { if (_archivePath == null || !File.Exists(_archivePath)) { @@ -96,10 +97,10 @@ public void Commit(IEnumerable existingSdkVersions) Spectre.Console.AnsiConsole.Progress() .Start(ctx => { - var installTask = ctx.AddTask($"Installing .NET SDK {_install.FullySpecifiedVersion.Value}", autoStart: true); + var installTask = ctx.AddTask($"Installing .NET SDK {_resolvedVersion}", autoStart: true); // Extract archive directly to target directory with special handling for muxer - var extractResult = ExtractArchiveDirectlyToTarget(_archivePath, _request.TargetDirectory, existingSdkVersions, installTask); + var extractResult = ExtractArchiveDirectlyToTarget(_archivePath, _request.InstallRoot.Path!, existingSdkVersions, installTask); if (extractResult != null) { throw new InvalidOperationException($"Failed to install SDK: {extractResult}"); @@ -113,7 +114,7 @@ public void Commit(IEnumerable existingSdkVersions) * Extracts the archive directly to the target directory with special handling for muxer. * Combines extraction and installation into a single operation. */ - private string? ExtractArchiveDirectlyToTarget(string archivePath, string targetDir, IEnumerable existingSdkVersions, Spectre.Console.ProgressTask? installTask) + private string? ExtractArchiveDirectlyToTarget(string archivePath, string targetDir, IEnumerable existingSdkVersions, Spectre.Console.ProgressTask? installTask) { try { @@ -140,14 +141,14 @@ public void Commit(IEnumerable existingSdkVersions) /** * Configure muxer handling by determining if it needs to be updated. */ - private MuxerHandlingConfig ConfigureMuxerHandling(IEnumerable existingSdkVersions) + private MuxerHandlingConfig ConfigureMuxerHandling(IEnumerable existingSdkVersions) { - DotnetVersion? existingMuxerVersion = existingSdkVersions.Any() ? existingSdkVersions.Max() : (DotnetVersion?)null; - DotnetVersion newRuntimeVersion = _install.FullySpecifiedVersion; + ReleaseVersion? existingMuxerVersion = existingSdkVersions.Any() ? existingSdkVersions.Max() : (ReleaseVersion?)null; + ReleaseVersion newRuntimeVersion = _resolvedVersion; bool shouldUpdateMuxer = existingMuxerVersion is null || newRuntimeVersion.CompareTo(existingMuxerVersion) > 0; string muxerName = DnupUtilities.GetDotnetExeName(); - string muxerTargetPath = Path.Combine(_request.TargetDirectory, muxerName); + string muxerTargetPath = Path.Combine(_request.InstallRoot.Path!, muxerName); return new MuxerHandlingConfig( muxerName, @@ -421,12 +422,16 @@ public void Dispose() } } + // TODO: InstallerOrchestratorSingleton also checks existing installs via the manifest. Which should we use and where? // This should be cached and more sophisticated based on vscode logic in the future - private IEnumerable GetExistingSdkVersions(string targetDirectory) + private IEnumerable GetExistingSdkVersions(DotnetInstallRoot installRoot) { - var dotnetExe = Path.Combine(targetDirectory, DnupUtilities.GetDotnetExeName()); + if (installRoot.Path == null) + return Enumerable.Empty(); + + var dotnetExe = Path.Combine(installRoot.Path, DnupUtilities.GetDotnetExeName()); if (!File.Exists(dotnetExe)) - return Enumerable.Empty(); + return Enumerable.Empty(); try { @@ -440,14 +445,14 @@ private IEnumerable GetExistingSdkVersions(string targetDirectory var output = process.StandardOutput.ReadToEnd(); process.WaitForExit(); - var versions = new List(); + var versions = new List(); foreach (var line in output.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)) { var parts = line.Split(' '); if (parts.Length > 0) { var versionStr = parts[0]; - if (DotnetVersion.TryParse(versionStr, out var version)) + if (ReleaseVersion.TryParse(versionStr, out var version)) { versions.Add(version); } diff --git a/src/Installer/dnup/BootstrapperController.cs b/src/Installer/dnup/BootstrapperController.cs index 0b9b37305912..3e7a6b1e888a 100644 --- a/src/Installer/dnup/BootstrapperController.cs +++ b/src/Installer/dnup/BootstrapperController.cs @@ -19,17 +19,17 @@ public BootstrapperController(IEnvironmentProvider? environmentProvider = null) _environmentProvider = environmentProvider ?? new EnvironmentProvider(); } - public InstallType GetConfiguredInstallType(out string? currentInstallPath) + public DotnetInstallRoot GetConfiguredInstallType() { - currentInstallPath = null; + string? foundDotnet = _environmentProvider.GetCommandPath("dotnet"); if (string.IsNullOrEmpty(foundDotnet)) { - return InstallType.None; + return new(null, InstallType.None, DnupUtilities.GetDefaultInstallArchitecture()); } string installDir = Path.GetDirectoryName(foundDotnet)!; - currentInstallPath = installDir; + string? dotnetRoot = Environment.GetEnvironmentVariable("DOTNET_ROOT"); string programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); @@ -44,18 +44,18 @@ public InstallType GetConfiguredInstallType(out string? currentInstallPath) !dotnetRoot.StartsWith(Path.Combine(programFiles, "dotnet"), StringComparison.OrdinalIgnoreCase) && !dotnetRoot.StartsWith(Path.Combine(programFilesX86, "dotnet"), StringComparison.OrdinalIgnoreCase)) { - return InstallType.Inconsistent; + return new(installDir, InstallType.Inconsistent, DnupUtilities.GetDefaultInstallArchitecture()); } - return InstallType.Admin; + return new(installDir, InstallType.Admin, DnupUtilities.GetDefaultInstallArchitecture()); } else { // User install: DOTNET_ROOT must be set and match installDir if (string.IsNullOrEmpty(dotnetRoot) || !DnupUtilities.PathsEqual(dotnetRoot, installDir)) { - return InstallType.Inconsistent; + return new(installDir, InstallType.Inconsistent, DnupUtilities.GetDefaultInstallArchitecture()); } - return InstallType.User; + return new(installDir, InstallType.User, DnupUtilities.GetDefaultInstallArchitecture()); } } @@ -96,35 +96,31 @@ public GlobalJsonInfo GetGlobalJsonInfo(string initialDirectory) return null; } - public void InstallSdks(string dotnetRoot, ProgressContext progressContext, IEnumerable sdkVersions) + public void InstallSdks(DotnetInstallRoot dotnetRoot, ProgressContext progressContext, IEnumerable sdkVersions) { foreach (var channelVersion in sdkVersions) { - InstallSDK(dotnetRoot, progressContext, channelVersion); + InstallSDK(dotnetRoot, progressContext, new UpdateChannel(channelVersion)); } } - private void InstallSDK(string dotnetRoot, ProgressContext progressContext, string channelVersion) + private void InstallSDK(DotnetInstallRoot dotnetRoot, ProgressContext progressContext, UpdateChannel channnel) { DotnetInstallRequest request = new DotnetInstallRequest( - channelVersion, dotnetRoot, - InstallType.User, - InstallMode.SDK, - // Get current machine architecture and convert it to correct enum value - DnupUtilities.GetInstallArchitecture(RuntimeInformation.ProcessArchitecture), - new ManagementCadence(ManagementCadenceType.DNUP), + channnel, + InstallComponent.SDK, new InstallRequestOptions() ); DotnetInstall? newInstall = InstallerOrchestratorSingleton.Instance.Install(request); if (newInstall == null) { - throw new Exception($"Failed to install .NET SDK {channelVersion}"); + throw new Exception($"Failed to install .NET SDK {channnel.Name}"); } else { - Spectre.Console.AnsiConsole.MarkupLine($"[green]Installed .NET SDK {newInstall.FullySpecifiedVersion}, available via {newInstall.MuxerDirectory}[/]"); + Spectre.Console.AnsiConsole.MarkupLine($"[green]Installed .NET SDK {newInstall.Version}, available via {newInstall.InstallRoot}[/]"); } } diff --git a/src/Installer/dnup/Commands/Sdk/Install/EnvironmentVariableMockDotnetInstaller.cs b/src/Installer/dnup/Commands/Sdk/Install/EnvironmentVariableMockDotnetInstaller.cs index 50004365e49c..4617d65eabe8 100644 --- a/src/Installer/dnup/Commands/Sdk/Install/EnvironmentVariableMockDotnetInstaller.cs +++ b/src/Installer/dnup/Commands/Sdk/Install/EnvironmentVariableMockDotnetInstaller.cs @@ -27,16 +27,16 @@ public string GetDefaultDotnetInstallPath() return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "dotnet"); } - public InstallType GetConfiguredInstallType(out string? currentInstallPath) + public DotnetInstallRoot GetConfiguredInstallType() { var testHookDefaultInstall = Environment.GetEnvironmentVariable("DOTNET_TESTHOOK_DEFAULT_INSTALL"); - InstallType returnValue = InstallType.None; - if (!Enum.TryParse(testHookDefaultInstall, out returnValue)) + InstallType installtype = InstallType.None; + if (!Enum.TryParse(testHookDefaultInstall, out installtype)) { - returnValue = InstallType.None; + installtype = InstallType.None; } - currentInstallPath = Environment.GetEnvironmentVariable("DOTNET_TESTHOOK_CURRENT_INSTALL_PATH"); - return returnValue; + var installPath = Environment.GetEnvironmentVariable("DOTNET_TESTHOOK_CURRENT_INSTALL_PATH"); + return new(installPath, installtype, DnupUtilities.GetDefaultInstallArchitecture()); } public string? GetLatestInstalledAdminVersion() @@ -49,7 +49,7 @@ public InstallType GetConfiguredInstallType(out string? currentInstallPath) return latestAdminVersion; } - public void InstallSdks(string dotnetRoot, ProgressContext progressContext, IEnumerable sdkVersions) + public void InstallSdks(DotnetInstallRoot dotnetRoot, ProgressContext progressContext, IEnumerable sdkVersions) { using (var httpClient = new HttpClient()) { diff --git a/src/Installer/dnup/Commands/Sdk/Install/SdkInstallCommand.cs b/src/Installer/dnup/Commands/Sdk/Install/SdkInstallCommand.cs index cfbeb2d195c4..888f0f65e689 100644 --- a/src/Installer/dnup/Commands/Sdk/Install/SdkInstallCommand.cs +++ b/src/Installer/dnup/Commands/Sdk/Install/SdkInstallCommand.cs @@ -29,8 +29,7 @@ public override int Execute() { var globalJsonInfo = _dotnetInstaller.GetGlobalJsonInfo(Environment.CurrentDirectory); - string? currentInstallPath; - InstallType defaultInstallState = _dotnetInstaller.GetConfiguredInstallType(out currentInstallPath); + var currentDotnetInstallRoot = _dotnetInstaller.GetConfiguredInstallType(); string? resolvedInstallPath = null; @@ -55,10 +54,10 @@ public override int Execute() resolvedInstallPath = _installPath; } - if (resolvedInstallPath == null && defaultInstallState == InstallType.User) + if (resolvedInstallPath == null && currentDotnetInstallRoot.Type == InstallType.User) { // If a user installation is already set up, we don't need to prompt for the install path - resolvedInstallPath = currentInstallPath; + resolvedInstallPath = currentDotnetInstallRoot.Path; } if (resolvedInstallPath == null) @@ -133,35 +132,35 @@ public override int Execute() // If global.json specified an install path, we don't prompt for setting the default install path (since you probably don't want to do that for a repo-local path) if (_interactive && installPathFromGlobalJson == null) { - if (defaultInstallState == InstallType.None) + if (currentDotnetInstallRoot.Type == InstallType.None) { resolvedSetDefaultInstall = SpectreAnsiConsole.Confirm( $"Do you want to set the install path ({resolvedInstallPath}) as the default dotnet install? This will update the PATH and DOTNET_ROOT environment variables.", defaultValue: true); } - else if (defaultInstallState == InstallType.User) + else if (currentDotnetInstallRoot.Type == InstallType.User) { - if (DnupUtilities.PathsEqual(resolvedInstallPath, currentInstallPath)) + if (DnupUtilities.PathsEqual(resolvedInstallPath, currentDotnetInstallRoot.Path)) { // No need to prompt here, the default install is already set up. } else { resolvedSetDefaultInstall = SpectreAnsiConsole.Confirm( - $"The default dotnet install is currently set to {currentInstallPath}. Do you want to change it to {resolvedInstallPath}?", + $"The default dotnet install is currently set to {currentDotnetInstallRoot.Path}. Do you want to change it to {resolvedInstallPath}?", defaultValue: false); } } - else if (defaultInstallState == InstallType.Admin) + else if (currentDotnetInstallRoot.Type == InstallType.Admin) { - SpectreAnsiConsole.WriteLine($"You have an existing admin install of .NET in {currentInstallPath}. We can configure your system to use the new install of .NET " + + SpectreAnsiConsole.WriteLine($"You have an existing admin install of .NET in {currentDotnetInstallRoot.Path}. We can configure your system to use the new install of .NET " + $"in {resolvedInstallPath} instead. This would mean that the admin install of .NET would no longer be accessible from the PATH or from Visual Studio."); SpectreAnsiConsole.WriteLine("You can change this later with the \"dotnet defaultinstall\" command."); resolvedSetDefaultInstall = SpectreAnsiConsole.Confirm( $"Do you want to set the user install path ({resolvedInstallPath}) as the default dotnet install? This will update the PATH and DOTNET_ROOT environment variables.", defaultValue: true); } - else if (defaultInstallState == InstallType.Inconsistent) + else if (currentDotnetInstallRoot.Type == InstallType.Inconsistent) { // TODO: Figure out what to do here resolvedSetDefaultInstall = false; @@ -177,23 +176,19 @@ public override int Execute() // Create a request and resolve it using the channel version resolver var installRequest = new DotnetInstallRequest( - resolvedChannel, - resolvedInstallPath, - InstallType.User, - InstallMode.SDK, - DnupUtilities.GetInstallArchitecture(RuntimeInformation.ProcessArchitecture), - new ManagementCadence(ManagementCadenceType.DNUP), + new DotnetInstallRoot(resolvedInstallPath, InstallType.User, DnupUtilities.GetInstallArchitecture(RuntimeInformation.ProcessArchitecture)), + new UpdateChannel(resolvedChannel), + InstallComponent.SDK, new InstallRequestOptions()); - var resolvedInstall = _channelVersionResolver.Resolve(installRequest); - var resolvedChannelVersion = resolvedInstall.FullySpecifiedVersion.Value; + var resolvedVersion = _channelVersionResolver.Resolve(installRequest); - if (resolvedSetDefaultInstall == true && defaultInstallState == InstallType.Admin) + if (resolvedSetDefaultInstall == true && currentDotnetInstallRoot.Type == InstallType.Admin) { if (_interactive) { var latestAdminVersion = _dotnetInstaller.GetLatestInstalledAdminVersion(); - if (latestAdminVersion != null && new ReleaseVersion(resolvedChannelVersion) < new ReleaseVersion(latestAdminVersion)) + if (latestAdminVersion != null && resolvedVersion < new ReleaseVersion(latestAdminVersion)) { SpectreAnsiConsole.WriteLine($"Since the admin installs of the .NET SDK will no longer be accessible, we recommend installing the latest admin installed " + $"version ({latestAdminVersion}) to the new user install location. This will make sure this version of the .NET SDK continues to be used for projects that don't specify a .NET SDK version in global.json."); @@ -213,7 +208,7 @@ public override int Execute() // TODO: Implement transaction / rollback? - SpectreAnsiConsole.MarkupInterpolated($"Installing .NET SDK [blue]{resolvedChannelVersion}[/] to [blue]{resolvedInstallPath}[/]..."); + SpectreAnsiConsole.MarkupInterpolated($"Installing .NET SDK [blue]{resolvedVersion}[/] to [blue]{resolvedInstallPath}[/]..."); // Create and use a progress context var progressContext = SpectreAnsiConsole.Progress().Start(ctx => ctx); @@ -222,22 +217,19 @@ public override int Execute() DotnetInstall? mainInstall = InstallerOrchestratorSingleton.Instance.Install(installRequest); if (mainInstall == null) { - SpectreAnsiConsole.MarkupLine($"[red]Failed to install .NET SDK {resolvedChannelVersion}[/]"); + SpectreAnsiConsole.MarkupLine($"[red]Failed to install .NET SDK {resolvedVersion}[/]"); return 1; } - SpectreAnsiConsole.MarkupLine($"[green]Installed .NET SDK {mainInstall.FullySpecifiedVersion}, available via {mainInstall.MuxerDirectory}[/]"); + SpectreAnsiConsole.MarkupLine($"[green]Installed .NET SDK {mainInstall.Version}, available via {mainInstall.InstallRoot}[/]"); // Install any additional versions foreach (var additionalVersion in additionalVersionsToInstall) { // Create the request for the additional version var additionalRequest = new DotnetInstallRequest( - additionalVersion, - resolvedInstallPath, - InstallType.User, - InstallMode.SDK, - DnupUtilities.GetInstallArchitecture(RuntimeInformation.ProcessArchitecture), - new ManagementCadence(ManagementCadenceType.DNUP), + new DotnetInstallRoot(resolvedInstallPath, InstallType.User, DnupUtilities.GetInstallArchitecture(RuntimeInformation.ProcessArchitecture)), + new UpdateChannel(additionalVersion), + InstallComponent.SDK, new InstallRequestOptions()); // Install the additional version directly using InstallerOrchestratorSingleton @@ -248,7 +240,7 @@ public override int Execute() } else { - SpectreAnsiConsole.MarkupLine($"[green]Installed additional .NET SDK {additionalInstall.FullySpecifiedVersion}, available via {additionalInstall.MuxerDirectory}[/]"); + SpectreAnsiConsole.MarkupLine($"[green]Installed additional .NET SDK {additionalInstall.Version}, available via {additionalInstall.InstallRoot}[/]"); } } @@ -259,7 +251,7 @@ public override int Execute() if (resolvedUpdateGlobalJson == true) { - _dotnetInstaller.UpdateGlobalJson(globalJsonInfo!.GlobalJsonPath!, resolvedChannelVersion, globalJsonInfo.AllowPrerelease, globalJsonInfo.RollForward); + _dotnetInstaller.UpdateGlobalJson(globalJsonInfo!.GlobalJsonPath!, resolvedVersion!.ToString(), globalJsonInfo.AllowPrerelease, globalJsonInfo.RollForward); } diff --git a/src/Installer/dnup/DnupSharedManifest.cs b/src/Installer/dnup/DnupSharedManifest.cs index dc935366ee97..a1b11a869bfa 100644 --- a/src/Installer/dnup/DnupSharedManifest.cs +++ b/src/Installer/dnup/DnupSharedManifest.cs @@ -73,15 +73,15 @@ public IEnumerable GetInstalledVersions(IInstallationValidator? v /// /// Gets installed versions filtered by a specific muxer directory. /// - /// Directory to filter by (must match the MuxerDirectory property) + /// Directory to filter by (must match the InstallRoot property) /// Optional validator to check installation validity /// Installations that match the specified directory - public IEnumerable GetInstalledVersions(string muxerDirectory, IInstallationValidator? validator = null) + public IEnumerable GetInstalledVersions(DotnetInstallRoot installRoot, IInstallationValidator? validator = null) { return GetInstalledVersions(validator) .Where(install => DnupUtilities.PathsEqual( - Path.GetFullPath(install.MuxerDirectory), - Path.GetFullPath(muxerDirectory))); + Path.GetFullPath(install.InstallRoot.Path!), + Path.GetFullPath(installRoot.Path!))); } public void AddInstalledVersion(DotnetInstall version) @@ -102,7 +102,7 @@ public void RemoveInstalledVersion(DotnetInstall version) EnsureManifestExists(); var installs = GetInstalledVersions().ToList(); - installs.RemoveAll(i => i.Id == version.Id && i.FullySpecifiedVersion == version.FullySpecifiedVersion); + installs.RemoveAll(i => DnupUtilities.PathsEqual(i.InstallRoot.Path, version.InstallRoot.Path) && i.Version.Equals(version.Version)); var json = JsonSerializer.Serialize(installs, DnupManifestJsonContext.Default.ListDotnetInstall); File.WriteAllText(ManifestPath, json); } diff --git a/src/Installer/dnup/DnupUtilities.cs b/src/Installer/dnup/DnupUtilities.cs index c2b5a901918d..5ef1738bb4a3 100644 --- a/src/Installer/dnup/DnupUtilities.cs +++ b/src/Installer/dnup/DnupUtilities.cs @@ -45,6 +45,11 @@ public static InstallArchitecture GetInstallArchitecture(System.Runtime.InteropS }; } + public static InstallArchitecture GetDefaultInstallArchitecture() + { + return GetInstallArchitecture(RuntimeInformation.ProcessArchitecture); + } + public static void ForceReplaceFile(string sourcePath, string destPath) { File.Copy(sourcePath, destPath, overwrite: true); diff --git a/src/Installer/dnup/DotnetInstall.cs b/src/Installer/dnup/DotnetInstall.cs index 7ff4b756f224..470b73f530b8 100644 --- a/src/Installer/dnup/DotnetInstall.cs +++ b/src/Installer/dnup/DotnetInstall.cs @@ -2,24 +2,16 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using Microsoft.Deployment.DotNet.Releases; namespace Microsoft.DotNet.Tools.Bootstrapper; -/// -/// Base record for .NET installation information with common properties. -/// -public record DotnetInstallBase( - string ResolvedDirectory, +public record DotnetInstallRoot( + string? Path, InstallType Type, - InstallMode Mode, InstallArchitecture Architecture) { - public Guid Id { get; } = Guid.NewGuid(); -} - -public record InstallRequestOptions() -{ - // Include things such as the custom feed here. + // Do we need a GUID for the ID here? } /// @@ -27,21 +19,20 @@ public record InstallRequestOptions() /// The MuxerDirectory is the directory of the corresponding .NET host that has visibility into this .NET installation. /// public record DotnetInstall( - DotnetVersion FullySpecifiedVersion, - string MuxerDirectory, - InstallType Type, - InstallMode Mode, - InstallArchitecture Architecture, - ManagementCadence Cadence) : DotnetInstallBase(MuxerDirectory, Type, Mode, Architecture); + DotnetInstallRoot InstallRoot, + ReleaseVersion Version, + InstallComponent Component); /// /// Represents a request for a .NET installation with a channel version that will get resolved into a fully specified version. /// public record DotnetInstallRequest( - string ChannelVersion, - string TargetDirectory, - InstallType Type, - InstallMode Mode, - InstallArchitecture Architecture, - ManagementCadence Cadence, - InstallRequestOptions Options) : DotnetInstallBase(Path.Combine(TargetDirectory, DnupUtilities.GetDotnetExeName()), Type, Mode, Architecture); + DotnetInstallRoot InstallRoot, + UpdateChannel Channel, + InstallComponent Component, + InstallRequestOptions Options); + +public record InstallRequestOptions() +{ + // Include things such as the custom feed here. +} diff --git a/src/Installer/dnup/IBootstrapperController.cs b/src/Installer/dnup/IBootstrapperController.cs index bc4dd5aa7e17..be4d7760352b 100644 --- a/src/Installer/dnup/IBootstrapperController.cs +++ b/src/Installer/dnup/IBootstrapperController.cs @@ -14,11 +14,11 @@ public interface IBootstrapperController string GetDefaultDotnetInstallPath(); - InstallType GetConfiguredInstallType(out string? currentInstallPath); + DotnetInstallRoot GetConfiguredInstallType(); string? GetLatestInstalledAdminVersion(); - void InstallSdks(string dotnetRoot, ProgressContext progressContext, IEnumerable sdkVersions); + void InstallSdks(DotnetInstallRoot dotnetRoot, ProgressContext progressContext, IEnumerable sdkVersions); void UpdateGlobalJson(string globalJsonPath, string? sdkVersion = null, bool? allowPrerelease = null, string? rollForward = null); diff --git a/src/Installer/dnup/IDnupManifest.cs b/src/Installer/dnup/IDnupManifest.cs index 252eefd41e65..4aa56b20dcb6 100644 --- a/src/Installer/dnup/IDnupManifest.cs +++ b/src/Installer/dnup/IDnupManifest.cs @@ -2,13 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using Microsoft.Deployment.DotNet.Releases; namespace Microsoft.DotNet.Tools.Bootstrapper { internal interface IDnupManifest { IEnumerable GetInstalledVersions(IInstallationValidator? validator = null); - IEnumerable GetInstalledVersions(string muxerDirectory, IInstallationValidator? validator = null); + IEnumerable GetInstalledVersions(DotnetInstallRoot installRoot, IInstallationValidator? validator = null); void AddInstalledVersion(DotnetInstall version); void RemoveInstalledVersion(DotnetInstall version); } diff --git a/src/Installer/dnup/InstallMode.cs b/src/Installer/dnup/InstallComponent.cs similarity index 89% rename from src/Installer/dnup/InstallMode.cs rename to src/Installer/dnup/InstallComponent.cs index 329ac4e27416..a65f29c987b5 100644 --- a/src/Installer/dnup/InstallMode.cs +++ b/src/Installer/dnup/InstallComponent.cs @@ -3,11 +3,12 @@ namespace Microsoft.DotNet.Tools.Bootstrapper { - public enum InstallMode + public enum InstallComponent { SDK, Runtime, ASPNETCore, WindowsDesktop } + } diff --git a/src/Installer/dnup/InstallerOrchestratorSingleton.cs b/src/Installer/dnup/InstallerOrchestratorSingleton.cs index 0384e885e0c5..f17a6d64bc97 100644 --- a/src/Installer/dnup/InstallerOrchestratorSingleton.cs +++ b/src/Installer/dnup/InstallerOrchestratorSingleton.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using Microsoft.Deployment.DotNet.Releases; namespace Microsoft.DotNet.Tools.Bootstrapper; @@ -24,26 +25,37 @@ private InstallerOrchestratorSingleton() public DotnetInstall? Install(DotnetInstallRequest installRequest) { // Map InstallRequest to DotnetInstallObject by converting channel to fully specified version - DotnetInstall install = new ManifestChannelVersionResolver().Resolve(installRequest); + ReleaseVersion? versionToInstall = new ManifestChannelVersionResolver().Resolve(installRequest); + + if (versionToInstall == null) + { + Console.WriteLine($"\nCould not resolve version for channel '{installRequest.Channel.Name}'."); + return null; + } + + DotnetInstall install = new( + installRequest.InstallRoot, + versionToInstall, + installRequest.Component); // Check if the install already exists and we don't need to do anything // read write mutex only for manifest? using (var finalizeLock = modifyInstallStateMutex()) { - if (InstallAlreadyExists(installRequest.ResolvedDirectory, install)) + if (InstallAlreadyExists(install)) { - Console.WriteLine($"\n.NET SDK {install.FullySpecifiedVersion.Value} is already installed, skipping installation."); + Console.WriteLine($"\n.NET SDK {versionToInstall} is already installed, skipping installation."); return install; } } - ArchiveDotnetInstaller installer = new(installRequest, install); + using ArchiveDotnetInstaller installer = new(installRequest, versionToInstall); installer.Prepare(); // Extract and commit the install to the directory using (var finalizeLock = modifyInstallStateMutex()) { - if (InstallAlreadyExists(installRequest.ResolvedDirectory, install)) + if (InstallAlreadyExists(install)) { return install; } @@ -68,24 +80,23 @@ private InstallerOrchestratorSingleton() /// /// Gets the existing installs from the manifest. Must hold a mutex over the directory. /// - private IEnumerable GetExistingInstalls(string directory) + private IEnumerable GetExistingInstalls(DotnetInstallRoot installRoot) { var manifestManager = new DnupSharedManifest(); // Use the overload that filters by muxer directory - return manifestManager.GetInstalledVersions(directory); + return manifestManager.GetInstalledVersions(installRoot); } /// /// Checks if the installation already exists. Must hold a mutex over the directory. /// - private bool InstallAlreadyExists(string directory, DotnetInstall install) + private bool InstallAlreadyExists(DotnetInstall install) { - var existingInstalls = GetExistingInstalls(directory); + var existingInstalls = GetExistingInstalls(install.InstallRoot); // Check if there's any existing installation that matches the version we're trying to install return existingInstalls.Any(existing => - existing.FullySpecifiedVersion.Value == install.FullySpecifiedVersion.Value && - existing.Type == install.Type && - existing.Architecture == install.Architecture); + existing.Version.Equals(install.Version) && + existing.Component == install.Component); } } diff --git a/src/Installer/dnup/ManifestChannelVersionResolver.cs b/src/Installer/dnup/ManifestChannelVersionResolver.cs index 2b3c89aff8a2..506bed9cd416 100644 --- a/src/Installer/dnup/ManifestChannelVersionResolver.cs +++ b/src/Installer/dnup/ManifestChannelVersionResolver.cs @@ -3,34 +3,22 @@ using System.Collections.Generic; using System.Linq; +using Microsoft.Deployment.DotNet.Releases; using Microsoft.DotNet.Tools.Bootstrapper; namespace Microsoft.DotNet.Tools.Bootstrapper; internal class ManifestChannelVersionResolver { - public DotnetInstall Resolve(DotnetInstallRequest dotnetChannelVersion) + public ReleaseVersion? Resolve(DotnetInstallRequest installRequest) { - string channel = dotnetChannelVersion.ChannelVersion; - DotnetVersion dotnetVersion = new DotnetVersion(channel); - // If not fully specified, resolve to latest using ReleaseManifest - if (!dotnetVersion.IsFullySpecified) + if (!installRequest.Channel.IsFullySpecifiedVersion()) { var manifest = new ReleaseManifest(); - var latestVersion = manifest.GetLatestVersionForChannel(channel, dotnetChannelVersion.Mode); - if (latestVersion != null) - { - dotnetVersion = new DotnetVersion(latestVersion); - } + return manifest.GetLatestVersionForChannel(installRequest.Channel, installRequest.Component); } - return new DotnetInstall( - dotnetVersion, - dotnetChannelVersion.ResolvedDirectory, - dotnetChannelVersion.Type, - dotnetChannelVersion.Mode, - dotnetChannelVersion.Architecture, - dotnetChannelVersion.Cadence); + return new ReleaseVersion(installRequest.Channel.Name); } } diff --git a/src/Installer/dnup/ReleaseManifest.cs b/src/Installer/dnup/ReleaseManifest.cs index 9b9dd7140c81..4909cadb65cf 100644 --- a/src/Installer/dnup/ReleaseManifest.cs +++ b/src/Installer/dnup/ReleaseManifest.cs @@ -24,9 +24,9 @@ internal class ReleaseManifest : IDisposable /// /// Channel string to parse (e.g., "9", "9.0", "9.0.1xx", "9.0.103") /// Tuple containing (major, minor, featureBand, isFullySpecified) - private (int Major, int Minor, string? FeatureBand, bool IsFullySpecified) ParseVersionChannel(string channel) + private (int Major, int Minor, string? FeatureBand, bool IsFullySpecified) ParseVersionChannel(UpdateChannel channel) { - var parts = channel.Split('.'); + var parts = channel.Name.Split('.'); int major = parts.Length > 0 && int.TryParse(parts[0], out var m) ? m : -1; int minor = parts.Length > 1 && int.TryParse(parts[1], out var n) ? n : -1; @@ -88,7 +88,7 @@ private List GetProductsForMajorVersion(ProductCollection index, int ma /// Optional major version filter /// Optional minor version filter /// Latest SDK version string, or null if none found - private string? GetLatestSdkVersion(IEnumerable releases, int? majorFilter = null, int? minorFilter = null) + private ReleaseVersion? GetLatestSdkVersion(IEnumerable releases, int? majorFilter = null, int? minorFilter = null) { var allSdks = releases .SelectMany(r => r.Sdks) @@ -100,7 +100,7 @@ private List GetProductsForMajorVersion(ProductCollection index, int ma if (allSdks.Any()) { - return allSdks.First().Version.ToString(); + return allSdks.First().Version; } return null; @@ -114,7 +114,7 @@ private List GetProductsForMajorVersion(ProductCollection index, int ma /// Optional minor version filter /// Optional runtime type filter (null for any runtime) /// Latest runtime version string, or null if none found - private string? GetLatestRuntimeVersion(IEnumerable releases, int? majorFilter = null, int? minorFilter = null, string? runtimeType = null) + private ReleaseVersion? GetLatestRuntimeVersion(IEnumerable releases, int? majorFilter = null, int? minorFilter = null, string? runtimeType = null) { var allRuntimes = releases.SelectMany(r => r.Runtimes).ToList(); @@ -155,7 +155,7 @@ private List GetProductsForMajorVersion(ProductCollection index, int ma if (allRuntimes.Any()) { - return allRuntimes.OrderByDescending(r => r.Version).First().Version.ToString(); + return allRuntimes.OrderByDescending(r => r.Version).First().Version; } return null; @@ -169,7 +169,7 @@ private List GetProductsForMajorVersion(ProductCollection index, int ma /// Minor version /// Feature band prefix (e.g., "1" for "1xx") /// Latest matching version string, or fallback format if none found - private string? GetLatestFeatureBandVersion(IEnumerable releases, int major, int minor, string featureBand) + private ReleaseVersion? GetLatestFeatureBandVersion(IEnumerable releases, int major, int minor, string featureBand) { var allSdkComponents = releases.SelectMany(r => r.Sdks).ToList(); @@ -190,11 +190,11 @@ private List GetProductsForMajorVersion(ProductCollection index, int ma if (featureBandSdks.Any()) { // Return the exact version from the latest matching SDK - return featureBandSdks.First().Version.ToString(); + return featureBandSdks.First().Version; } // Fallback if no actual release matches the feature band pattern - return $"{major}.{minor}.{featureBand}00"; + return null; } /// @@ -203,33 +203,28 @@ private List GetProductsForMajorVersion(ProductCollection index, int ma /// Channel string (e.g., "9", "9.0", "9.0.1xx", "9.0.103", "lts", "sts", "preview") /// InstallMode.SDK or InstallMode.Runtime /// Latest fully specified version string, or null if not found - public string? GetLatestVersionForChannel(string channel, InstallMode mode) + public ReleaseVersion? GetLatestVersionForChannel(UpdateChannel channel, InstallComponent component) { - // If channel is null or empty, return null - if (string.IsNullOrEmpty(channel)) - { - return null; - } - // Check for special channel strings (case insensitive) - if (string.Equals(channel, "lts", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(channel.Name, "lts", StringComparison.OrdinalIgnoreCase)) { // Handle LTS (Long-Term Support) channel var productIndex = ProductCollection.GetAsync().GetAwaiter().GetResult(); - return GetLatestVersionBySupportStatus(productIndex, isLts: true, mode); + return GetLatestVersionBySupportStatus(productIndex, isLts: true, component); } - else if (string.Equals(channel, "sts", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(channel.Name, "sts", StringComparison.OrdinalIgnoreCase)) { // Handle STS (Standard-Term Support) channel var productIndex = ProductCollection.GetAsync().GetAwaiter().GetResult(); - return GetLatestVersionBySupportStatus(productIndex, isLts: false, mode); + return GetLatestVersionBySupportStatus(productIndex, isLts: false, component); } - else if (string.Equals(channel, "preview", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(channel.Name, "preview", StringComparison.OrdinalIgnoreCase)) { // Handle Preview channel - get the latest preview version var productIndex = ProductCollection.GetAsync().GetAwaiter().GetResult(); - return GetLatestPreviewVersion(productIndex, mode); + return GetLatestPreviewVersion(productIndex, component); } // Parse the channel string into components + var (major, minor, featureBand, isFullySpecified) = ParseVersionChannel(channel); // If major is invalid, return null @@ -241,7 +236,7 @@ private List GetProductsForMajorVersion(ProductCollection index, int ma // If the version is already fully specified, just return it as-is if (isFullySpecified) { - return channel; + return new ReleaseVersion(channel.Name); } // Load the index manifest @@ -250,19 +245,19 @@ private List GetProductsForMajorVersion(ProductCollection index, int ma // Case 1: Major only version (e.g., "9") if (minor < 0) { - return GetLatestVersionForMajorOnly(index, major, mode); + return GetLatestVersionForMajorOnly(index, major, component); } // Case 2: Major.Minor version (e.g., "9.0") if (minor >= 0 && featureBand == null) { - return GetLatestVersionForMajorMinor(index, major, minor, mode); + return GetLatestVersionForMajorMinor(index, major, minor, component); } // Case 3: Feature band version (e.g., "9.0.1xx") if (minor >= 0 && featureBand != null) { - return GetLatestVersionForFeatureBand(index, major, minor, featureBand, mode); + return GetLatestVersionForFeatureBand(index, major, minor, featureBand, component); } return null; @@ -271,7 +266,7 @@ private List GetProductsForMajorVersion(ProductCollection index, int ma /// /// Gets the latest version for a major-only channel (e.g., "9"). /// - private string? GetLatestVersionForMajorOnly(ProductCollection index, int major, InstallMode mode) + private ReleaseVersion? GetLatestVersionForMajorOnly(ProductCollection index, int major, InstallComponent component) { // Get products matching the major version var matchingProducts = GetProductsForMajorVersion(index, major); @@ -289,7 +284,7 @@ private List GetProductsForMajorVersion(ProductCollection index, int ma } // Find the latest version based on mode - if (mode == InstallMode.SDK) + if (component == InstallComponent.SDK) { return GetLatestSdkVersion(allReleases, major); } @@ -304,9 +299,9 @@ private List GetProductsForMajorVersion(ProductCollection index, int ma /// /// The product collection to search /// True for LTS (Long-Term Support), false for STS (Standard-Term Support) - /// InstallMode.SDK or InstallMode.Runtime + /// InstallComponent.SDK or InstallComponent.Runtime /// Latest stable version string matching the support status, or null if none found - private string? GetLatestVersionBySupportStatus(ProductCollection index, bool isLts, InstallMode mode) + private ReleaseVersion? GetLatestVersionBySupportStatus(ProductCollection index, bool isLts, InstallComponent component) { // Get all products var allProducts = index.ToList(); @@ -342,7 +337,7 @@ private List GetProductsForMajorVersion(ProductCollection index, int ma } // Find latest version based on mode - if (mode == InstallMode.SDK) + if (component == InstallComponent.SDK) { var sdks = stableReleases .SelectMany(r => r.Sdks) @@ -352,7 +347,7 @@ private List GetProductsForMajorVersion(ProductCollection index, int ma if (sdks.Any()) { - return sdks.First().Version.ToString(); + return sdks.First().Version; } } else // Runtime mode @@ -365,7 +360,7 @@ private List GetProductsForMajorVersion(ProductCollection index, int ma if (runtimes.Any()) { - return runtimes.First().Version.ToString(); + return runtimes.First().Version; } } } @@ -377,9 +372,9 @@ private List GetProductsForMajorVersion(ProductCollection index, int ma /// Gets the latest preview version available. /// /// The product collection to search - /// InstallMode.SDK or InstallMode.Runtime + /// InstallComponent.SDK or InstallComponent.Runtime /// Latest preview version string, or null if none found - private string? GetLatestPreviewVersion(ProductCollection index, InstallMode mode) + private ReleaseVersion? GetLatestPreviewVersion(ProductCollection index, InstallComponent component) { // Get all products var allProducts = index.ToList(); @@ -413,7 +408,7 @@ private List GetProductsForMajorVersion(ProductCollection index, int ma } // Find latest version based on mode - if (mode == InstallMode.SDK) + if (component == InstallComponent.SDK) { var sdks = previewReleases .SelectMany(r => r.Sdks) @@ -423,7 +418,7 @@ private List GetProductsForMajorVersion(ProductCollection index, int ma if (sdks.Any()) { - return sdks.First().Version.ToString(); + return sdks.First().Version; } } else // Runtime mode @@ -436,7 +431,7 @@ private List GetProductsForMajorVersion(ProductCollection index, int ma if (runtimes.Any()) { - return runtimes.First().Version.ToString(); + return runtimes.First().Version; } } } @@ -445,7 +440,7 @@ private List GetProductsForMajorVersion(ProductCollection index, int ma } /// /// Gets the latest version for a major.minor channel (e.g., "9.0"). /// - private string? GetLatestVersionForMajorMinor(ProductCollection index, int major, int minor, InstallMode mode) + private ReleaseVersion? GetLatestVersionForMajorMinor(ProductCollection index, int major, int minor, InstallComponent component) { // Find the product for the requested major.minor string channelKey = $"{major}.{minor}"; @@ -460,7 +455,7 @@ private List GetProductsForMajorVersion(ProductCollection index, int ma var releases = product.GetReleasesAsync().GetAwaiter().GetResult(); // Find the latest version based on mode - if (mode == InstallMode.SDK) + if (component == InstallComponent.SDK) { return GetLatestSdkVersion(releases, major, minor); } @@ -473,7 +468,7 @@ private List GetProductsForMajorVersion(ProductCollection index, int ma /// /// Gets the latest version for a feature band channel (e.g., "9.0.1xx"). /// - private string? GetLatestVersionForFeatureBand(ProductCollection index, int major, int minor, string featureBand, InstallMode mode) + private ReleaseVersion? GetLatestVersionForFeatureBand(ProductCollection index, int major, int minor, string featureBand, InstallComponent component) { // Find the product for the requested major.minor string channelKey = $"{major}.{minor}"; @@ -488,7 +483,7 @@ private List GetProductsForMajorVersion(ProductCollection index, int ma var releases = product.GetReleasesAsync().GetAwaiter().GetResult(); // For SDK mode, use feature band filtering - if (mode == InstallMode.SDK) + if (component == InstallComponent.SDK) { return GetLatestFeatureBandVersion(releases, major, minor, featureBand); } @@ -571,9 +566,9 @@ private static string GetDefaultCacheDirectory() /// /// The .NET installation details /// The download URL for the installer/archive, or null if not found - public string? GetDownloadUrl(DotnetInstall install) + public string? GetDownloadUrl(DotnetInstallRequest installRequest, ReleaseVersion resolvedVersion) { - var targetFile = FindReleaseFile(install); + var targetFile = FindReleaseFile(installRequest, resolvedVersion); return targetFile?.Address.ToString(); } @@ -714,16 +709,16 @@ public bool DownloadArchive(string downloadUrl, string destinationPath, IProgres /// The local path to save the downloaded file /// Optional progress reporting /// True if download and verification were successful, false otherwise - public bool DownloadArchiveWithVerification(DotnetInstall install, string destinationPath, IProgress? progress = null) + public bool DownloadArchiveWithVerification(DotnetInstallRequest installRequest, ReleaseVersion resolvedVersion, string destinationPath, IProgress? progress = null) { // Get the download URL and expected hash - string? downloadUrl = GetDownloadUrl(install); + string? downloadUrl = GetDownloadUrl(installRequest, resolvedVersion); if (string.IsNullOrEmpty(downloadUrl)) { return false; } - string? expectedHash = GetArchiveHash(install); + string? expectedHash = GetArchiveHash(installRequest, resolvedVersion); if (string.IsNullOrEmpty(expectedHash)) { return false; @@ -742,18 +737,18 @@ public bool DownloadArchiveWithVerification(DotnetInstall install, string destin /// /// The .NET installation details /// The matching ReleaseFile, throws if none are available. - private ReleaseFile? FindReleaseFile(DotnetInstall install) + private ReleaseFile? FindReleaseFile(DotnetInstallRequest installRequest, ReleaseVersion resolvedVersion) { try { var productCollection = GetProductCollection(); - var product = FindProduct(productCollection, install.FullySpecifiedVersion.Value) ?? throw new InvalidOperationException($"No product found for version {install.FullySpecifiedVersion.MajorMinor}"); - var release = FindRelease(product, install.FullySpecifiedVersion.Value, install.Mode) ?? throw new InvalidOperationException($"No release found for version {install.FullySpecifiedVersion.Value}"); - return FindMatchingFile(release, install); + var product = FindProduct(productCollection, resolvedVersion) ?? throw new InvalidOperationException($"No product found for version {resolvedVersion}"); + var release = FindRelease(product, resolvedVersion, installRequest.Component) ?? throw new InvalidOperationException($"No release found for version {resolvedVersion}"); + return FindMatchingFile(release, installRequest, resolvedVersion); } catch (Exception ex) { - throw new InvalidOperationException($"Failed to find an available release for install {install} : ${ex.Message}"); + throw new InvalidOperationException($"Failed to find an available release for install {installRequest} : ${ex.Message}", ex); } } @@ -820,9 +815,8 @@ private static ProductCollection DeserializeProductCollection(string json) /// /// Finds the product for the given version. /// - private static Product? FindProduct(ProductCollection productCollection, string version) + private static Product? FindProduct(ProductCollection productCollection, ReleaseVersion releaseVersion) { - var releaseVersion = new ReleaseVersion(version); var majorMinor = $"{releaseVersion.Major}.{releaseVersion.Minor}"; return productCollection.FirstOrDefault(p => p.ProductVersion == majorMinor); } @@ -830,16 +824,15 @@ private static ProductCollection DeserializeProductCollection(string json) /// /// Finds the specific release for the given version. /// - private static ProductRelease? FindRelease(Product product, string version, InstallMode mode) + private static ProductRelease? FindRelease(Product product, ReleaseVersion resolvedVersion, InstallComponent component) { var releases = product.GetReleasesAsync().GetAwaiter().GetResult(); - var targetReleaseVersion = new ReleaseVersion(version); // Get all releases var allReleases = releases.ToList(); // First try to find the exact version in the original release list - var exactReleaseMatch = allReleases.FirstOrDefault(r => r.Version.Equals(targetReleaseVersion)); + var exactReleaseMatch = allReleases.FirstOrDefault(r => r.Version.Equals(resolvedVersion)); if (exactReleaseMatch != null) { return exactReleaseMatch; @@ -851,41 +844,30 @@ private static ProductCollection DeserializeProductCollection(string json) bool foundMatch = false; // Check the appropriate collection based on the mode - if (mode == InstallMode.SDK) + if (component == InstallComponent.SDK) { foreach (var sdk in release.Sdks) { // Check for exact match - if (sdk.Version.Equals(targetReleaseVersion)) + if (sdk.Version.Equals(resolvedVersion)) { foundMatch = true; break; } - // Check for match on major, minor, patch - if (sdk.Version.Major == targetReleaseVersion.Major && - sdk.Version.Minor == targetReleaseVersion.Minor && - sdk.Version.Patch == targetReleaseVersion.Patch) - { - foundMatch = true; - break; - } + // Not sure what the point of the below logic was + //// Check for match on major, minor, patch + //if (sdk.Version.Major == targetReleaseVersion.Major && + // sdk.Version.Minor == targetReleaseVersion.Minor && + // sdk.Version.Patch == targetReleaseVersion.Patch) + //{ + // foundMatch = true; + // break; + //} } } else // Runtime mode { - // Filter by runtime type based on file names in the release - var runtimeTypeMatches = release.Files.Any(f => - f.Name.Contains("runtime", StringComparison.OrdinalIgnoreCase) && - !f.Name.Contains("aspnetcore", StringComparison.OrdinalIgnoreCase) && - !f.Name.Contains("windowsdesktop", StringComparison.OrdinalIgnoreCase)); - - var aspnetCoreMatches = release.Files.Any(f => - f.Name.Contains("aspnetcore", StringComparison.OrdinalIgnoreCase)); - - var windowsDesktopMatches = release.Files.Any(f => - f.Name.Contains("windowsdesktop", StringComparison.OrdinalIgnoreCase)); - // Get the appropriate runtime components based on the file patterns var filteredRuntimes = release.Runtimes; @@ -895,20 +877,20 @@ private static ProductCollection DeserializeProductCollection(string json) foreach (var runtime in filteredRuntimes) { // Check for exact match - if (runtime.Version.Equals(targetReleaseVersion)) + if (runtime.Version.Equals(resolvedVersion)) { foundMatch = true; break; } - // Check for match on major, minor, patch - if (runtime.Version.Major == targetReleaseVersion.Major && - runtime.Version.Minor == targetReleaseVersion.Minor && - runtime.Version.Patch == targetReleaseVersion.Patch) - { - foundMatch = true; - break; - } + //// Check for match on major, minor, patch + //if (runtime.Version.Major == targetReleaseVersion.Major && + // runtime.Version.Minor == targetReleaseVersion.Minor && + // runtime.Version.Patch == targetReleaseVersion.Patch) + //{ + // foundMatch = true; + // break; + //} } } @@ -924,14 +906,14 @@ private static ProductCollection DeserializeProductCollection(string json) /// /// Finds the matching file in the release for the given installation requirements. /// - private static ReleaseFile? FindMatchingFile(ProductRelease release, DotnetInstall install) + private static ReleaseFile? FindMatchingFile(ProductRelease release, DotnetInstallRequest installRequest, ReleaseVersion resolvedVersion) { - var rid = DnupUtilities.GetRuntimeIdentifier(install.Architecture); + var rid = DnupUtilities.GetRuntimeIdentifier(installRequest.InstallRoot.Architecture); var fileExtension = DnupUtilities.GetArchiveFileExtensionForPlatform(); // Determine the component type pattern to look for in file names string componentTypePattern; - if (install.Mode == InstallMode.SDK) + if (installRequest.Component == InstallComponent.SDK) { componentTypePattern = "sdk"; } @@ -942,12 +924,12 @@ private static ProductCollection DeserializeProductCollection(string json) componentTypePattern = "runtime"; // Check if this is specifically an ASP.NET Core runtime - if (install.FullySpecifiedVersion.Value.Contains("aspnetcore")) + if (installRequest.Component == InstallComponent.ASPNETCore) { componentTypePattern = "aspnetcore"; } // Check if this is specifically a Windows Desktop runtime - else if (install.FullySpecifiedVersion.Value.Contains("windowsdesktop")) + else if (installRequest.Component == InstallComponent.WindowsDesktop) { componentTypePattern = "windowsdesktop"; } @@ -966,7 +948,7 @@ private static ProductCollection DeserializeProductCollection(string json) } // If we have multiple matching files, prefer the one with the full version in the name - var versionString = install.FullySpecifiedVersion.Value; + var versionString = resolvedVersion.ToString(); var bestMatch = matchingFiles.FirstOrDefault(f => f.Name.Contains(versionString, StringComparison.OrdinalIgnoreCase)); // If no file has the exact version string, return the first match @@ -978,9 +960,9 @@ private static ProductCollection DeserializeProductCollection(string json) /// /// The .NET installation details /// The SHA512 hash string of the installer/archive, or null if not found - public string? GetArchiveHash(DotnetInstall install) + public string? GetArchiveHash(DotnetInstallRequest installRequest, ReleaseVersion resolvedVersion) { - var targetFile = FindReleaseFile(install); + var targetFile = FindReleaseFile(installRequest, resolvedVersion); return targetFile?.Hash; } diff --git a/src/Installer/dnup/UpdateChannel.cs b/src/Installer/dnup/UpdateChannel.cs new file mode 100644 index 000000000000..fc25144ed5b0 --- /dev/null +++ b/src/Installer/dnup/UpdateChannel.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.Deployment.DotNet.Releases; + +namespace Microsoft.DotNet.Tools.Bootstrapper +{ + public class UpdateChannel + { + public string Name { get; set; } + + public UpdateChannel(string name) + { + Name = name; + } + + public bool IsFullySpecifiedVersion() + { + return ReleaseVersion.TryParse(Name, out _); + } + + } +} diff --git a/test/dnup.Tests/DotnetInstallTests.cs b/test/dnup.Tests/DotnetInstallTests.cs deleted file mode 100644 index b0c497b8e9a1..000000000000 --- a/test/dnup.Tests/DotnetInstallTests.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.DotNet.Tools.Bootstrapper; - -namespace Microsoft.DotNet.Tools.Dnup.Tests; - -public class DotnetInstallTests -{ - [Fact] - public void DotnetInstallBase_ShouldInitializeCorrectly() - { - var directory = "/test/directory"; - var type = InstallType.User; - var mode = InstallMode.SDK; - var architecture = InstallArchitecture.x64; - - var install = new DotnetInstallBase(directory, type, mode, architecture); - - install.ResolvedDirectory.Should().Be(directory); - install.Type.Should().Be(type); - install.Mode.Should().Be(mode); - install.Architecture.Should().Be(architecture); - install.Id.Should().NotBe(Guid.Empty); - } - - [Fact] - public void DotnetInstall_ShouldInheritFromBase() - { - var version = "8.0.301"; - var directory = "/test/directory"; - var type = InstallType.User; - var mode = InstallMode.SDK; - var architecture = InstallArchitecture.x64; - - var install = new DotnetInstall(new DotnetVersion(version), directory, type, mode, architecture, new ManagementCadence()); - - install.FullySpecifiedVersion.Value.Should().Be(version); - install.ResolvedDirectory.Should().Be(directory); - install.Type.Should().Be(type); - install.Mode.Should().Be(mode); - install.Architecture.Should().Be(architecture); - install.Id.Should().NotBe(Guid.Empty); - } - - [Fact] - public void MultipleInstances_ShouldHaveUniqueIds() - { - // Arrange & Act - var install1 = new DotnetInstallBase("dir1", InstallType.User, InstallMode.SDK, InstallArchitecture.x64); - var install2 = new DotnetInstallBase("dir2", InstallType.Admin, InstallMode.Runtime, InstallArchitecture.x64); - - // Assert - install1.Id.Should().NotBe(install2.Id); - } - - [Fact] - public void Records_ShouldSupportValueEquality() - { - // Arrange - var install1 = new DotnetInstall("8.0.301", "/test", InstallType.User, InstallMode.SDK, InstallArchitecture.x64, new ManagementCadence()); - var install2 = new DotnetInstall("8.0.301", "/test", InstallType.User, InstallMode.SDK, InstallArchitecture.x64, new ManagementCadence()); - - // Act & Assert - // Records should be equal based on values, except for the Id which is always unique - install1.FullySpecifiedVersion.Should().Be(install2.FullySpecifiedVersion); - install1.ResolvedDirectory.Should().Be(install2.ResolvedDirectory); - install1.Type.Should().Be(install2.Type); - install1.Mode.Should().Be(install2.Mode); - install1.Architecture.Should().Be(install2.Architecture); - - // But Ids should be different - install1.Id.Should().NotBe(install2.Id); - } -}