diff --git a/.github/workflows/update-dotnet-install-scripts.yml b/.github/workflows/update-dotnet-install-scripts.yml deleted file mode 100644 index 79f22ac3a47..00000000000 --- a/.github/workflows/update-dotnet-install-scripts.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Update dotnet-install Scripts - -on: - workflow_dispatch: - schedule: - - cron: '0 6 * * *' # Daily at 06:00 UTC - -permissions: - contents: write - pull-requests: write - -jobs: - update-scripts: - runs-on: ubuntu-latest - if: ${{ github.repository_owner == 'dotnet' }} - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - - name: Download dotnet-install.sh - run: | - curl -sSL -o src/Aspire.Cli/Resources/dotnet-install.sh https://dot.net/v1/dotnet-install.sh - - - name: Download dotnet-install.ps1 - run: | - curl -sSL -o src/Aspire.Cli/Resources/dotnet-install.ps1 https://dot.net/v1/dotnet-install.ps1 - - - name: Create or update pull request - uses: dotnet/actions-create-pull-request@e8d799aa1f8b17f324f9513832811b0a62f1e0b1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - branch: update-dotnet-install-scripts - base: main - commit-message: "[Automated] Update dotnet-install scripts" - labels: | - area-cli - area-engineering-systems - title: "[Automated] Update dotnet-install scripts" - body: "Auto-generated update of embedded dotnet-install.sh and dotnet-install.ps1 scripts from https://dot.net/v1/." diff --git a/extension/schemas/aspire-global-settings.schema.json b/extension/schemas/aspire-global-settings.schema.json index 7dfa58264b6..606b7ebd700 100644 --- a/extension/schemas/aspire-global-settings.schema.json +++ b/extension/schemas/aspire-global-settings.schema.json @@ -29,22 +29,6 @@ "description": "Enable or disable watch mode by default when running Aspire applications for automatic restarts on file changes", "default": false }, - "dotnetSdkInstallationEnabled": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "string", - "enum": [ - "true", - "false" - ] - } - ], - "description": "Enable or disable automatic .NET SDK installation when a required SDK version is missing", - "default": true - }, "execCommandEnabled": { "anyOf": [ { diff --git a/extension/schemas/aspire-settings.schema.json b/extension/schemas/aspire-settings.schema.json index c8cc5a7fc17..90cfd3abc95 100644 --- a/extension/schemas/aspire-settings.schema.json +++ b/extension/schemas/aspire-settings.schema.json @@ -33,22 +33,6 @@ "description": "Enable or disable watch mode by default when running Aspire applications for automatic restarts on file changes", "default": false }, - "dotnetSdkInstallationEnabled": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "string", - "enum": [ - "true", - "false" - ] - } - ], - "description": "Enable or disable automatic .NET SDK installation when a required SDK version is missing", - "default": true - }, "execCommandEnabled": { "anyOf": [ { diff --git a/src/Aspire.Cli/Aspire.Cli.csproj b/src/Aspire.Cli/Aspire.Cli.csproj index 91174cefb73..6944c894d90 100644 --- a/src/Aspire.Cli/Aspire.Cli.csproj +++ b/src/Aspire.Cli/Aspire.Cli.csproj @@ -186,8 +186,6 @@ - - diff --git a/src/Aspire.Cli/Commands/AddCommand.cs b/src/Aspire.Cli/Commands/AddCommand.cs index a936e494864..ef7bb20a7bb 100644 --- a/src/Aspire.Cli/Commands/AddCommand.cs +++ b/src/Aspire.Cli/Commands/AddCommand.cs @@ -86,7 +86,7 @@ protected override async Task ExecuteAsync(ParseResult parseResult, Cancell // Check if the .NET SDK is available (only needed for .NET projects) if (project.LanguageId == KnownLanguageId.CSharp) { - if (!await SdkInstallHelper.EnsureSdkInstalledAsync(_sdkInstaller, InteractionService, _features, Telemetry, _hostEnvironment, cancellationToken)) + if (!await SdkInstallHelper.EnsureSdkInstalledAsync(_sdkInstaller, InteractionService, Telemetry, cancellationToken)) { return ExitCodeConstants.SdkNotInstalled; } diff --git a/src/Aspire.Cli/Commands/ExecCommand.cs b/src/Aspire.Cli/Commands/ExecCommand.cs index b8fd7f68a74..abf14c52d3f 100644 --- a/src/Aspire.Cli/Commands/ExecCommand.cs +++ b/src/Aspire.Cli/Commands/ExecCommand.cs @@ -24,8 +24,6 @@ internal class ExecCommand : BaseCommand private readonly IProjectLocator _projectLocator; private readonly IAnsiConsole _ansiConsole; private readonly IDotNetSdkInstaller _sdkInstaller; - private readonly ICliHostEnvironment _hostEnvironment; - private readonly IFeatures _features; private static readonly OptionWithLegacy s_appHostOption = new("--apphost", "--project", ExecCommandStrings.ProjectArgumentDescription); private static readonly Option s_resourceOption = new("--resource", "-r") @@ -55,7 +53,7 @@ public ExecCommand( IDotNetSdkInstaller sdkInstaller, IFeatures features, ICliUpdateNotifier updateNotifier, - CliExecutionContext executionContext, ICliHostEnvironment hostEnvironment) + CliExecutionContext executionContext) : base("exec", ExecCommandStrings.Description, features, updateNotifier, executionContext, interactionService, telemetry) { _runner = runner; @@ -63,8 +61,6 @@ public ExecCommand( _projectLocator = projectLocator; _ansiConsole = ansiConsole; _sdkInstaller = sdkInstaller; - _hostEnvironment = hostEnvironment; - _features = features; Options.Add(s_appHostOption); Options.Add(s_resourceOption); @@ -79,7 +75,7 @@ public ExecCommand( protected override async Task ExecuteAsync(ParseResult parseResult, CancellationToken cancellationToken) { // Check if the .NET SDK is available - if (!await SdkInstallHelper.EnsureSdkInstalledAsync(_sdkInstaller, InteractionService, _features, Telemetry, _hostEnvironment, cancellationToken)) + if (!await SdkInstallHelper.EnsureSdkInstalledAsync(_sdkInstaller, InteractionService, Telemetry, cancellationToken)) { return ExitCodeConstants.SdkNotInstalled; } diff --git a/src/Aspire.Cli/Commands/InitCommand.cs b/src/Aspire.Cli/Commands/InitCommand.cs index 46323a1419a..709ea77aca5 100644 --- a/src/Aspire.Cli/Commands/InitCommand.cs +++ b/src/Aspire.Cli/Commands/InitCommand.cs @@ -33,8 +33,6 @@ internal sealed class InitCommand : BaseCommand, IPackageMetaPrefetchingCommand private readonly IPackagingService _packagingService; private readonly ISolutionLocator _solutionLocator; private readonly IDotNetSdkInstaller _sdkInstaller; - private readonly ICliHostEnvironment _hostEnvironment; - private readonly IFeatures _features; private readonly ICliUpdateNotifier _updateNotifier; private readonly CliExecutionContext _executionContext; private readonly IConfigurationService _configurationService; @@ -78,7 +76,6 @@ public InitCommand( IFeatures features, ICliUpdateNotifier updateNotifier, CliExecutionContext executionContext, - ICliHostEnvironment hostEnvironment, IInteractionService interactionService, IConfigurationService configurationService, ILanguageService languageService, @@ -93,8 +90,6 @@ public InitCommand( _packagingService = packagingService; _solutionLocator = solutionLocator; _sdkInstaller = sdkInstaller; - _hostEnvironment = hostEnvironment; - _features = features; _updateNotifier = updateNotifier; _executionContext = executionContext; _configurationService = configurationService; @@ -149,7 +144,7 @@ protected override async Task ExecuteAsync(ParseResult parseResult, Cancell } // For C#, we need the .NET SDK - if (!await SdkInstallHelper.EnsureSdkInstalledAsync(_sdkInstaller, InteractionService, _features, Telemetry, _hostEnvironment, cancellationToken)) + if (!await SdkInstallHelper.EnsureSdkInstalledAsync(_sdkInstaller, InteractionService, Telemetry, cancellationToken)) { return ExitCodeConstants.SdkNotInstalled; } diff --git a/src/Aspire.Cli/DotNet/DotNetCliRunner.cs b/src/Aspire.Cli/DotNet/DotNetCliRunner.cs index 627f2d5e745..9514676f717 100644 --- a/src/Aspire.Cli/DotNet/DotNetCliRunner.cs +++ b/src/Aspire.Cli/DotNet/DotNetCliRunner.cs @@ -441,15 +441,6 @@ public async Task RunAsync(FileInfo projectFile, bool watch, bool noBuild, } } - if (features.IsFeatureEnabled(KnownFeatures.DotNetSdkInstallationEnabled, true)) - { - // Only set the environment variable if it's not already set by the user - if (!finalEnv.ContainsKey("DOTNET_ROLL_FORWARD")) - { - finalEnv["DOTNET_ROLL_FORWARD"] = "LatestMajor"; - } - } - // Set the backchannel socket path when backchannel is configured if (backchannelCompletionSource is not null) { diff --git a/src/Aspire.Cli/DotNet/DotNetSdkInstaller.cs b/src/Aspire.Cli/DotNet/DotNetSdkInstaller.cs index c5335608d7b..36b20947242 100644 --- a/src/Aspire.Cli/DotNet/DotNetSdkInstaller.cs +++ b/src/Aspire.Cli/DotNet/DotNetSdkInstaller.cs @@ -2,11 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; -using System.Reflection; using System.Runtime.InteropServices; using Aspire.Cli.Configuration; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; using Semver; namespace Aspire.Cli.DotNet; @@ -14,7 +12,7 @@ namespace Aspire.Cli.DotNet; /// /// Default implementation of that checks for dotnet on the system PATH. /// -internal sealed class DotNetSdkInstaller(IFeatures features, IConfiguration configuration, CliExecutionContext executionContext, IDotNetCliRunner dotNetCliRunner, ILogger logger) : IDotNetSdkInstaller +internal sealed class DotNetSdkInstaller(IFeatures features, IConfiguration configuration) : IDotNetSdkInstaller { /// /// The minimum .NET SDK version required for Aspire. @@ -22,36 +20,14 @@ internal sealed class DotNetSdkInstaller(IFeatures features, IConfiguration conf public const string MinimumSdkVersion = "10.0.100"; /// - public async Task<(bool Success, string? HighestDetectedVersion, string MinimumRequiredVersion, bool ForceInstall)> CheckAsync(CancellationToken cancellationToken = default) + public async Task<(bool Success, string? HighestDetectedVersion, string MinimumRequiredVersion)> CheckAsync(CancellationToken cancellationToken = default) { var minimumVersion = GetEffectiveMinimumSdkVersion(configuration); - // Check if alwaysInstallSdk is enabled - this forces installation even when SDK check passes - var alwaysInstallSdk = configuration["alwaysInstallSdk"]; - var forceInstall = !string.IsNullOrEmpty(alwaysInstallSdk) && - bool.TryParse(alwaysInstallSdk, out var alwaysInstall) && - alwaysInstall; - - // First check if we already have the SDK installed in our private sdks directory - if (!forceInstall) - { - var sdksDirectory = GetSdksDirectory(); - var sdkInstallPath = Path.Combine(sdksDirectory, "dotnet", minimumVersion); - var dotnetExecutable = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - ? Path.Combine(sdkInstallPath, "dotnet.exe") - : Path.Combine(sdkInstallPath, "dotnet"); - - if (File.Exists(dotnetExecutable)) - { - logger.LogDebug("Found private SDK installation at {Path}", sdkInstallPath); - return (true, minimumVersion, minimumVersion, false); - } - } - if (!features.IsFeatureEnabled(KnownFeatures.MinimumSdkCheckEnabled, true)) { // If the feature is disabled, we assume the SDK is available - return (true, null, minimumVersion, forceInstall); + return (true, null, minimumVersion); } try @@ -79,13 +55,13 @@ internal sealed class DotNetSdkInstaller(IFeatures features, IConfiguration conf if (process.ExitCode != 0) { - return (false, null, minimumVersion, forceInstall); + return (false, null, minimumVersion); } // Parse the minimum version requirement if (!SemVersion.TryParse(minimumVersion, SemVersionStyles.Strict, out var minVersion)) { - return (false, null, minimumVersion, forceInstall); + return (false, null, minimumVersion); } // Parse each line of the output to find SDK versions @@ -117,154 +93,12 @@ internal sealed class DotNetSdkInstaller(IFeatures features, IConfiguration conf } } - return (meetsMinimum, highestDetectedVersion?.ToString(), minimumVersion, forceInstall); + return (meetsMinimum, highestDetectedVersion?.ToString(), minimumVersion); } catch (Exception ex) when (ex is not OperationCanceledException) // If cancellation is requested let that bubble up. { // If we can't start the process, the SDK is not available - return (false, null, minimumVersion, forceInstall); - } - } - - /// - public async Task InstallAsync(CancellationToken cancellationToken = default) - { - var sdkVersion = GetEffectiveMinimumSdkVersion(configuration); - var sdksDirectory = GetSdksDirectory(); - var sdkInstallPath = Path.Combine(sdksDirectory, "dotnet", sdkVersion); - - // Check if SDK is already installed in the private location - if (Directory.Exists(sdkInstallPath)) - { - // SDK already installed, nothing to do - return; - } - - // Create the sdks directory if it doesn't exist - Directory.CreateDirectory(sdksDirectory); - - // Determine which install script to use based on the platform - var (resourceName, scriptFileName, scriptRunner) = GetInstallScriptInfo(); - - // Extract the install script from embedded resources - var scriptPath = Path.Combine(sdksDirectory, scriptFileName); - var assembly = Assembly.GetExecutingAssembly(); - using var resourceStream = assembly.GetManifestResourceStream(resourceName); - if (resourceStream == null) - { - throw new InvalidOperationException($"Could not find embedded resource: {resourceName}"); - } - - using var fileStream = File.Create(scriptPath); - await resourceStream.CopyToAsync(fileStream, cancellationToken); - - // Make the script executable on Unix-like systems - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - // Set execute permission on Unix systems - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - try - { - var mode = File.GetUnixFileMode(scriptPath); - mode |= UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute; - File.SetUnixFileMode(scriptPath, mode); - } - catch (Exception ex) - { - logger.LogWarning(ex, "Failed to set executable permission on {ScriptPath}", scriptPath); - } - } - } - - // Run the install script - var installProcess = new Process - { - StartInfo = new ProcessStartInfo - { - FileName = scriptRunner, - UseShellExecute = false, - CreateNoWindow = true, - RedirectStandardOutput = true, - RedirectStandardError = true - } - }; - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - // PowerShell script arguments - installProcess.StartInfo.Arguments = $"-ExecutionPolicy Bypass -File \"{scriptPath}\" -Version {sdkVersion} -InstallDir \"{sdkInstallPath}\" -NoPath"; - } - else - { - // Bash script arguments - installProcess.StartInfo.Arguments = $"\"{scriptPath}\" --version {sdkVersion} --install-dir \"{sdkInstallPath}\" --no-path"; - } - - installProcess.Start(); - - // Capture and log stdout and stderr - var stdoutTask = Task.Run(async () => - { - string? line; - while ((line = await installProcess.StandardOutput.ReadLineAsync(cancellationToken).ConfigureAwait(false)) is not null) - { - logger.LogDebug("dotnet-install stdout: {Line}", line); - } - }, cancellationToken); - - var stderrTask = Task.Run(async () => - { - string? line; - while ((line = await installProcess.StandardError.ReadLineAsync(cancellationToken).ConfigureAwait(false)) is not null) - { - logger.LogDebug("dotnet-install stderr: {Line}", line); - } - }, cancellationToken); - - await installProcess.WaitForExitAsync(cancellationToken); - - // Wait for output capture to complete - await Task.WhenAll(stdoutTask, stderrTask); - - if (installProcess.ExitCode != 0) - { - throw new InvalidOperationException($"Failed to install .NET SDK {sdkVersion}. Exit code: {installProcess.ExitCode}"); - } - - // Clean up the install script - try - { - File.Delete(scriptPath); - } - catch - { - // Ignore cleanup errors - } - - // After installation, call dotnet nuget config paths to initialize NuGet - // This is important on Windows where NuGet needs to create initial config on first use - logger.LogDebug("Initializing NuGet configuration for private SDK installation"); - try - { - var options = new DotNetCliRunnerInvocationOptions(); - var (exitCode, _) = await dotNetCliRunner.GetNuGetConfigPathsAsync( - new DirectoryInfo(Environment.CurrentDirectory), - options, - cancellationToken); - - if (exitCode == 0) - { - logger.LogDebug("NuGet configuration initialized successfully"); - } - else - { - logger.LogDebug("NuGet configuration initialization returned exit code {ExitCode}", exitCode); - } - } - catch (Exception ex) - { - logger.LogDebug(ex, "Failed to initialize NuGet configuration, continuing anyway"); + return (false, null, minimumVersion); } } @@ -284,99 +118,6 @@ private static string GetCurrentArchitecture() }; } - /// - /// Gets the directory where .NET SDKs are stored. - /// - /// The full path to the sdks directory. - private string GetSdksDirectory() - { - return executionContext.SdksDirectory.FullName; - } - - /// - /// Gets the install script information based on the current platform. - /// - /// A tuple containing the embedded resource name, script file name, and script runner command. - private static (string ResourceName, string ScriptFileName, string ScriptRunner) GetInstallScriptInfo() - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - // Try pwsh first (PowerShell Core), then fall back to powershell (Windows PowerShell) - var powerShellExecutable = GetAvailablePowerShell(); - return ( - "Aspire.Cli.Resources.dotnet-install.ps1", - "dotnet-install.ps1", - powerShellExecutable - ); - } - else - { - return ( - "Aspire.Cli.Resources.dotnet-install.sh", - "dotnet-install.sh", - "bash" - ); - } - } - - /// - /// Determines which PowerShell executable is available on the system. - /// Tries pwsh (PowerShell Core) first, then falls back to powershell (Windows PowerShell). - /// - /// The name of the available PowerShell executable. - private static string GetAvailablePowerShell() - { - // Try pwsh first (PowerShell Core - cross-platform) - if (IsPowerShellAvailable("pwsh")) - { - return "pwsh"; - } - - // Fall back to powershell (Windows PowerShell) - if (IsPowerShellAvailable("powershell")) - { - return "powershell"; - } - - // Default to powershell if neither can be verified - // The installation will fail later with a clear error if it's not available - return "powershell"; - } - - /// - /// Checks if a PowerShell executable is available by running it with --version. - /// - /// The PowerShell executable name to check (pwsh or powershell). - /// True if the executable is available and responds to --version. - private static bool IsPowerShellAvailable(string executable) - { - try - { - var process = new Process - { - StartInfo = new ProcessStartInfo - { - FileName = executable, - Arguments = "--version", - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - CreateNoWindow = true - } - }; - - process.Start(); - process.WaitForExit(1000); // Wait up to 1 second - - return process.ExitCode == 0; - } - catch - { - // If the executable doesn't exist or can't be started, return false - return false; - } - } - /// /// Gets the effective minimum SDK version based on configuration. /// diff --git a/src/Aspire.Cli/DotNet/IDotNetSdkInstaller.cs b/src/Aspire.Cli/DotNet/IDotNetSdkInstaller.cs index 92368117137..d25b57c3a20 100644 --- a/src/Aspire.Cli/DotNet/IDotNetSdkInstaller.cs +++ b/src/Aspire.Cli/DotNet/IDotNetSdkInstaller.cs @@ -4,7 +4,7 @@ namespace Aspire.Cli.DotNet; /// -/// Service responsible for checking and installing the .NET SDK. +/// Service responsible for checking .NET SDK availability. /// internal interface IDotNetSdkInstaller { @@ -12,14 +12,6 @@ internal interface IDotNetSdkInstaller /// Checks if the .NET SDK is available on the system PATH. /// /// Cancellation token. - /// A tuple containing: success flag, highest detected version, minimum required version, and whether to force installation. - Task<(bool Success, string? HighestDetectedVersion, string MinimumRequiredVersion, bool ForceInstall)> CheckAsync(CancellationToken cancellationToken = default); - - /// - /// Installs the .NET SDK. This method is reserved for future extensibility. - /// - /// Cancellation token. - /// A task representing the asynchronous operation. - Task InstallAsync(CancellationToken cancellationToken = default); - + /// A tuple containing: success flag, highest detected version, and minimum required version. + Task<(bool Success, string? HighestDetectedVersion, string MinimumRequiredVersion)> CheckAsync(CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/Aspire.Cli/KnownFeatures.cs b/src/Aspire.Cli/KnownFeatures.cs index fe9932cec0f..0531317439b 100644 --- a/src/Aspire.Cli/KnownFeatures.cs +++ b/src/Aspire.Cli/KnownFeatures.cs @@ -28,7 +28,6 @@ internal static class KnownFeatures public static string ExperimentalPolyglotJava => "experimentalPolyglot:java"; public static string ExperimentalPolyglotGo => "experimentalPolyglot:go"; public static string ExperimentalPolyglotPython => "experimentalPolyglot:python"; - public static string DotNetSdkInstallationEnabled => "dotnetSdkInstallationEnabled"; public static string RunningInstanceDetectionEnabled => "runningInstanceDetectionEnabled"; private static readonly Dictionary s_featureMetadata = new() @@ -98,11 +97,6 @@ internal static class KnownFeatures "Enable or disable experimental Python language support for polyglot Aspire applications", DefaultValue: false), - [DotNetSdkInstallationEnabled] = new( - DotNetSdkInstallationEnabled, - "Enable or disable automatic .NET SDK installation when a required SDK version is missing", - DefaultValue: true), - [RunningInstanceDetectionEnabled] = new( RunningInstanceDetectionEnabled, "Enable or disable detection of already running Aspire instances to prevent conflicts", diff --git a/src/Aspire.Cli/Projects/DotNetAppHostProject.cs b/src/Aspire.Cli/Projects/DotNetAppHostProject.cs index 131f13b6478..1f485315b30 100644 --- a/src/Aspire.Cli/Projects/DotNetAppHostProject.cs +++ b/src/Aspire.Cli/Projects/DotNetAppHostProject.cs @@ -191,7 +191,7 @@ private static bool IsPossiblyUnbuildableAppHost(FileInfo projectFile) public async Task RunAsync(AppHostProjectContext context, CancellationToken cancellationToken) { // .NET projects require the SDK to be installed - if (!await SdkInstallHelper.EnsureSdkInstalledAsync(_sdkInstaller, _interactionService, _features, _telemetry, cancellationToken: cancellationToken)) + if (!await SdkInstallHelper.EnsureSdkInstalledAsync(_sdkInstaller, _interactionService, _telemetry, cancellationToken: cancellationToken)) { // Signal build failure so RunCommand doesn't wait forever context.BuildCompletionSource?.TrySetResult(false); @@ -362,7 +362,7 @@ private static void ConfigureSingleFileEnvironment(FileInfo appHostFile, Diction public async Task PublishAsync(PublishContext context, CancellationToken cancellationToken) { // .NET projects require the SDK to be installed - if (!await SdkInstallHelper.EnsureSdkInstalledAsync(_sdkInstaller, _interactionService, _features, _telemetry, cancellationToken: cancellationToken)) + if (!await SdkInstallHelper.EnsureSdkInstalledAsync(_sdkInstaller, _interactionService, _telemetry, cancellationToken: cancellationToken)) { // Throw an exception that will be caught by the command and result in SdkNotInstalled exit code // This is cleaner than trying to signal through the backchannel pattern diff --git a/src/Aspire.Cli/Resources/dotnet-install.ps1 b/src/Aspire.Cli/Resources/dotnet-install.ps1 deleted file mode 100644 index 8537c601ef2..00000000000 --- a/src/Aspire.Cli/Resources/dotnet-install.ps1 +++ /dev/null @@ -1,1573 +0,0 @@ -# -# Copyright (c) .NET Foundation and contributors. All rights reserved. -# Licensed under the MIT license. See LICENSE file in the project root for full license information. -# - -<# -.SYNOPSIS - Installs dotnet cli -.DESCRIPTION - Installs dotnet cli. If dotnet installation already exists in the given directory - it will update it only if the requested version differs from the one already installed. - - Note that the intended use of this script is for Continuous Integration (CI) scenarios, where: - - The SDK needs to be installed without user interaction and without admin rights. - - The SDK installation doesn't need to persist across multiple CI runs. - To set up a development environment or to run apps, use installers rather than this script. Visit https://dotnet.microsoft.com/download to get the installer. - -.PARAMETER Channel - Default: LTS - Download from the Channel specified. Possible values: - - STS - the most recent Standard Term Support release - - LTS - the most recent Long Term Support release - - 2-part version in a format A.B - represents a specific release - examples: 2.0, 1.0 - - 3-part version in a format A.B.Cxx - represents a specific SDK release - examples: 5.0.1xx, 5.0.2xx - Supported since 5.0 release - Warning: Value "Current" is deprecated for the Channel parameter. Use "STS" instead. - Note: The version parameter overrides the channel parameter when any version other than 'latest' is used. -.PARAMETER Quality - Download the latest build of specified quality in the channel. The possible values are: daily, preview, GA. - Works only in combination with channel. Not applicable for STS and LTS channels and will be ignored if those channels are used. - Supported since 5.0 release. - Note: The version parameter overrides the channel parameter when any version other than 'latest' is used, and therefore overrides the quality. -.PARAMETER Version - Default: latest - Represents a build version on specific channel. Possible values: - - latest - the latest build on specific channel - - 3-part version in a format A.B.C - represents specific version of build - examples: 2.0.0-preview2-006120, 1.1.0 -.PARAMETER Internal - Download internal builds. Requires providing credentials via -FeedCredential parameter. -.PARAMETER FeedCredential - Token to access Azure feed. Used as a query string to append to the Azure feed. - This parameter typically is not specified. -.PARAMETER InstallDir - Default: %LocalAppData%\Microsoft\dotnet - Path to where to install dotnet. Note that binaries will be placed directly in a given directory. -.PARAMETER Architecture - Default: - this value represents currently running OS architecture - Architecture of dotnet binaries to be installed. - Possible values are: , amd64, x64, x86, arm64, arm -.PARAMETER SharedRuntime - This parameter is obsolete and may be removed in a future version of this script. - The recommended alternative is '-Runtime dotnet'. - Installs just the shared runtime bits, not the entire SDK. -.PARAMETER Runtime - Installs just a shared runtime, not the entire SDK. - Possible values: - - dotnet - the Microsoft.NETCore.App shared runtime - - aspnetcore - the Microsoft.AspNetCore.App shared runtime - - windowsdesktop - the Microsoft.WindowsDesktop.App shared runtime -.PARAMETER DryRun - If set it will not perform installation but instead display what command line to use to consistently install - currently requested version of dotnet cli. In example if you specify version 'latest' it will display a link - with specific version so that this command can be used deterministically in a build script. - It also displays binaries location if you prefer to install or download it yourself. -.PARAMETER NoPath - By default this script will set environment variable PATH for the current process to the binaries folder inside installation folder. - If set it will display binaries location but not set any environment variable. -.PARAMETER Verbose - Displays diagnostics information. -.PARAMETER AzureFeed - Default: https://builds.dotnet.microsoft.com/dotnet - For internal use only. - Allows using a different storage to download SDK archives from. -.PARAMETER UncachedFeed - For internal use only. - Allows using a different storage to download SDK archives from. -.PARAMETER ProxyAddress - If set, the installer will use the proxy when making web requests -.PARAMETER ProxyUseDefaultCredentials - Default: false - Use default credentials, when using proxy address. -.PARAMETER ProxyBypassList - If set with ProxyAddress, will provide the list of comma separated urls that will bypass the proxy -.PARAMETER SkipNonVersionedFiles - Default: false - Skips installing non-versioned files if they already exist, such as dotnet.exe. -.PARAMETER JSonFile - Determines the SDK version from a user specified global.json file - Note: global.json must have a value for 'SDK:Version' -.PARAMETER DownloadTimeout - Determines timeout duration in seconds for downloading of the SDK file - Default: 1200 seconds (20 minutes) -.PARAMETER KeepZip - If set, downloaded file is kept -.PARAMETER ZipPath - Use that path to store installer, generated by default -.EXAMPLE - dotnet-install.ps1 -Version 7.0.401 - Installs the .NET SDK version 7.0.401 -.EXAMPLE - dotnet-install.ps1 -Channel 8.0 -Quality GA - Installs the latest GA (general availability) version of the .NET 8.0 SDK -#> -[cmdletbinding()] -param( - [string]$Channel = "LTS", - [string]$Quality, - [string]$Version = "Latest", - [switch]$Internal, - [string]$JSonFile, - [Alias('i')][string]$InstallDir = "", - [string]$Architecture = "", - [string]$Runtime, - [Obsolete("This parameter may be removed in a future version of this script. The recommended alternative is '-Runtime dotnet'.")] - [switch]$SharedRuntime, - [switch]$DryRun, - [switch]$NoPath, - [string]$AzureFeed, - [string]$UncachedFeed, - [string]$FeedCredential, - [string]$ProxyAddress, - [switch]$ProxyUseDefaultCredentials, - [string[]]$ProxyBypassList = @(), - [switch]$SkipNonVersionedFiles, - [int]$DownloadTimeout = 1200, - [switch]$KeepZip, - [string]$ZipPath = [System.IO.Path]::combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()), - [switch]$Help -) - -Set-StrictMode -Version Latest -$ErrorActionPreference = "Stop" -$ProgressPreference = "SilentlyContinue" - -function Say($str) { - try { - Write-Host "dotnet-install: $str" - } - catch { - # Some platforms cannot utilize Write-Host (Azure Functions, for instance). Fall back to Write-Output - Write-Output "dotnet-install: $str" - } -} - -function Say-Warning($str) { - try { - Write-Warning "dotnet-install: $str" - } - catch { - # Some platforms cannot utilize Write-Warning (Azure Functions, for instance). Fall back to Write-Output - Write-Output "dotnet-install: Warning: $str" - } -} - -# Writes a line with error style settings. -# Use this function to show a human-readable comment along with an exception. -function Say-Error($str) { - try { - # Write-Error is quite verbose for the purpose of the function, let's write one line with error style settings. - $Host.UI.WriteErrorLine("dotnet-install: $str") - } - catch { - Write-Output "dotnet-install: Error: $str" - } -} - -function Say-Verbose($str) { - try { - Write-Verbose "dotnet-install: $str" - } - catch { - # Some platforms cannot utilize Write-Verbose (Azure Functions, for instance). Fall back to Write-Output - Write-Output "dotnet-install: $str" - } -} - -function Measure-Action($name, $block) { - $time = Measure-Command $block - $totalSeconds = $time.TotalSeconds - Say-Verbose "Action '$name' took $totalSeconds seconds" -} - -function Get-Remote-File-Size($zipUri) { - try { - $response = Invoke-WebRequest -UseBasicParsing -Uri $zipUri -Method Head - $fileSize = $response.Headers["Content-Length"] - if ((![string]::IsNullOrEmpty($fileSize))) { - Say "Remote file $zipUri size is $fileSize bytes." - - return $fileSize - } - } - catch { - Say-Verbose "Content-Length header was not extracted for $zipUri." - } - - return $null -} - -function Say-Invocation($Invocation) { - $command = $Invocation.MyCommand; - $args = (($Invocation.BoundParameters.Keys | foreach { "-$_ `"$($Invocation.BoundParameters[$_])`"" }) -join " ") - Say-Verbose "$command $args" -} - -function Invoke-With-Retry([ScriptBlock]$ScriptBlock, [System.Threading.CancellationToken]$cancellationToken = [System.Threading.CancellationToken]::None, [int]$MaxAttempts = 3, [int]$SecondsBetweenAttempts = 1) { - $Attempts = 0 - $local:startTime = $(get-date) - - while ($true) { - try { - return & $ScriptBlock - } - catch { - $Attempts++ - if (($Attempts -lt $MaxAttempts) -and -not $cancellationToken.IsCancellationRequested) { - Start-Sleep $SecondsBetweenAttempts - } - else { - $local:elapsedTime = $(get-date) - $local:startTime - if (($local:elapsedTime.TotalSeconds - $DownloadTimeout) -gt 0 -and -not $cancellationToken.IsCancellationRequested) { - throw New-Object System.TimeoutException("Failed to reach the server: connection timeout: default timeout is $DownloadTimeout second(s)"); - } - throw; - } - } - } -} - -function Get-Machine-Architecture() { - Say-Invocation $MyInvocation - - # On PS x86, PROCESSOR_ARCHITECTURE reports x86 even on x64 systems. - # To get the correct architecture, we need to use PROCESSOR_ARCHITEW6432. - # PS x64 doesn't define this, so we fall back to PROCESSOR_ARCHITECTURE. - # Possible values: amd64, x64, x86, arm64, arm - if ( $ENV:PROCESSOR_ARCHITEW6432 -ne $null ) { - return $ENV:PROCESSOR_ARCHITEW6432 - } - - try { - if ( ((Get-CimInstance -ClassName CIM_OperatingSystem).OSArchitecture) -like "ARM*") { - if ( [Environment]::Is64BitOperatingSystem ) { - return "arm64" - } - return "arm" - } - } - catch { - # Machine doesn't support Get-CimInstance - } - - return $ENV:PROCESSOR_ARCHITECTURE -} - -function Get-CLIArchitecture-From-Architecture([string]$Architecture) { - Say-Invocation $MyInvocation - - if ($Architecture -eq "") { - $Architecture = Get-Machine-Architecture - } - - switch ($Architecture.ToLowerInvariant()) { - { ($_ -eq "amd64") -or ($_ -eq "x64") } { return "x64" } - { $_ -eq "x86" } { return "x86" } - { $_ -eq "arm" } { return "arm" } - { $_ -eq "arm64" } { return "arm64" } - default { throw "Architecture '$Architecture' not supported. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues" } - } -} - -function ValidateFeedCredential([string] $FeedCredential) { - if ($Internal -and [string]::IsNullOrWhitespace($FeedCredential)) { - $message = "Provide credentials via -FeedCredential parameter." - if ($DryRun) { - Say-Warning "$message" - } - else { - throw "$message" - } - } - - #FeedCredential should start with "?", for it to be added to the end of the link. - #adding "?" at the beginning of the FeedCredential if needed. - if ((![string]::IsNullOrWhitespace($FeedCredential)) -and ($FeedCredential[0] -ne '?')) { - $FeedCredential = "?" + $FeedCredential - } - - return $FeedCredential -} -function Get-NormalizedQuality([string]$Quality) { - Say-Invocation $MyInvocation - - if ([string]::IsNullOrEmpty($Quality)) { - return "" - } - - switch ($Quality) { - { @("daily", "preview") -contains $_ } { return $Quality.ToLowerInvariant() } - #ga quality is available without specifying quality, so normalizing it to empty - { $_ -eq "ga" } { return "" } - default { throw "'$Quality' is not a supported value for -Quality option. Supported values are: daily, preview, ga. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues." } - } -} - -function Get-NormalizedChannel([string]$Channel) { - Say-Invocation $MyInvocation - - if ([string]::IsNullOrEmpty($Channel)) { - return "" - } - - if ($Channel.Contains("Current")) { - Say-Warning 'Value "Current" is deprecated for -Channel option. Use "STS" instead.' - } - - if ($Channel.StartsWith('release/')) { - Say-Warning 'Using branch name with -Channel option is no longer supported with newer releases. Use -Quality option with a channel in X.Y format instead, such as "-Channel 5.0 -Quality Daily."' - } - - switch ($Channel) { - { $_ -eq "lts" } { return "LTS" } - { $_ -eq "sts" } { return "STS" } - { $_ -eq "current" } { return "STS" } - default { return $Channel.ToLowerInvariant() } - } -} - -function Get-NormalizedProduct([string]$Runtime) { - Say-Invocation $MyInvocation - - switch ($Runtime) { - { $_ -eq "dotnet" } { return "dotnet-runtime" } - { $_ -eq "aspnetcore" } { return "aspnetcore-runtime" } - { $_ -eq "windowsdesktop" } { return "windowsdesktop-runtime" } - { [string]::IsNullOrEmpty($_) } { return "dotnet-sdk" } - default { throw "'$Runtime' is not a supported value for -Runtime option, supported values are: dotnet, aspnetcore, windowsdesktop. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues." } - } -} - - -# The version text returned from the feeds is a 1-line or 2-line string: -# For the SDK and the dotnet runtime (2 lines): -# Line 1: # commit_hash -# Line 2: # 4-part version -# For the aspnetcore runtime (1 line): -# Line 1: # 4-part version -function Get-Version-From-LatestVersion-File-Content([string]$VersionText) { - Say-Invocation $MyInvocation - - $Data = -split $VersionText - - $VersionInfo = @{ - CommitHash = $(if ($Data.Count -gt 1) { $Data[0] }) - Version = $Data[-1] # last line is always the version number. - } - return $VersionInfo -} - -function Load-Assembly([string] $Assembly) { - try { - Add-Type -Assembly $Assembly | Out-Null - } - catch { - # On Nano Server, Powershell Core Edition is used. Add-Type is unable to resolve base class assemblies because they are not GAC'd. - # Loading the base class assemblies is not unnecessary as the types will automatically get resolved. - } -} - -function GetHTTPResponse([Uri] $Uri, [bool]$HeaderOnly, [bool]$DisableRedirect, [bool]$DisableFeedCredential) { - $cts = New-Object System.Threading.CancellationTokenSource - - $downloadScript = { - - $HttpClient = $null - - try { - # HttpClient is used vs Invoke-WebRequest in order to support Nano Server which doesn't support the Invoke-WebRequest cmdlet. - Load-Assembly -Assembly System.Net.Http - - if (-not $ProxyAddress) { - try { - # Despite no proxy being explicitly specified, we may still be behind a default proxy - $DefaultProxy = [System.Net.WebRequest]::DefaultWebProxy; - if ($DefaultProxy -and (-not $DefaultProxy.IsBypassed($Uri))) { - if ($null -ne $DefaultProxy.GetProxy($Uri)) { - $ProxyAddress = $DefaultProxy.GetProxy($Uri).OriginalString - } - else { - $ProxyAddress = $null - } - $ProxyUseDefaultCredentials = $true - } - } - catch { - # Eat the exception and move forward as the above code is an attempt - # at resolving the DefaultProxy that may not have been a problem. - $ProxyAddress = $null - Say-Verbose("Exception ignored: $_.Exception.Message - moving forward...") - } - } - - $HttpClientHandler = New-Object System.Net.Http.HttpClientHandler - if ($ProxyAddress) { - $HttpClientHandler.Proxy = New-Object System.Net.WebProxy -Property @{ - Address = $ProxyAddress; - UseDefaultCredentials = $ProxyUseDefaultCredentials; - BypassList = $ProxyBypassList; - } - } - if ($DisableRedirect) { - $HttpClientHandler.AllowAutoRedirect = $false - } - $HttpClient = New-Object System.Net.Http.HttpClient -ArgumentList $HttpClientHandler - - # Default timeout for HttpClient is 100s. For a 50 MB download this assumes 500 KB/s average, any less will time out - # Defaulting to 20 minutes allows it to work over much slower connections. - $HttpClient.Timeout = New-TimeSpan -Seconds $DownloadTimeout - - if ($HeaderOnly) { - $completionOption = [System.Net.Http.HttpCompletionOption]::ResponseHeadersRead - } - else { - $completionOption = [System.Net.Http.HttpCompletionOption]::ResponseContentRead - } - - if ($DisableFeedCredential) { - $UriWithCredential = $Uri - } - else { - $UriWithCredential = "${Uri}${FeedCredential}" - } - - $Task = $HttpClient.GetAsync("$UriWithCredential", $completionOption).ConfigureAwait("false"); - $Response = $Task.GetAwaiter().GetResult(); - - if (($null -eq $Response) -or ((-not $HeaderOnly) -and (-not ($Response.IsSuccessStatusCode)))) { - # The feed credential is potentially sensitive info. Do not log FeedCredential to console output. - $DownloadException = [System.Exception] "Unable to download $Uri." - - if ($null -ne $Response) { - $DownloadException.Data["StatusCode"] = [int] $Response.StatusCode - $DownloadException.Data["ErrorMessage"] = "Unable to download $Uri. Returned HTTP status code: " + $DownloadException.Data["StatusCode"] - - if (404 -eq [int] $Response.StatusCode) { - $cts.Cancel() - } - } - - throw $DownloadException - } - - return $Response - } - catch [System.Net.Http.HttpRequestException] { - $DownloadException = [System.Exception] "Unable to download $Uri." - - # Pick up the exception message and inner exceptions' messages if they exist - $CurrentException = $PSItem.Exception - $ErrorMsg = $CurrentException.Message + "`r`n" - while ($CurrentException.InnerException) { - $CurrentException = $CurrentException.InnerException - $ErrorMsg += $CurrentException.Message + "`r`n" - } - - # Check if there is an issue concerning TLS. - if ($ErrorMsg -like "*SSL/TLS*") { - $ErrorMsg += "Ensure that TLS 1.2 or higher is enabled to use this script.`r`n" - } - - $DownloadException.Data["ErrorMessage"] = $ErrorMsg - throw $DownloadException - } - finally { - if ($null -ne $HttpClient) { - $HttpClient.Dispose() - } - } - } - - try { - return Invoke-With-Retry $downloadScript $cts.Token - } - finally { - if ($null -ne $cts) { - $cts.Dispose() - } - } -} - -function Get-Version-From-LatestVersion-File([string]$AzureFeed, [string]$Channel) { - Say-Invocation $MyInvocation - - $VersionFileUrl = $null - if ($Runtime -eq "dotnet") { - $VersionFileUrl = "$AzureFeed/Runtime/$Channel/latest.version" - } - elseif ($Runtime -eq "aspnetcore") { - $VersionFileUrl = "$AzureFeed/aspnetcore/Runtime/$Channel/latest.version" - } - elseif ($Runtime -eq "windowsdesktop") { - $VersionFileUrl = "$AzureFeed/WindowsDesktop/$Channel/latest.version" - } - elseif (-not $Runtime) { - $VersionFileUrl = "$AzureFeed/Sdk/$Channel/latest.version" - } - else { - throw "Invalid value for `$Runtime" - } - - Say-Verbose "Constructed latest.version URL: $VersionFileUrl" - - try { - $Response = GetHTTPResponse -Uri $VersionFileUrl - } - catch { - Say-Verbose "Failed to download latest.version file." - throw - } - $StringContent = $Response.Content.ReadAsStringAsync().Result - - switch ($Response.Content.Headers.ContentType) { - { ($_ -eq "application/octet-stream") } { $VersionText = $StringContent } - { ($_ -eq "text/plain") } { $VersionText = $StringContent } - { ($_ -eq "text/plain; charset=UTF-8") } { $VersionText = $StringContent } - default { throw "``$Response.Content.Headers.ContentType`` is an unknown .version file content type." } - } - - $VersionInfo = Get-Version-From-LatestVersion-File-Content $VersionText - - return $VersionInfo -} - -function Parse-Jsonfile-For-Version([string]$JSonFile) { - Say-Invocation $MyInvocation - - If (-Not (Test-Path $JSonFile)) { - throw "Unable to find '$JSonFile'" - } - try { - $JSonContent = Get-Content($JSonFile) -Raw | ConvertFrom-Json | Select-Object -expand "sdk" -ErrorAction SilentlyContinue - } - catch { - Say-Error "Json file unreadable: '$JSonFile'" - throw - } - if ($JSonContent) { - try { - $JSonContent.PSObject.Properties | ForEach-Object { - $PropertyName = $_.Name - if ($PropertyName -eq "version") { - $Version = $_.Value - Say-Verbose "Version = $Version" - } - } - } - catch { - Say-Error "Unable to parse the SDK node in '$JSonFile'" - throw - } - } - else { - throw "Unable to find the SDK node in '$JSonFile'" - } - If ($Version -eq $null) { - throw "Unable to find the SDK:version node in '$JSonFile'" - } - return $Version -} - -function Get-Specific-Version-From-Version([string]$AzureFeed, [string]$Channel, [string]$Version, [string]$JSonFile) { - Say-Invocation $MyInvocation - - if (-not $JSonFile) { - if ($Version.ToLowerInvariant() -eq "latest") { - $LatestVersionInfo = Get-Version-From-LatestVersion-File -AzureFeed $AzureFeed -Channel $Channel - return $LatestVersionInfo.Version - } - else { - return $Version - } - } - else { - return Parse-Jsonfile-For-Version $JSonFile - } -} - -function Get-Download-Link([string]$AzureFeed, [string]$SpecificVersion, [string]$CLIArchitecture) { - Say-Invocation $MyInvocation - - # If anything fails in this lookup it will default to $SpecificVersion - $SpecificProductVersion = Get-Product-Version -AzureFeed $AzureFeed -SpecificVersion $SpecificVersion - - if ($Runtime -eq "dotnet") { - $PayloadURL = "$AzureFeed/Runtime/$SpecificVersion/dotnet-runtime-$SpecificProductVersion-win-$CLIArchitecture.zip" - } - elseif ($Runtime -eq "aspnetcore") { - $PayloadURL = "$AzureFeed/aspnetcore/Runtime/$SpecificVersion/aspnetcore-runtime-$SpecificProductVersion-win-$CLIArchitecture.zip" - } - elseif ($Runtime -eq "windowsdesktop") { - # The windows desktop runtime is part of the core runtime layout prior to 5.0 - $PayloadURL = "$AzureFeed/Runtime/$SpecificVersion/windowsdesktop-runtime-$SpecificProductVersion-win-$CLIArchitecture.zip" - if ($SpecificVersion -match '^(\d+)\.(.*)$') { - $majorVersion = [int]$Matches[1] - if ($majorVersion -ge 5) { - $PayloadURL = "$AzureFeed/WindowsDesktop/$SpecificVersion/windowsdesktop-runtime-$SpecificProductVersion-win-$CLIArchitecture.zip" - } - } - } - elseif (-not $Runtime) { - $PayloadURL = "$AzureFeed/Sdk/$SpecificVersion/dotnet-sdk-$SpecificProductVersion-win-$CLIArchitecture.zip" - } - else { - throw "Invalid value for `$Runtime" - } - - Say-Verbose "Constructed primary named payload URL: $PayloadURL" - - return $PayloadURL, $SpecificProductVersion -} - -function Get-LegacyDownload-Link([string]$AzureFeed, [string]$SpecificVersion, [string]$CLIArchitecture) { - Say-Invocation $MyInvocation - - if (-not $Runtime) { - $PayloadURL = "$AzureFeed/Sdk/$SpecificVersion/dotnet-dev-win-$CLIArchitecture.$SpecificVersion.zip" - } - elseif ($Runtime -eq "dotnet") { - $PayloadURL = "$AzureFeed/Runtime/$SpecificVersion/dotnet-win-$CLIArchitecture.$SpecificVersion.zip" - } - else { - return $null - } - - Say-Verbose "Constructed legacy named payload URL: $PayloadURL" - - return $PayloadURL -} - -function Get-Product-Version([string]$AzureFeed, [string]$SpecificVersion, [string]$PackageDownloadLink) { - Say-Invocation $MyInvocation - - # Try to get the version number, using the productVersion.txt file located next to the installer file. - $ProductVersionTxtURLs = (Get-Product-Version-Url $AzureFeed $SpecificVersion $PackageDownloadLink -Flattened $true), - (Get-Product-Version-Url $AzureFeed $SpecificVersion $PackageDownloadLink -Flattened $false) - - Foreach ($ProductVersionTxtURL in $ProductVersionTxtURLs) { - Say-Verbose "Checking for the existence of $ProductVersionTxtURL" - - try { - $productVersionResponse = GetHTTPResponse($productVersionTxtUrl) - - if ($productVersionResponse.StatusCode -eq 200) { - $productVersion = $productVersionResponse.Content.ReadAsStringAsync().Result.Trim() - if ($productVersion -ne $SpecificVersion) { - Say "Using alternate version $productVersion found in $ProductVersionTxtURL" - } - return $productVersion - } - else { - Say-Verbose "Got StatusCode $($productVersionResponse.StatusCode) when trying to get productVersion.txt at $productVersionTxtUrl." - } - } - catch { - Say-Verbose "Could not read productVersion.txt at $productVersionTxtUrl (Exception: '$($_.Exception.Message)'. )" - } - } - - # Getting the version number with productVersion.txt has failed. Try parsing the download link for a version number. - if ([string]::IsNullOrEmpty($PackageDownloadLink)) { - Say-Verbose "Using the default value '$SpecificVersion' as the product version." - return $SpecificVersion - } - - $productVersion = Get-ProductVersionFromDownloadLink $PackageDownloadLink $SpecificVersion - return $productVersion -} - -function Get-Product-Version-Url([string]$AzureFeed, [string]$SpecificVersion, [string]$PackageDownloadLink, [bool]$Flattened) { - Say-Invocation $MyInvocation - - $majorVersion = $null - if ($SpecificVersion -match '^(\d+)\.(.*)') { - $majorVersion = $Matches[1] -as [int] - } - - $pvFileName = 'productVersion.txt' - if ($Flattened) { - if (-not $Runtime) { - $pvFileName = 'sdk-productVersion.txt' - } - elseif ($Runtime -eq "dotnet") { - $pvFileName = 'runtime-productVersion.txt' - } - else { - $pvFileName = "$Runtime-productVersion.txt" - } - } - - if ([string]::IsNullOrEmpty($PackageDownloadLink)) { - if ($Runtime -eq "dotnet") { - $ProductVersionTxtURL = "$AzureFeed/Runtime/$SpecificVersion/$pvFileName" - } - elseif ($Runtime -eq "aspnetcore") { - $ProductVersionTxtURL = "$AzureFeed/aspnetcore/Runtime/$SpecificVersion/$pvFileName" - } - elseif ($Runtime -eq "windowsdesktop") { - # The windows desktop runtime is part of the core runtime layout prior to 5.0 - $ProductVersionTxtURL = "$AzureFeed/Runtime/$SpecificVersion/$pvFileName" - if ($majorVersion -ne $null -and $majorVersion -ge 5) { - $ProductVersionTxtURL = "$AzureFeed/WindowsDesktop/$SpecificVersion/$pvFileName" - } - } - elseif (-not $Runtime) { - $ProductVersionTxtURL = "$AzureFeed/Sdk/$SpecificVersion/$pvFileName" - } - else { - throw "Invalid value '$Runtime' specified for `$Runtime" - } - } - else { - $ProductVersionTxtURL = $PackageDownloadLink.Substring(0, $PackageDownloadLink.LastIndexOf("/")) + "/$pvFileName" - } - - Say-Verbose "Constructed productVersion link: $ProductVersionTxtURL" - - return $ProductVersionTxtURL -} - -function Get-ProductVersionFromDownloadLink([string]$PackageDownloadLink, [string]$SpecificVersion) { - Say-Invocation $MyInvocation - - #product specific version follows the product name - #for filename 'dotnet-sdk-3.1.404-win-x64.zip': the product version is 3.1.400 - $filename = $PackageDownloadLink.Substring($PackageDownloadLink.LastIndexOf("/") + 1) - $filenameParts = $filename.Split('-') - if ($filenameParts.Length -gt 2) { - $productVersion = $filenameParts[2] - Say-Verbose "Extracted product version '$productVersion' from download link '$PackageDownloadLink'." - } - else { - Say-Verbose "Using the default value '$SpecificVersion' as the product version." - $productVersion = $SpecificVersion - } - return $productVersion -} - -function Get-User-Share-Path() { - Say-Invocation $MyInvocation - - $InstallRoot = $env:DOTNET_INSTALL_DIR - if (!$InstallRoot) { - $InstallRoot = "$env:LocalAppData\Microsoft\dotnet" - } - elseif ($InstallRoot -like "$env:ProgramFiles\dotnet\?*") { - Say-Warning "The install root specified by the environment variable DOTNET_INSTALL_DIR points to the sub folder of $env:ProgramFiles\dotnet which is the default dotnet install root using .NET SDK installer. It is better to keep aligned with .NET SDK installer." - } - return $InstallRoot -} - -function Resolve-Installation-Path([string]$InstallDir) { - Say-Invocation $MyInvocation - - if ($InstallDir -eq "") { - return Get-User-Share-Path - } - return $InstallDir -} - -function Test-User-Write-Access([string]$InstallDir) { - try { - $tempFileName = [guid]::NewGuid().ToString() - $tempFilePath = Join-Path -Path $InstallDir -ChildPath $tempFileName - New-Item -Path $tempFilePath -ItemType File -Force - Remove-Item $tempFilePath -Force - return $true - } - catch { - return $false - } -} - -function Is-Dotnet-Package-Installed([string]$InstallRoot, [string]$RelativePathToPackage, [string]$SpecificVersion) { - Say-Invocation $MyInvocation - - $DotnetPackagePath = Join-Path -Path $InstallRoot -ChildPath $RelativePathToPackage | Join-Path -ChildPath $SpecificVersion - Say-Verbose "Is-Dotnet-Package-Installed: DotnetPackagePath=$DotnetPackagePath" - return Test-Path $DotnetPackagePath -PathType Container -} - -function Get-Absolute-Path([string]$RelativeOrAbsolutePath) { - # Too much spam - # Say-Invocation $MyInvocation - - return $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($RelativeOrAbsolutePath) -} - -function Get-Path-Prefix-With-Version($path) { - # example path with regex: shared/1.0.0-beta-12345/somepath - $match = [regex]::match($path, "/\d+\.\d+[^/]+/") - if ($match.Success) { - return $entry.FullName.Substring(0, $match.Index + $match.Length) - } - - return $null -} - -function Get-List-Of-Directories-And-Versions-To-Unpack-From-Dotnet-Package([System.IO.Compression.ZipArchive]$Zip, [string]$OutPath) { - Say-Invocation $MyInvocation - - $ret = @() - foreach ($entry in $Zip.Entries) { - $dir = Get-Path-Prefix-With-Version $entry.FullName - if ($null -ne $dir) { - $path = Get-Absolute-Path $(Join-Path -Path $OutPath -ChildPath $dir) - if (-Not (Test-Path $path -PathType Container)) { - $ret += $dir - } - } - } - - $ret = $ret | Sort-Object | Get-Unique - - $values = ($ret | foreach { "$_" }) -join ";" - Say-Verbose "Directories to unpack: $values" - - return $ret -} - -# Example zip content and extraction algorithm: -# Rule: files if extracted are always being extracted to the same relative path locally -# .\ -# a.exe # file does not exist locally, extract -# b.dll # file exists locally, override only if $OverrideFiles set -# aaa\ # same rules as for files -# ... -# abc\1.0.0\ # directory contains version and exists locally -# ... # do not extract content under versioned part -# abc\asd\ # same rules as for files -# ... -# def\ghi\1.0.1\ # directory contains version and does not exist locally -# ... # extract content -function Extract-Dotnet-Package([string]$ZipPath, [string]$OutPath) { - Say-Invocation $MyInvocation - - Load-Assembly -Assembly System.IO.Compression.FileSystem - Set-Variable -Name Zip - try { - $Zip = [System.IO.Compression.ZipFile]::OpenRead($ZipPath) - - $DirectoriesToUnpack = Get-List-Of-Directories-And-Versions-To-Unpack-From-Dotnet-Package -Zip $Zip -OutPath $OutPath - - foreach ($entry in $Zip.Entries) { - $PathWithVersion = Get-Path-Prefix-With-Version $entry.FullName - if (($null -eq $PathWithVersion) -Or ($DirectoriesToUnpack -contains $PathWithVersion)) { - $DestinationPath = Get-Absolute-Path $(Join-Path -Path $OutPath -ChildPath $entry.FullName) - $DestinationDir = Split-Path -Parent $DestinationPath - $OverrideFiles = $OverrideNonVersionedFiles -Or (-Not (Test-Path $DestinationPath)) - if ((-Not $DestinationPath.EndsWith("\")) -And $OverrideFiles) { - New-Item -ItemType Directory -Force -Path $DestinationDir | Out-Null - [System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $DestinationPath, $OverrideNonVersionedFiles) - } - } - } - } - catch { - Say-Error "Failed to extract package. Exception: $_" - throw; - } - finally { - if ($null -ne $Zip) { - $Zip.Dispose() - } - } -} - -function DownloadFile($Source, [string]$OutPath) { - if ($Source -notlike "http*") { - # Using System.IO.Path.GetFullPath to get the current directory - # does not work in this context - $pwd gives the current directory - if (![System.IO.Path]::IsPathRooted($Source)) { - $Source = $(Join-Path -Path $pwd -ChildPath $Source) - } - $Source = Get-Absolute-Path $Source - Say "Copying file from $Source to $OutPath" - Copy-Item $Source $OutPath - return - } - - $Stream = $null - - try { - $Response = GetHTTPResponse -Uri $Source - $Stream = $Response.Content.ReadAsStreamAsync().Result - $File = [System.IO.File]::Create($OutPath) - $Stream.CopyTo($File) - $File.Close() - - ValidateRemoteLocalFileSizes -LocalFileOutPath $OutPath -SourceUri $Source - } - finally { - if ($null -ne $Stream) { - $Stream.Dispose() - } - } -} - -function ValidateRemoteLocalFileSizes([string]$LocalFileOutPath, $SourceUri) { - try { - $remoteFileSize = Get-Remote-File-Size -zipUri $SourceUri - $fileSize = [long](Get-Item $LocalFileOutPath).Length - Say "Downloaded file $SourceUri size is $fileSize bytes." - - if ((![string]::IsNullOrEmpty($remoteFileSize)) -and !([string]::IsNullOrEmpty($fileSize)) ) { - if ($remoteFileSize -ne $fileSize) { - Say "The remote and local file sizes are not equal. Remote file size is $remoteFileSize bytes and local size is $fileSize bytes. The local package may be corrupted." - } - else { - Say "The remote and local file sizes are equal." - } - } - else { - Say "Either downloaded or local package size can not be measured. One of them may be corrupted." - } - } - catch { - Say "Either downloaded or local package size can not be measured. One of them may be corrupted." - } -} - -function SafeRemoveFile($Path) { - try { - if (Test-Path $Path) { - Remove-Item $Path - Say-Verbose "The temporary file `"$Path`" was removed." - } - else { - Say-Verbose "The temporary file `"$Path`" does not exist, therefore is not removed." - } - } - catch { - Say-Warning "Failed to remove the temporary file: `"$Path`", remove it manually." - } -} - -function Prepend-Sdk-InstallRoot-To-Path([string]$InstallRoot) { - $BinPath = Get-Absolute-Path $(Join-Path -Path $InstallRoot -ChildPath "") - if (-Not $NoPath) { - $SuffixedBinPath = "$BinPath;" - if (-Not $env:path.Contains($SuffixedBinPath)) { - Say "Adding to current process PATH: `"$BinPath`". Note: This change will not be visible if PowerShell was run as a child process." - $env:path = $SuffixedBinPath + $env:path - } - else { - Say-Verbose "Current process PATH already contains `"$BinPath`"" - } - } - else { - Say "Binaries of dotnet can be found in $BinPath" - } -} - -function PrintDryRunOutput($Invocation, $DownloadLinks) { - Say "Payload URLs:" - - for ($linkIndex = 0; $linkIndex -lt $DownloadLinks.count; $linkIndex++) { - Say "URL #$linkIndex - $($DownloadLinks[$linkIndex].type): $($DownloadLinks[$linkIndex].downloadLink)" - } - $RepeatableCommand = ".\$ScriptName -Version `"$SpecificVersion`" -InstallDir `"$InstallRoot`" -Architecture `"$CLIArchitecture`"" - if ($Runtime -eq "dotnet") { - $RepeatableCommand += " -Runtime `"dotnet`"" - } - elseif ($Runtime -eq "aspnetcore") { - $RepeatableCommand += " -Runtime `"aspnetcore`"" - } - - foreach ($key in $Invocation.BoundParameters.Keys) { - if (-not (@("Architecture", "Channel", "DryRun", "InstallDir", "Runtime", "SharedRuntime", "Version", "Quality", "FeedCredential") -contains $key)) { - $RepeatableCommand += " -$key `"$($Invocation.BoundParameters[$key])`"" - } - } - if ($Invocation.BoundParameters.Keys -contains "FeedCredential") { - $RepeatableCommand += " -FeedCredential `"`"" - } - Say "Repeatable invocation: $RepeatableCommand" - if ($SpecificVersion -ne $EffectiveVersion) { - Say "NOTE: Due to finding a version manifest with this runtime, it would actually install with version '$EffectiveVersion'" - } -} - -function Get-AkaMSDownloadLink([string]$Channel, [string]$Quality, [bool]$Internal, [string]$Product, [string]$Architecture) { - Say-Invocation $MyInvocation - - #quality is not supported for LTS or STS channel - if (![string]::IsNullOrEmpty($Quality) -and (@("LTS", "STS") -contains $Channel)) { - $Quality = "" - Say-Warning "Specifying quality for STS or LTS channel is not supported, the quality will be ignored." - } - Say-Verbose "Retrieving primary payload URL from aka.ms link for channel: '$Channel', quality: '$Quality' product: '$Product', os: 'win', architecture: '$Architecture'." - - #construct aka.ms link - $akaMsLink = "https://aka.ms/dotnet" - if ($Internal) { - $akaMsLink += "/internal" - } - $akaMsLink += "/$Channel" - if (-not [string]::IsNullOrEmpty($Quality)) { - $akaMsLink += "/$Quality" - } - $akaMsLink += "/$Product-win-$Architecture.zip" - Say-Verbose "Constructed aka.ms link: '$akaMsLink'." - $akaMsDownloadLink = $null - - for ($maxRedirections = 9; $maxRedirections -ge 0; $maxRedirections--) { - #get HTTP response - #do not pass credentials as a part of the $akaMsLink and do not apply credentials in the GetHTTPResponse function - #otherwise the redirect link would have credentials as well - #it would result in applying credentials twice to the resulting link and thus breaking it, and in echoing credentials to the output as a part of redirect link - $Response = GetHTTPResponse -Uri $akaMsLink -HeaderOnly $true -DisableRedirect $true -DisableFeedCredential $true - Say-Verbose "Received response:`n$Response" - - if ([string]::IsNullOrEmpty($Response)) { - Say-Verbose "The link '$akaMsLink' is not valid: failed to get redirect location. The resource is not available." - return $null - } - - #if HTTP code is 301 (Moved Permanently), the redirect link exists - if ($Response.StatusCode -eq 301) { - try { - $akaMsDownloadLink = $Response.Headers.GetValues("Location")[0] - - if ([string]::IsNullOrEmpty($akaMsDownloadLink)) { - Say-Verbose "The link '$akaMsLink' is not valid: server returned 301 (Moved Permanently), but the headers do not contain the redirect location." - return $null - } - - Say-Verbose "The redirect location retrieved: '$akaMsDownloadLink'." - # This may yet be a link to another redirection. Attempt to retrieve the page again. - $akaMsLink = $akaMsDownloadLink - continue - } - catch { - Say-Verbose "The link '$akaMsLink' is not valid: failed to get redirect location." - return $null - } - } - elseif ((($Response.StatusCode -lt 300) -or ($Response.StatusCode -ge 400)) -and (-not [string]::IsNullOrEmpty($akaMsDownloadLink))) { - # Redirections have ended. - return $akaMsDownloadLink - } - - Say-Verbose "The link '$akaMsLink' is not valid: failed to retrieve the redirection location." - return $null - } - - Say-Verbose "Aka.ms links have redirected more than the maximum allowed redirections. This may be caused by a cyclic redirection of aka.ms links." - return $null - -} - -function Get-AkaMsLink-And-Version([string] $NormalizedChannel, [string] $NormalizedQuality, [bool] $Internal, [string] $ProductName, [string] $Architecture) { - $AkaMsDownloadLink = Get-AkaMSDownloadLink -Channel $NormalizedChannel -Quality $NormalizedQuality -Internal $Internal -Product $ProductName -Architecture $Architecture - - if ([string]::IsNullOrEmpty($AkaMsDownloadLink)) { - if (-not [string]::IsNullOrEmpty($NormalizedQuality)) { - # if quality is specified - exit with error - there is no fallback approach - Say-Error "Failed to locate the latest version in the channel '$NormalizedChannel' with '$NormalizedQuality' quality for '$ProductName', os: 'win', architecture: '$Architecture'." - Say-Error "Refer to: https://aka.ms/dotnet-os-lifecycle for information on .NET Core support." - throw "aka.ms link resolution failure" - } - Say-Verbose "Falling back to latest.version file approach." - return ($null, $null, $null) - } - else { - Say-Verbose "Retrieved primary named payload URL from aka.ms link: '$AkaMsDownloadLink'." - Say-Verbose "Downloading using legacy url will not be attempted." - - #get version from the path - $pathParts = $AkaMsDownloadLink.Split('/') - if ($pathParts.Length -ge 2) { - $SpecificVersion = $pathParts[$pathParts.Length - 2] - Say-Verbose "Version: '$SpecificVersion'." - } - else { - Say-Error "Failed to extract the version from download link '$AkaMsDownloadLink'." - return ($null, $null, $null) - } - - #retrieve effective (product) version - $EffectiveVersion = Get-Product-Version -SpecificVersion $SpecificVersion -PackageDownloadLink $AkaMsDownloadLink - Say-Verbose "Product version: '$EffectiveVersion'." - - return ($AkaMsDownloadLink, $SpecificVersion, $EffectiveVersion); - } -} - -function Get-Feeds-To-Use() { - $feeds = @( - "https://builds.dotnet.microsoft.com/dotnet" - "https://ci.dot.net/public" - ) - - if (-not [string]::IsNullOrEmpty($AzureFeed)) { - $feeds = @($AzureFeed) - } - - if (-not [string]::IsNullOrEmpty($UncachedFeed)) { - $feeds = @($UncachedFeed) - } - - Write-Verbose "Initialized feeds: $feeds" - - return $feeds -} - -function Resolve-AssetName-And-RelativePath([string] $Runtime) { - - if ($Runtime -eq "dotnet") { - $assetName = ".NET Core Runtime" - $dotnetPackageRelativePath = "shared\Microsoft.NETCore.App" - } - elseif ($Runtime -eq "aspnetcore") { - $assetName = "ASP.NET Core Runtime" - $dotnetPackageRelativePath = "shared\Microsoft.AspNetCore.App" - } - elseif ($Runtime -eq "windowsdesktop") { - $assetName = ".NET Core Windows Desktop Runtime" - $dotnetPackageRelativePath = "shared\Microsoft.WindowsDesktop.App" - } - elseif (-not $Runtime) { - $assetName = ".NET Core SDK" - $dotnetPackageRelativePath = "sdk" - } - else { - throw "Invalid value for `$Runtime" - } - - return ($assetName, $dotnetPackageRelativePath) -} - -function Prepare-Install-Directory { - $diskSpaceWarning = "Failed to check the disk space. Installation will continue, but it may fail if you do not have enough disk space."; - - if ($PSVersionTable.PSVersion.Major -lt 7) { - Say-Verbose $diskSpaceWarning - return - } - - New-Item -ItemType Directory -Force -Path $InstallRoot | Out-Null - - $installDrive = $((Get-Item $InstallRoot -Force).PSDrive.Name); - $diskInfo = $null - try { - $diskInfo = Get-PSDrive -Name $installDrive - } - catch { - Say-Warning $diskSpaceWarning - } - - # The check is relevant for PS version >= 7, the result can be irrelevant for older versions. See https://github.com/PowerShell/PowerShell/issues/12442. - if ( ($null -ne $diskInfo) -and ($diskInfo.Free / 1MB -le 100)) { - throw "There is not enough disk space on drive ${installDrive}:" - } -} - -if ($Help) { - Get-Help $PSCommandPath -Examples - exit -} - -Say-Verbose "Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:" -Say-Verbose "- The SDK needs to be installed without user interaction and without admin rights." -Say-Verbose "- The SDK installation doesn't need to persist across multiple CI runs." -Say-Verbose "To set up a development environment or to run apps, use installers rather than this script. Visit https://dotnet.microsoft.com/download to get the installer.`r`n" - -if ($SharedRuntime -and (-not $Runtime)) { - $Runtime = "dotnet" -} - -$OverrideNonVersionedFiles = !$SkipNonVersionedFiles - -Measure-Action "Product discovery" { - $script:CLIArchitecture = Get-CLIArchitecture-From-Architecture $Architecture - $script:NormalizedQuality = Get-NormalizedQuality $Quality - Say-Verbose "Normalized quality: '$NormalizedQuality'" - $script:NormalizedChannel = Get-NormalizedChannel $Channel - Say-Verbose "Normalized channel: '$NormalizedChannel'" - $script:NormalizedProduct = Get-NormalizedProduct $Runtime - Say-Verbose "Normalized product: '$NormalizedProduct'" - $script:FeedCredential = ValidateFeedCredential $FeedCredential -} - -$InstallRoot = Resolve-Installation-Path $InstallDir -if (-not (Test-User-Write-Access $InstallRoot)) { - Say-Error "The current user doesn't have write access to the installation root '$InstallRoot' to install .NET. Please try specifying a different installation directory using the -InstallDir parameter, or ensure the selected directory has the appropriate permissions." - throw -} -Say-Verbose "InstallRoot: $InstallRoot" -$ScriptName = $MyInvocation.MyCommand.Name -($assetName, $dotnetPackageRelativePath) = Resolve-AssetName-And-RelativePath -Runtime $Runtime - -$feeds = Get-Feeds-To-Use -$DownloadLinks = @() - -if ($Version.ToLowerInvariant() -ne "latest" -and -not [string]::IsNullOrEmpty($Quality)) { - throw "Quality and Version options are not allowed to be specified simultaneously. See https:// learn.microsoft.com/dotnet/core/tools/dotnet-install-script#options for details." -} - -# aka.ms links can only be used if the user did not request a specific version via the command line or a global.json file. -if ([string]::IsNullOrEmpty($JSonFile) -and ($Version -eq "latest")) { - ($DownloadLink, $SpecificVersion, $EffectiveVersion) = Get-AkaMsLink-And-Version $NormalizedChannel $NormalizedQuality $Internal $NormalizedProduct $CLIArchitecture - - if ($null -ne $DownloadLink) { - $DownloadLinks += New-Object PSObject -Property @{downloadLink = "$DownloadLink"; specificVersion = "$SpecificVersion"; effectiveVersion = "$EffectiveVersion"; type = 'aka.ms' } - Say-Verbose "Generated aka.ms link $DownloadLink with version $EffectiveVersion" - - if (-Not $DryRun) { - Say-Verbose "Checking if the version $EffectiveVersion is already installed" - if (Is-Dotnet-Package-Installed -InstallRoot $InstallRoot -RelativePathToPackage $dotnetPackageRelativePath -SpecificVersion $EffectiveVersion) { - Say "$assetName with version '$EffectiveVersion' is already installed." - Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot - return - } - } - } -} - -# Primary and legacy links cannot be used if a quality was specified. -# If we already have an aka.ms link, no need to search the blob feeds. -if ([string]::IsNullOrEmpty($NormalizedQuality) -and 0 -eq $DownloadLinks.count) { - foreach ($feed in $feeds) { - try { - $SpecificVersion = Get-Specific-Version-From-Version -AzureFeed $feed -Channel $Channel -Version $Version -JSonFile $JSonFile - $DownloadLink, $EffectiveVersion = Get-Download-Link -AzureFeed $feed -SpecificVersion $SpecificVersion -CLIArchitecture $CLIArchitecture - $LegacyDownloadLink = Get-LegacyDownload-Link -AzureFeed $feed -SpecificVersion $SpecificVersion -CLIArchitecture $CLIArchitecture - - $DownloadLinks += New-Object PSObject -Property @{downloadLink = "$DownloadLink"; specificVersion = "$SpecificVersion"; effectiveVersion = "$EffectiveVersion"; type = 'primary' } - Say-Verbose "Generated primary link $DownloadLink with version $EffectiveVersion" - - if (-not [string]::IsNullOrEmpty($LegacyDownloadLink)) { - $DownloadLinks += New-Object PSObject -Property @{downloadLink = "$LegacyDownloadLink"; specificVersion = "$SpecificVersion"; effectiveVersion = "$EffectiveVersion"; type = 'legacy' } - Say-Verbose "Generated legacy link $LegacyDownloadLink with version $EffectiveVersion" - } - - if (-Not $DryRun) { - Say-Verbose "Checking if the version $EffectiveVersion is already installed" - if (Is-Dotnet-Package-Installed -InstallRoot $InstallRoot -RelativePathToPackage $dotnetPackageRelativePath -SpecificVersion $EffectiveVersion) { - Say "$assetName with version '$EffectiveVersion' is already installed." - Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot - return - } - } - } - catch { - Say-Verbose "Failed to acquire download links from feed $feed. Exception: $_" - } - } -} - -if ($DownloadLinks.count -eq 0) { - throw "Failed to resolve the exact version number." -} - -if ($DryRun) { - PrintDryRunOutput $MyInvocation $DownloadLinks - return -} - -Measure-Action "Installation directory preparation" { Prepare-Install-Directory } - -Say-Verbose "Zip path: $ZipPath" - -$DownloadSucceeded = $false -$DownloadedLink = $null -$ErrorMessages = @() - -foreach ($link in $DownloadLinks) { - Say-Verbose "Downloading `"$($link.type)`" link $($link.downloadLink)" - - try { - Measure-Action "Package download" { DownloadFile -Source $link.downloadLink -OutPath $ZipPath } - Say-Verbose "Download succeeded." - $DownloadSucceeded = $true - $DownloadedLink = $link - break - } - catch { - $StatusCode = $null - $ErrorMessage = $null - - if ($PSItem.Exception.Data.Contains("StatusCode")) { - $StatusCode = $PSItem.Exception.Data["StatusCode"] - } - - if ($PSItem.Exception.Data.Contains("ErrorMessage")) { - $ErrorMessage = $PSItem.Exception.Data["ErrorMessage"] - } - else { - $ErrorMessage = $PSItem.Exception.Message - } - - Say-Verbose "Download failed with status code $StatusCode. Error message: $ErrorMessage" - $ErrorMessages += "Downloading from `"$($link.type)`" link has failed with error:`nUri: $($link.downloadLink)`nStatusCode: $StatusCode`nError: $ErrorMessage" - } - - # This link failed. Clean up before trying the next one. - SafeRemoveFile -Path $ZipPath -} - -if (-not $DownloadSucceeded) { - foreach ($ErrorMessage in $ErrorMessages) { - Say-Error $ErrorMessages - } - - throw "Could not find `"$assetName`" with version = $($DownloadLinks[0].effectiveVersion)`nRefer to: https://aka.ms/dotnet-os-lifecycle for information on .NET support" -} - -Say "Extracting the archive." -Measure-Action "Package extraction" { Extract-Dotnet-Package -ZipPath $ZipPath -OutPath $InstallRoot } - -# Check if the SDK version is installed; if not, fail the installation. -$isAssetInstalled = $false - -# if the version contains "RTM" or "servicing"; check if a 'release-type' SDK version is installed. -if ($DownloadedLink.effectiveVersion -Match "rtm" -or $DownloadedLink.effectiveVersion -Match "servicing") { - $ReleaseVersion = $DownloadedLink.effectiveVersion.Split("-")[0] - Say-Verbose "Checking installation: version = $ReleaseVersion" - $isAssetInstalled = Is-Dotnet-Package-Installed -InstallRoot $InstallRoot -RelativePathToPackage $dotnetPackageRelativePath -SpecificVersion $ReleaseVersion -} - -# Check if the SDK version is installed. -if (!$isAssetInstalled) { - Say-Verbose "Checking installation: version = $($DownloadedLink.effectiveVersion)" - $isAssetInstalled = Is-Dotnet-Package-Installed -InstallRoot $InstallRoot -RelativePathToPackage $dotnetPackageRelativePath -SpecificVersion $DownloadedLink.effectiveVersion -} - -# Version verification failed. More likely something is wrong either with the downloaded content or with the verification algorithm. -if (!$isAssetInstalled) { - Say-Error "Failed to verify the version of installed `"$assetName`".`nInstallation source: $($DownloadedLink.downloadLink).`nInstallation location: $InstallRoot.`nReport the bug at https://github.com/dotnet/install-scripts/issues." - throw "`"$assetName`" with version = $($DownloadedLink.effectiveVersion) failed to install with an unknown error." -} - -if (-not $KeepZip) { - SafeRemoveFile -Path $ZipPath -} - -Measure-Action "Setting up shell environment" { Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot } - -Say "Note that the script does not ensure your Windows version is supported during the installation." -Say "To check the list of supported versions, go to https://learn.microsoft.com/dotnet/core/install/windows#supported-versions" -Say "Installed version is $($DownloadedLink.effectiveVersion)" -Say "Installation finished" - -# SIG # Begin signature block -# MIIoKgYJKoZIhvcNAQcCoIIoGzCCKBcCAQExDzANBglghkgBZQMEAgEFADB5Bgor -# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG -# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCyKn7B6ieM6Y2C -# rr9TCFvTSv2mMIh9mBGXh4z2gOksEqCCDXYwggX0MIID3KADAgECAhMzAAAEhV6Z -# 7A5ZL83XAAAAAASFMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD -# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p -# bmcgUENBIDIwMTEwHhcNMjUwNjE5MTgyMTM3WhcNMjYwNjE3MTgyMTM3WjB0MQsw -# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u -# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -# AQDASkh1cpvuUqfbqxele7LCSHEamVNBfFE4uY1FkGsAdUF/vnjpE1dnAD9vMOqy -# 5ZO49ILhP4jiP/P2Pn9ao+5TDtKmcQ+pZdzbG7t43yRXJC3nXvTGQroodPi9USQi -# 9rI+0gwuXRKBII7L+k3kMkKLmFrsWUjzgXVCLYa6ZH7BCALAcJWZTwWPoiT4HpqQ -# hJcYLB7pfetAVCeBEVZD8itKQ6QA5/LQR+9X6dlSj4Vxta4JnpxvgSrkjXCz+tlJ -# 67ABZ551lw23RWU1uyfgCfEFhBfiyPR2WSjskPl9ap6qrf8fNQ1sGYun2p4JdXxe -# UAKf1hVa/3TQXjvPTiRXCnJPAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE -# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUuCZyGiCuLYE0aU7j5TFqY05kko0w -# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW -# MBQGA1UEBRMNMjMwMDEyKzUwNTM1OTAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci -# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j -# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG -# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu -# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0 -# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBACjmqAp2Ci4sTHZci+qk -# tEAKsFk5HNVGKyWR2rFGXsd7cggZ04H5U4SV0fAL6fOE9dLvt4I7HBHLhpGdE5Uj -# Ly4NxLTG2bDAkeAVmxmd2uKWVGKym1aarDxXfv3GCN4mRX+Pn4c+py3S/6Kkt5eS -# DAIIsrzKw3Kh2SW1hCwXX/k1v4b+NH1Fjl+i/xPJspXCFuZB4aC5FLT5fgbRKqns -# WeAdn8DsrYQhT3QXLt6Nv3/dMzv7G/Cdpbdcoul8FYl+t3dmXM+SIClC3l2ae0wO -# lNrQ42yQEycuPU5OoqLT85jsZ7+4CaScfFINlO7l7Y7r/xauqHbSPQ1r3oIC+e71 -# 5s2G3ClZa3y99aYx2lnXYe1srcrIx8NAXTViiypXVn9ZGmEkfNcfDiqGQwkml5z9 -# nm3pWiBZ69adaBBbAFEjyJG4y0a76bel/4sDCVvaZzLM3TFbxVO9BQrjZRtbJZbk -# C3XArpLqZSfx53SuYdddxPX8pvcqFuEu8wcUeD05t9xNbJ4TtdAECJlEi0vvBxlm -# M5tzFXy2qZeqPMXHSQYqPgZ9jvScZ6NwznFD0+33kbzyhOSz/WuGbAu4cHZG8gKn -# lQVT4uA2Diex9DMs2WHiokNknYlLoUeWXW1QrJLpqO82TLyKTbBM/oZHAdIc0kzo -# STro9b3+vjn2809D0+SOOCVZMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq -# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x -# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv -# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 -# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG -# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG -# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg -# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC -# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03 -# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr -# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg -# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy -# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9 -# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh -# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k -# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB -# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn -# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90 -# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w -# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o -# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD -# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa -# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny -# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG -# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t -# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV -# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3 -# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG -# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl -# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb -# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l -# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6 -# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0 -# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560 -# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam -# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa -# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah -# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA -# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt -# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr -# /Xmfwb1tbWrJUnMTDXpQzTGCGgowghoGAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw -# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN -# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp -# Z25pbmcgUENBIDIwMTECEzMAAASFXpnsDlkvzdcAAAAABIUwDQYJYIZIAWUDBAIB -# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO -# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEID8/Z0hz8wCpH2YjVYR3wACO -# qi7toMi0S892RCpCiXnDMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A -# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB -# BQAEggEAR3ofVJe8H1PSnVv5GEV/iNRKzDHBYXTNKjw6gaEwywGiLnvok4fIy+o/ -# pgoyuM4RLT6jq9o/62LWZPnRCXiQiidnt9u6BtjAQFoy9Hyz39SnG3SIfcXwQU6S -# Kn6sdIdkCnp9zgCw0A1um1l9ZESP36cub7lCkog6Qd1N+d5KAMuDMHX4MybWYjva -# YmW+c3RMH4HoBd6igF/hUaz0VTf+yrdIUaBIJ9UlWTMVkwokmQ9I79IwPU5hHnRu -# Ao8D6p++BagDKmVHo4bY/ADy4GDn4nrLA09mwd0YQPDZvb3K3Z2rIABM0UdS4+lG -# c/pZsaRUT7TE8NzWXP+vWQ9bdkhNbaGCF5QwgheQBgorBgEEAYI3AwMBMYIXgDCC -# F3wGCSqGSIb3DQEHAqCCF20wghdpAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq -# hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl -# AwQCAQUABCAd+KomD6n/vMp0PpchU0Vc9uK1oIZ/s0smWP9W6KAY4QIGaSc7gduW -# GBMyMDI1MTIxMDIyNDQ0NC41NjdaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV -# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE -# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l -# cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046REMwMC0w -# NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg -# ghHqMIIHIDCCBQigAwIBAgITMwAAAgO7HlwAOGx0ygABAAACAzANBgkqhkiG9w0B -# AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE -# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD -# VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yNTAxMzAxOTQy -# NDZaFw0yNjA0MjIxOTQyNDZaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz -# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv -# cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z -# MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046REMwMC0wNUUwLUQ5NDcxJTAjBgNV -# BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB -# AQUAA4ICDwAwggIKAoICAQChl0MH5wAnOx8Uh8RtidF0J0yaFDHJYHTpPvRR16X1 -# KxGDYfT8PrcGjCLCiaOu3K1DmUIU4Rc5olndjappNuOgzwUoj43VbbJx5PFTY/a1 -# Z80tpqVP0OoKJlUkfDPSBLFgXWj6VgayRCINtLsUasy0w5gysD7ILPZuiQjace5K -# xASjKf2MVX1qfEzYBbTGNEijSQCKwwyc0eavr4Fo3X/+sCuuAtkTWissU64k8rK6 -# 0jsGRApiESdfuHr0yWAmc7jTOPNeGAx6KCL2ktpnGegLDd1IlE6Bu6BSwAIFHr7z -# OwIlFqyQuCe0SQALCbJhsT9y9iy61RJAXsU0u0TC5YYmTSbEI7g10dYx8Uj+vh9I -# nLoKYC5DpKb311bYVd0bytbzlfTRslRTJgotnfCAIGMLqEqk9/2VRGu9klJi1j9n -# VfqyYHYrMPOBXcrQYW0jmKNjOL47CaEArNzhDBia1wXdJANKqMvJ8pQe2m8/ciby -# DM+1BVZquNAov9N4tJF4ACtjX0jjXNDUMtSZoVFQH+FkWdfPWx1uBIkc97R+xRLu -# PjUypHZ5A3AALSke4TaRBvbvTBYyW2HenOT7nYLKTO4jw5Qq6cw3Z9zTKSPQ6D5l -# yiYpes5RR2MdMvJS4fCcPJFeaVOvuWFSQ/EGtVBShhmLB+5ewzFzdpf1UuJmuOQT -# TwIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFLIpWUB+EeeQ29sWe0VdzxWQGJJ9MB8G -# A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG -# Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy -# MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w -# XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy -# dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG -# A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD -# AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQCQEMbesD6TC08R0oYCdSC452AQrGf/O89G -# Q54CtgEsbxzwGDVUcmjXFcnaJSTNedBKVXkBgawRonP1LgxH4bzzVj2eWNmzGIwO -# 1FlhldAPOHAzLBEHRoSZ4pddFtaQxoabU/N1vWyICiN60It85gnF5JD4MMXyd6pS -# 8eADIi6TtjfgKPoumWa0BFQ/aEzjUrfPN1r7crK+qkmLztw/ENS7zemfyx4kGRgw -# Y1WBfFqm/nFlJDPQBicqeU3dOp9hj7WqD0Rc+/4VZ6wQjesIyCkv5uhUNy2LhNDi -# 2leYtAiIFpmjfNk4GngLvC2Tj9IrOMv20Srym5J/Fh7yWAiPeGs3yA3QapjZTtfr -# 7NfzpBIJQ4xT/ic4WGWqhGlRlVBI5u6Ojw3ZxSZCLg3vRC4KYypkh8FdIWoKirji -# dEGlXsNOo+UP/YG5KhebiudTBxGecfJCuuUspIdRhStHAQsjv/dAqWBLlhorq2OC -# aP+wFhE3WPgnnx5pflvlujocPgsN24++ddHrl3O1FFabW8m0UkDHSKCh8QTwTkYO -# wu99iExBVWlbYZRz2qOIBjL/ozEhtCB0auKhfTLLeuNGBUaBz+oZZ+X9UAECoMhk -# ETjb6YfNaI1T7vVAaiuhBoV/JCOQT+RYZrgykyPpzpmwMNFBD1vdW/29q9nkTWoE -# hcEOO0L9NzCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI -# hvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw -# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x -# MjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAy -# MDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMC -# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV -# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp -# bWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC -# AQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25Phdg -# M/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPF -# dvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6 -# GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBp -# Dco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50Zu -# yjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3E -# XzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0 -# lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1q -# GFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ -# +QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PA -# PBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkw -# EgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxG -# NSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARV -# MFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWlj -# cm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAK -# BggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC -# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX -# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v -# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI -# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j -# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG -# 9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0x -# M7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmC -# VgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449 -# xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wM -# nosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDS -# PeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2d -# Y3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxn -# GSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+Crvs -# QWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokL -# jzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL -# 6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNN -# MIICNQIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp -# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw -# b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn -# MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOkRDMDAtMDVFMC1EOTQ3MSUwIwYDVQQD -# ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQDN -# rxRX/iz6ss1lBCXG8P1LFxD0e6CBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD -# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w -# IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA7OQtPTAiGA8yMDI1MTIxMDE3MzI0 -# NVoYDzIwMjUxMjExMTczMjQ1WjB0MDoGCisGAQQBhFkKBAExLDAqMAoCBQDs5C09 -# AgEAMAcCAQACAgjlMAcCAQACAhNOMAoCBQDs5X69AgEAMDYGCisGAQQBhFkKBAIx -# KDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJKoZI -# hvcNAQELBQADggEBAFXCcBVLkxGEigIad7gAMsj2+SQdBANpzq4qPJXOu81TM3HC -# rAkCUTm3FRNc6YPdpfvl07lGlv/NHFCLyXL20d6PZ/1wlF5+WR2OvWjrktwDxYv8 -# cZqk7BrV9SB8xBe/GwVi7smKmlXhznqA6lFPO+VNfOwWcxn0H2yxEsAJKyDmgx/7 -# M8xnMTKeK8ulgSy4EoyGgFIO+nGHqxS0yaXe+OgzErkaavB1Qw7jfmm5/wlBCnwz -# 0UsbaequeL9UjA6FUw3Cc3F+3/D38BzyjJtTxjUVn+QiVWwOfikRJ2F7oZwpsJo3 -# yNIVpwJFpIV6VsqtxzaF0KQZBpS2lBGxVA17pFcxggQNMIIECQIBATCBkzB8MQsw -# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u -# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNy -# b3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAgO7HlwAOGx0ygABAAACAzAN -# BglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8G -# CSqGSIb3DQEJBDEiBCApggCahSc04fWyIz1KF4aeejwqHefyj2gzz7p9QsluFTCB -# +gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIEsD3RtxlvaTxFOZZnpQw0DksPmV -# duo5SyK9h9w++hMtMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh -# c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD -# b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw -# MTACEzMAAAIDux5cADhsdMoAAQAAAgMwIgQgwIRXpw5w7cRbWSfOFZ15Z2Nf90Hi -# Ms/hZSpx+kv4aHcwDQYJKoZIhvcNAQELBQAEggIAM+zwgqPLhBOAumqVnUO/YRh7 -# sePgzUWqveSw/J01TAD8JVXufiGmu4neLrGFki/Nz8ytun2DhJP/3xDRu39y/9Pb -# t2qKabzaoASwH95fTjHLYEp0PhqkEZ1hkaaYjVC3TAG0LgU2mrvkEjL3doD5MXu8 -# WWQGcnB0Wera/3POf4ylyQbUUnzo/Pl9qUbjPVW/JouzzDzijObLcYp7IDgIDxGL -# sVJqMgzP1ZWBWsjjx4J0YiYORUnIVKWKPXt/0O3X9VO3zDfOnWRLF8mJj+ybEnqa -# Wd8LxLJnCxpmTAjtELLgC46UB0N4GHR0+ymSba35Ciz4Kzc+7R9E1Ajy1yd2rmGR -# M2u/eAV8MvKybIzgTd9Lukk9KJ5lvzV52CuYyzHOzYgcNt/mFgvM6gfMAef3CeN0 -# EU7ECvTEYqno7krSRi6+HD+R14+7EwXbiR0E+KAB2Ppgj7GqHWKeL/Owyv0A1oEa -# 4ocdqMApLcY908U7IzNu5qo7PPas/RBsB9J52++fyZ/9RyP31IYKu8/5xI5Ef7aH -# XIopbEpuMHpHeuWlYWlfkULa5tjk4iPVCTRVsgn7IimLY/wgVOLL4ueOzZZ6aNws -# Q37w/ocvXIH/qXUllulfh5vINVYqXK3d+l0QT8LCMIxXpJSSgtcFcPJG6aSdOFRQ -# r6EOj+C9DH5MueMd9SY= -# SIG # End signature block diff --git a/src/Aspire.Cli/Resources/dotnet-install.sh b/src/Aspire.Cli/Resources/dotnet-install.sh deleted file mode 100644 index c44294628d1..00000000000 --- a/src/Aspire.Cli/Resources/dotnet-install.sh +++ /dev/null @@ -1,1887 +0,0 @@ -#!/usr/bin/env bash -# Copyright (c) .NET Foundation and contributors. All rights reserved. -# Licensed under the MIT license. See LICENSE file in the project root for full license information. -# - -# Stop script on NZEC -set -e -# Stop script if unbound variable found (use ${var:-} if intentional) -set -u -# By default cmd1 | cmd2 returns exit code of cmd2 regardless of cmd1 success -# This is causing it to fail -set -o pipefail - -# Use in the the functions: eval $invocation -invocation='say_verbose "Calling: ${yellow:-}${FUNCNAME[0]} ${green:-}$*${normal:-}"' - -# standard output may be used as a return value in the functions -# we need a way to write text on the screen in the functions so that -# it won't interfere with the return value. -# Exposing stream 3 as a pipe to standard output of the script itself -exec 3>&1 - -# Setup some colors to use. These need to work in fairly limited shells, like the Ubuntu Docker container where there are only 8 colors. -# See if stdout is a terminal -if [ -t 1 ] && command -v tput > /dev/null; then - # see if it supports colors - ncolors=$(tput colors || echo 0) - if [ -n "$ncolors" ] && [ $ncolors -ge 8 ]; then - bold="$(tput bold || echo)" - normal="$(tput sgr0 || echo)" - black="$(tput setaf 0 || echo)" - red="$(tput setaf 1 || echo)" - green="$(tput setaf 2 || echo)" - yellow="$(tput setaf 3 || echo)" - blue="$(tput setaf 4 || echo)" - magenta="$(tput setaf 5 || echo)" - cyan="$(tput setaf 6 || echo)" - white="$(tput setaf 7 || echo)" - fi -fi - -say_warning() { - printf "%b\n" "${yellow:-}dotnet_install: Warning: $1${normal:-}" >&3 -} - -say_err() { - printf "%b\n" "${red:-}dotnet_install: Error: $1${normal:-}" >&2 -} - -say() { - # using stream 3 (defined in the beginning) to not interfere with stdout of functions - # which may be used as return value - printf "%b\n" "${cyan:-}dotnet-install:${normal:-} $1" >&3 -} - -say_verbose() { - if [ "$verbose" = true ]; then - say "$1" - fi -} - -# This platform list is finite - if the SDK/Runtime has supported Linux distribution-specific assets, -# then and only then should the Linux distribution appear in this list. -# Adding a Linux distribution to this list does not imply distribution-specific support. -get_legacy_os_name_from_platform() { - eval $invocation - - platform="$1" - case "$platform" in - "centos.7") - echo "centos" - return 0 - ;; - "debian.8") - echo "debian" - return 0 - ;; - "debian.9") - echo "debian.9" - return 0 - ;; - "fedora.23") - echo "fedora.23" - return 0 - ;; - "fedora.24") - echo "fedora.24" - return 0 - ;; - "fedora.27") - echo "fedora.27" - return 0 - ;; - "fedora.28") - echo "fedora.28" - return 0 - ;; - "opensuse.13.2") - echo "opensuse.13.2" - return 0 - ;; - "opensuse.42.1") - echo "opensuse.42.1" - return 0 - ;; - "opensuse.42.3") - echo "opensuse.42.3" - return 0 - ;; - "rhel.7"*) - echo "rhel" - return 0 - ;; - "ubuntu.14.04") - echo "ubuntu" - return 0 - ;; - "ubuntu.16.04") - echo "ubuntu.16.04" - return 0 - ;; - "ubuntu.16.10") - echo "ubuntu.16.10" - return 0 - ;; - "ubuntu.18.04") - echo "ubuntu.18.04" - return 0 - ;; - "alpine.3.4.3") - echo "alpine" - return 0 - ;; - esac - return 1 -} - -get_legacy_os_name() { - eval $invocation - - local uname=$(uname) - if [ "$uname" = "Darwin" ]; then - echo "osx" - return 0 - elif [ -n "$runtime_id" ]; then - echo $(get_legacy_os_name_from_platform "${runtime_id%-*}" || echo "${runtime_id%-*}") - return 0 - else - if [ -e /etc/os-release ]; then - . /etc/os-release - os=$(get_legacy_os_name_from_platform "$ID${VERSION_ID:+.${VERSION_ID}}" || echo "") - if [ -n "$os" ]; then - echo "$os" - return 0 - fi - fi - fi - - say_verbose "Distribution specific OS name and version could not be detected: UName = $uname" - return 1 -} - -get_linux_platform_name() { - eval $invocation - - if [ -n "$runtime_id" ]; then - echo "${runtime_id%-*}" - return 0 - else - if [ -e /etc/os-release ]; then - . /etc/os-release - echo "$ID${VERSION_ID:+.${VERSION_ID}}" - return 0 - elif [ -e /etc/redhat-release ]; then - local redhatRelease=$(&1 || true) | grep -q musl -} - -get_current_os_name() { - eval $invocation - - local uname=$(uname) - if [ "$uname" = "Darwin" ]; then - echo "osx" - return 0 - elif [ "$uname" = "FreeBSD" ]; then - echo "freebsd" - return 0 - elif [ "$uname" = "Linux" ]; then - local linux_platform_name="" - linux_platform_name="$(get_linux_platform_name)" || true - - if [ "$linux_platform_name" = "rhel.6" ]; then - echo $linux_platform_name - return 0 - elif is_musl_based_distro; then - echo "linux-musl" - return 0 - elif [ "$linux_platform_name" = "linux-musl" ]; then - echo "linux-musl" - return 0 - else - echo "linux" - return 0 - fi - fi - - say_err "OS name could not be detected: UName = $uname" - return 1 -} - -machine_has() { - eval $invocation - - command -v "$1" > /dev/null 2>&1 - return $? -} - -check_min_reqs() { - local hasMinimum=false - if machine_has "curl"; then - hasMinimum=true - elif machine_has "wget"; then - hasMinimum=true - fi - - if [ "$hasMinimum" = "false" ]; then - say_err "curl (recommended) or wget are required to download dotnet. Install missing prerequisite to proceed." - return 1 - fi - return 0 -} - -# args: -# input - $1 -to_lowercase() { - #eval $invocation - - echo "$1" | tr '[:upper:]' '[:lower:]' - return 0 -} - -# args: -# input - $1 -remove_trailing_slash() { - #eval $invocation - - local input="${1:-}" - echo "${input%/}" - return 0 -} - -# args: -# input - $1 -remove_beginning_slash() { - #eval $invocation - - local input="${1:-}" - echo "${input#/}" - return 0 -} - -# args: -# root_path - $1 -# child_path - $2 - this parameter can be empty -combine_paths() { - eval $invocation - - # TODO: Consider making it work with any number of paths. For now: - if [ ! -z "${3:-}" ]; then - say_err "combine_paths: Function takes two parameters." - return 1 - fi - - local root_path="$(remove_trailing_slash "$1")" - local child_path="$(remove_beginning_slash "${2:-}")" - say_verbose "combine_paths: root_path=$root_path" - say_verbose "combine_paths: child_path=$child_path" - echo "$root_path/$child_path" - return 0 -} - -get_machine_architecture() { - eval $invocation - - if command -v uname > /dev/null; then - CPUName=$(uname -m) - case $CPUName in - armv1*|armv2*|armv3*|armv4*|armv5*|armv6*) - echo "armv6-or-below" - return 0 - ;; - armv*l) - echo "arm" - return 0 - ;; - aarch64|arm64) - if [ "$(getconf LONG_BIT)" -lt 64 ]; then - # This is 32-bit OS running on 64-bit CPU (for example Raspberry Pi OS) - echo "arm" - return 0 - fi - echo "arm64" - return 0 - ;; - s390x) - echo "s390x" - return 0 - ;; - ppc64le) - echo "ppc64le" - return 0 - ;; - loongarch64) - echo "loongarch64" - return 0 - ;; - riscv64) - echo "riscv64" - return 0 - ;; - powerpc|ppc) - echo "ppc" - return 0 - ;; - esac - fi - - # Always default to 'x64' - echo "x64" - return 0 -} - -# args: -# architecture - $1 -get_normalized_architecture_from_architecture() { - eval $invocation - - local architecture="$(to_lowercase "$1")" - - if [[ $architecture == \ ]]; then - machine_architecture="$(get_machine_architecture)" - if [[ "$machine_architecture" == "armv6-or-below" ]]; then - say_err "Architecture \`$machine_architecture\` not supported. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues" - return 1 - fi - - echo $machine_architecture - return 0 - fi - - case "$architecture" in - amd64|x64) - echo "x64" - return 0 - ;; - arm) - echo "arm" - return 0 - ;; - arm64) - echo "arm64" - return 0 - ;; - s390x) - echo "s390x" - return 0 - ;; - ppc64le) - echo "ppc64le" - return 0 - ;; - loongarch64) - echo "loongarch64" - return 0 - ;; - esac - - say_err "Architecture \`$architecture\` not supported. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues" - return 1 -} - -# args: -# version - $1 -# channel - $2 -# architecture - $3 -get_normalized_architecture_for_specific_sdk_version() { - eval $invocation - - local is_version_support_arm64="$(is_arm64_supported "$1")" - local is_channel_support_arm64="$(is_arm64_supported "$2")" - local architecture="$3"; - local osname="$(get_current_os_name)" - - if [ "$osname" == "osx" ] && [ "$architecture" == "arm64" ] && { [ "$is_version_support_arm64" = false ] || [ "$is_channel_support_arm64" = false ]; }; then - #check if rosetta is installed - if [ "$(/usr/bin/pgrep oahd >/dev/null 2>&1;echo $?)" -eq 0 ]; then - say_verbose "Changing user architecture from '$architecture' to 'x64' because .NET SDKs prior to version 6.0 do not support arm64." - echo "x64" - return 0; - else - say_err "Architecture \`$architecture\` is not supported for .NET SDK version \`$version\`. Please install Rosetta to allow emulation of the \`$architecture\` .NET SDK on this platform" - return 1 - fi - fi - - echo "$architecture" - return 0 -} - -# args: -# version or channel - $1 -is_arm64_supported() { - # Extract the major version by splitting on the dot - major_version="${1%%.*}" - - # Check if the major version is a valid number and less than 6 - case "$major_version" in - [0-9]*) - if [ "$major_version" -lt 6 ]; then - echo false - return 0 - fi - ;; - esac - - echo true - return 0 -} - -# args: -# user_defined_os - $1 -get_normalized_os() { - eval $invocation - - local osname="$(to_lowercase "$1")" - if [ ! -z "$osname" ]; then - case "$osname" in - osx | freebsd | rhel.6 | linux-musl | linux) - echo "$osname" - return 0 - ;; - macos) - osname='osx' - echo "$osname" - return 0 - ;; - *) - say_err "'$user_defined_os' is not a supported value for --os option, supported values are: osx, macos, linux, linux-musl, freebsd, rhel.6. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues." - return 1 - ;; - esac - else - osname="$(get_current_os_name)" || return 1 - fi - echo "$osname" - return 0 -} - -# args: -# quality - $1 -get_normalized_quality() { - eval $invocation - - local quality="$(to_lowercase "$1")" - if [ ! -z "$quality" ]; then - case "$quality" in - daily | preview) - echo "$quality" - return 0 - ;; - ga) - #ga quality is available without specifying quality, so normalizing it to empty - return 0 - ;; - *) - say_err "'$quality' is not a supported value for --quality option. Supported values are: daily, preview, ga. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues." - return 1 - ;; - esac - fi - return 0 -} - -# args: -# channel - $1 -get_normalized_channel() { - eval $invocation - - local channel="$(to_lowercase "$1")" - - if [[ $channel == current ]]; then - say_warning 'Value "Current" is deprecated for -Channel option. Use "STS" instead.' - fi - - if [[ $channel == release/* ]]; then - say_warning 'Using branch name with -Channel option is no longer supported with newer releases. Use -Quality option with a channel in X.Y format instead.'; - fi - - if [ ! -z "$channel" ]; then - case "$channel" in - lts) - echo "LTS" - return 0 - ;; - sts) - echo "STS" - return 0 - ;; - current) - echo "STS" - return 0 - ;; - *) - echo "$channel" - return 0 - ;; - esac - fi - - return 0 -} - -# args: -# runtime - $1 -get_normalized_product() { - eval $invocation - - local product="" - local runtime="$(to_lowercase "$1")" - if [[ "$runtime" == "dotnet" ]]; then - product="dotnet-runtime" - elif [[ "$runtime" == "aspnetcore" ]]; then - product="aspnetcore-runtime" - elif [ -z "$runtime" ]; then - product="dotnet-sdk" - fi - echo "$product" - return 0 -} - -# The version text returned from the feeds is a 1-line or 2-line string: -# For the SDK and the dotnet runtime (2 lines): -# Line 1: # commit_hash -# Line 2: # 4-part version -# For the aspnetcore runtime (1 line): -# Line 1: # 4-part version - -# args: -# version_text - stdin -get_version_from_latestversion_file_content() { - eval $invocation - - cat | tail -n 1 | sed 's/\r$//' - return 0 -} - -# args: -# install_root - $1 -# relative_path_to_package - $2 -# specific_version - $3 -is_dotnet_package_installed() { - eval $invocation - - local install_root="$1" - local relative_path_to_package="$2" - local specific_version="${3//[$'\t\r\n']}" - - local dotnet_package_path="$(combine_paths "$(combine_paths "$install_root" "$relative_path_to_package")" "$specific_version")" - say_verbose "is_dotnet_package_installed: dotnet_package_path=$dotnet_package_path" - - if [ -d "$dotnet_package_path" ]; then - return 0 - else - return 1 - fi -} - -# args: -# downloaded file - $1 -# remote_file_size - $2 -validate_remote_local_file_sizes() -{ - eval $invocation - - local downloaded_file="$1" - local remote_file_size="$2" - local file_size='' - - if [[ "$OSTYPE" == "linux-gnu"* ]]; then - file_size="$(stat -c '%s' "$downloaded_file")" - elif [[ "$OSTYPE" == "darwin"* ]]; then - # hardcode in order to avoid conflicts with GNU stat - file_size="$(/usr/bin/stat -f '%z' "$downloaded_file")" - fi - - if [ -n "$file_size" ]; then - say "Downloaded file size is $file_size bytes." - - if [ -n "$remote_file_size" ] && [ -n "$file_size" ]; then - if [ "$remote_file_size" -ne "$file_size" ]; then - say "The remote and local file sizes are not equal. The remote file size is $remote_file_size bytes and the local size is $file_size bytes. The local package may be corrupted." - else - say "The remote and local file sizes are equal." - fi - fi - - else - say "Either downloaded or local package size can not be measured. One of them may be corrupted." - fi -} - -# args: -# azure_feed - $1 -# channel - $2 -# normalized_architecture - $3 -get_version_from_latestversion_file() { - eval $invocation - - local azure_feed="$1" - local channel="$2" - local normalized_architecture="$3" - - local version_file_url=null - if [[ "$runtime" == "dotnet" ]]; then - version_file_url="$azure_feed/Runtime/$channel/latest.version" - elif [[ "$runtime" == "aspnetcore" ]]; then - version_file_url="$azure_feed/aspnetcore/Runtime/$channel/latest.version" - elif [ -z "$runtime" ]; then - version_file_url="$azure_feed/Sdk/$channel/latest.version" - else - say_err "Invalid value for \$runtime" - return 1 - fi - say_verbose "get_version_from_latestversion_file: latest url: $version_file_url" - - download "$version_file_url" || return $? - return 0 -} - -# args: -# json_file - $1 -parse_globaljson_file_for_version() { - eval $invocation - - local json_file="$1" - if [ ! -f "$json_file" ]; then - say_err "Unable to find \`$json_file\`" - return 1 - fi - - sdk_section=$(cat "$json_file" | tr -d "\r" | awk '/"sdk"/,/}/') - if [ -z "$sdk_section" ]; then - say_err "Unable to parse the SDK node in \`$json_file\`" - return 1 - fi - - sdk_list=$(echo $sdk_section | awk -F"[{}]" '{print $2}') - sdk_list=${sdk_list//[\" ]/} - sdk_list=${sdk_list//,/$'\n'} - - local version_info="" - while read -r line; do - IFS=: - while read -r key value; do - if [[ "$key" == "version" ]]; then - version_info=$value - fi - done <<< "$line" - done <<< "$sdk_list" - if [ -z "$version_info" ]; then - say_err "Unable to find the SDK:version node in \`$json_file\`" - return 1 - fi - - unset IFS; - echo "$version_info" - return 0 -} - -# args: -# azure_feed - $1 -# channel - $2 -# normalized_architecture - $3 -# version - $4 -# json_file - $5 -get_specific_version_from_version() { - eval $invocation - - local azure_feed="$1" - local channel="$2" - local normalized_architecture="$3" - local version="$(to_lowercase "$4")" - local json_file="$5" - - if [ -z "$json_file" ]; then - if [[ "$version" == "latest" ]]; then - local version_info - version_info="$(get_version_from_latestversion_file "$azure_feed" "$channel" "$normalized_architecture" false)" || return 1 - say_verbose "get_specific_version_from_version: version_info=$version_info" - echo "$version_info" | get_version_from_latestversion_file_content - return 0 - else - echo "$version" - return 0 - fi - else - local version_info - version_info="$(parse_globaljson_file_for_version "$json_file")" || return 1 - echo "$version_info" - return 0 - fi -} - -# args: -# azure_feed - $1 -# channel - $2 -# normalized_architecture - $3 -# specific_version - $4 -# normalized_os - $5 -construct_download_link() { - eval $invocation - - local azure_feed="$1" - local channel="$2" - local normalized_architecture="$3" - local specific_version="${4//[$'\t\r\n']}" - local specific_product_version="$(get_specific_product_version "$1" "$4")" - local osname="$5" - - local download_link=null - if [[ "$runtime" == "dotnet" ]]; then - download_link="$azure_feed/Runtime/$specific_version/dotnet-runtime-$specific_product_version-$osname-$normalized_architecture.tar.gz" - elif [[ "$runtime" == "aspnetcore" ]]; then - download_link="$azure_feed/aspnetcore/Runtime/$specific_version/aspnetcore-runtime-$specific_product_version-$osname-$normalized_architecture.tar.gz" - elif [ -z "$runtime" ]; then - download_link="$azure_feed/Sdk/$specific_version/dotnet-sdk-$specific_product_version-$osname-$normalized_architecture.tar.gz" - else - return 1 - fi - - echo "$download_link" - return 0 -} - -# args: -# azure_feed - $1 -# specific_version - $2 -# download link - $3 (optional) -get_specific_product_version() { - # If we find a 'productVersion.txt' at the root of any folder, we'll use its contents - # to resolve the version of what's in the folder, superseding the specified version. - # if 'productVersion.txt' is missing but download link is already available, product version will be taken from download link - eval $invocation - - local azure_feed="$1" - local specific_version="${2//[$'\t\r\n']}" - local package_download_link="" - if [ $# -gt 2 ]; then - local package_download_link="$3" - fi - local specific_product_version=null - - # Try to get the version number, using the productVersion.txt file located next to the installer file. - local download_links=($(get_specific_product_version_url "$azure_feed" "$specific_version" true "$package_download_link") - $(get_specific_product_version_url "$azure_feed" "$specific_version" false "$package_download_link")) - - for download_link in "${download_links[@]}" - do - say_verbose "Checking for the existence of $download_link" - - if machine_has "curl" - then - if ! specific_product_version=$(curl -sL --fail "${download_link}${feed_credential}" 2>&1); then - continue - else - echo "${specific_product_version//[$'\t\r\n']}" - return 0 - fi - - elif machine_has "wget" - then - specific_product_version=$(wget -qO- "${download_link}${feed_credential}" 2>&1) - if [ $? = 0 ]; then - echo "${specific_product_version//[$'\t\r\n']}" - return 0 - fi - fi - done - - # Getting the version number with productVersion.txt has failed. Try parsing the download link for a version number. - say_verbose "Failed to get the version using productVersion.txt file. Download link will be parsed instead." - specific_product_version="$(get_product_specific_version_from_download_link "$package_download_link" "$specific_version")" - echo "${specific_product_version//[$'\t\r\n']}" - return 0 -} - -# args: -# azure_feed - $1 -# specific_version - $2 -# is_flattened - $3 -# download link - $4 (optional) -get_specific_product_version_url() { - eval $invocation - - local azure_feed="$1" - local specific_version="$2" - local is_flattened="$3" - local package_download_link="" - if [ $# -gt 3 ]; then - local package_download_link="$4" - fi - - local pvFileName="productVersion.txt" - if [ "$is_flattened" = true ]; then - if [ -z "$runtime" ]; then - pvFileName="sdk-productVersion.txt" - elif [[ "$runtime" == "dotnet" ]]; then - pvFileName="runtime-productVersion.txt" - else - pvFileName="$runtime-productVersion.txt" - fi - fi - - local download_link=null - - if [ -z "$package_download_link" ]; then - if [[ "$runtime" == "dotnet" ]]; then - download_link="$azure_feed/Runtime/$specific_version/${pvFileName}" - elif [[ "$runtime" == "aspnetcore" ]]; then - download_link="$azure_feed/aspnetcore/Runtime/$specific_version/${pvFileName}" - elif [ -z "$runtime" ]; then - download_link="$azure_feed/Sdk/$specific_version/${pvFileName}" - else - return 1 - fi - else - download_link="${package_download_link%/*}/${pvFileName}" - fi - - say_verbose "Constructed productVersion link: $download_link" - echo "$download_link" - return 0 -} - -# args: -# download link - $1 -# specific version - $2 -get_product_specific_version_from_download_link() -{ - eval $invocation - - local download_link="$1" - local specific_version="$2" - local specific_product_version="" - - if [ -z "$download_link" ]; then - echo "$specific_version" - return 0 - fi - - #get filename - filename="${download_link##*/}" - - #product specific version follows the product name - #for filename 'dotnet-sdk-3.1.404-linux-x64.tar.gz': the product version is 3.1.404 - IFS='-' - read -ra filename_elems <<< "$filename" - count=${#filename_elems[@]} - if [[ "$count" -gt 2 ]]; then - specific_product_version="${filename_elems[2]}" - else - specific_product_version=$specific_version - fi - unset IFS; - echo "$specific_product_version" - return 0 -} - -# args: -# azure_feed - $1 -# channel - $2 -# normalized_architecture - $3 -# specific_version - $4 -construct_legacy_download_link() { - eval $invocation - - local azure_feed="$1" - local channel="$2" - local normalized_architecture="$3" - local specific_version="${4//[$'\t\r\n']}" - - local distro_specific_osname - distro_specific_osname="$(get_legacy_os_name)" || return 1 - - local legacy_download_link=null - if [[ "$runtime" == "dotnet" ]]; then - legacy_download_link="$azure_feed/Runtime/$specific_version/dotnet-$distro_specific_osname-$normalized_architecture.$specific_version.tar.gz" - elif [ -z "$runtime" ]; then - legacy_download_link="$azure_feed/Sdk/$specific_version/dotnet-dev-$distro_specific_osname-$normalized_architecture.$specific_version.tar.gz" - else - return 1 - fi - - echo "$legacy_download_link" - return 0 -} - -get_user_install_path() { - eval $invocation - - if [ ! -z "${DOTNET_INSTALL_DIR:-}" ]; then - echo "$DOTNET_INSTALL_DIR" - else - echo "$HOME/.dotnet" - fi - return 0 -} - -# args: -# install_dir - $1 -resolve_installation_path() { - eval $invocation - - local install_dir=$1 - if [ "$install_dir" = "" ]; then - local user_install_path="$(get_user_install_path)" - say_verbose "resolve_installation_path: user_install_path=$user_install_path" - echo "$user_install_path" - return 0 - fi - - echo "$install_dir" - return 0 -} - -# args: -# relative_or_absolute_path - $1 -get_absolute_path() { - eval $invocation - - local relative_or_absolute_path=$1 - echo "$(cd "$(dirname "$1")" && pwd -P)/$(basename "$1")" - return 0 -} - -# args: -# override - $1 (boolean, true or false) -get_cp_options() { - eval $invocation - - local override="$1" - local override_switch="" - - if [ "$override" = false ]; then - override_switch="-n" - - # create temporary files to check if 'cp -u' is supported - tmp_dir="$(mktemp -d)" - tmp_file="$tmp_dir/testfile" - tmp_file2="$tmp_dir/testfile2" - - touch "$tmp_file" - - # use -u instead of -n if it's available - if cp -u "$tmp_file" "$tmp_file2" 2>/dev/null; then - override_switch="-u" - fi - - # clean up - rm -f "$tmp_file" "$tmp_file2" - rm -rf "$tmp_dir" - fi - - echo "$override_switch" -} - -# args: -# input_files - stdin -# root_path - $1 -# out_path - $2 -# override - $3 -copy_files_or_dirs_from_list() { - eval $invocation - - local root_path="$(remove_trailing_slash "$1")" - local out_path="$(remove_trailing_slash "$2")" - local override="$3" - local override_switch="$(get_cp_options "$override")" - - cat | uniq | while read -r file_path; do - local path="$(remove_beginning_slash "${file_path#$root_path}")" - local target="$out_path/$path" - if [ "$override" = true ] || (! ([ -d "$target" ] || [ -e "$target" ])); then - mkdir -p "$out_path/$(dirname "$path")" - if [ -d "$target" ]; then - rm -rf "$target" - fi - cp -R $override_switch "$root_path/$path" "$target" - fi - done -} - -# args: -# zip_uri - $1 -get_remote_file_size() { - local zip_uri="$1" - - if machine_has "curl"; then - file_size=$(curl -sI "$zip_uri" | grep -i content-length | awk '{ num = $2 + 0; print num }') - elif machine_has "wget"; then - file_size=$(wget --spider --server-response -O /dev/null "$zip_uri" 2>&1 | grep -i 'Content-Length:' | awk '{ num = $2 + 0; print num }') - else - say "Neither curl nor wget is available on this system." - return - fi - - if [ -n "$file_size" ]; then - say "Remote file $zip_uri size is $file_size bytes." - echo "$file_size" - else - say_verbose "Content-Length header was not extracted for $zip_uri." - echo "" - fi -} - -# args: -# zip_path - $1 -# out_path - $2 -# remote_file_size - $3 -extract_dotnet_package() { - eval $invocation - - local zip_path="$1" - local out_path="$2" - local remote_file_size="$3" - - local temp_out_path="$(mktemp -d "$temporary_file_template")" - - local failed=false - tar -xzf "$zip_path" -C "$temp_out_path" > /dev/null || failed=true - - local folders_with_version_regex='^.*/[0-9]+\.[0-9]+[^/]+/' - find "$temp_out_path" -type f | grep -Eo "$folders_with_version_regex" | sort | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" false - find "$temp_out_path" -type f | grep -Ev "$folders_with_version_regex" | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" "$override_non_versioned_files" - - validate_remote_local_file_sizes "$zip_path" "$remote_file_size" - - rm -rf "$temp_out_path" - if [ -z ${keep_zip+x} ]; then - rm -f "$zip_path" && say_verbose "Temporary archive file $zip_path was removed" - fi - - if [ "$failed" = true ]; then - say_err "Extraction failed" - return 1 - fi - return 0 -} - -# args: -# remote_path - $1 -# disable_feed_credential - $2 -get_http_header() -{ - eval $invocation - local remote_path="$1" - local disable_feed_credential="$2" - - local failed=false - local response - if machine_has "curl"; then - get_http_header_curl $remote_path $disable_feed_credential || failed=true - elif machine_has "wget"; then - get_http_header_wget $remote_path $disable_feed_credential || failed=true - else - failed=true - fi - if [ "$failed" = true ]; then - say_verbose "Failed to get HTTP header: '$remote_path'." - return 1 - fi - return 0 -} - -# args: -# remote_path - $1 -# disable_feed_credential - $2 -get_http_header_curl() { - eval $invocation - local remote_path="$1" - local disable_feed_credential="$2" - - remote_path_with_credential="$remote_path" - if [ "$disable_feed_credential" = false ]; then - remote_path_with_credential+="$feed_credential" - fi - - curl_options="-I -sSL --retry 5 --retry-delay 2 --connect-timeout 15 " - curl $curl_options "$remote_path_with_credential" 2>&1 || return 1 - return 0 -} - -# args: -# remote_path - $1 -# disable_feed_credential - $2 -get_http_header_wget() { - eval $invocation - local remote_path="$1" - local disable_feed_credential="$2" - local wget_options="-q -S --spider --tries 5 " - - local wget_options_extra='' - - # Test for options that aren't supported on all wget implementations. - if [[ $(wget -h 2>&1 | grep -E 'waitretry|connect-timeout') ]]; then - wget_options_extra="--waitretry 2 --connect-timeout 15 " - else - say "wget extra options are unavailable for this environment" - fi - - remote_path_with_credential="$remote_path" - if [ "$disable_feed_credential" = false ]; then - remote_path_with_credential+="$feed_credential" - fi - - wget $wget_options $wget_options_extra "$remote_path_with_credential" 2>&1 - - return $? -} - -# args: -# remote_path - $1 -# [out_path] - $2 - stdout if not provided -download() { - eval $invocation - - local remote_path="$1" - local out_path="${2:-}" - - if [[ "$remote_path" != "http"* ]]; then - cp "$remote_path" "$out_path" - return $? - fi - - local failed=false - local attempts=0 - while [ $attempts -lt 3 ]; do - attempts=$((attempts+1)) - failed=false - if machine_has "curl"; then - downloadcurl "$remote_path" "$out_path" || failed=true - elif machine_has "wget"; then - downloadwget "$remote_path" "$out_path" || failed=true - else - say_err "Missing dependency: neither curl nor wget was found." - exit 1 - fi - - if [ "$failed" = false ] || [ $attempts -ge 3 ] || { [ -n "${http_code-}" ] && [ "${http_code}" = "404" ]; }; then - break - fi - - say "Download attempt #$attempts has failed: ${http_code-} ${download_error_msg-}" - say "Attempt #$((attempts+1)) will start in $((attempts*10)) seconds." - sleep $((attempts*10)) - done - - if [ "$failed" = true ]; then - say_verbose "Download failed: $remote_path" - return 1 - fi - return 0 -} - -# Updates global variables $http_code and $download_error_msg -downloadcurl() { - eval $invocation - unset http_code - unset download_error_msg - local remote_path="$1" - local out_path="${2:-}" - # Append feed_credential as late as possible before calling curl to avoid logging feed_credential - # Avoid passing URI with credentials to functions: note, most of them echoing parameters of invocation in verbose output. - local remote_path_with_credential="${remote_path}${feed_credential}" - local curl_options="--retry 20 --retry-delay 2 --connect-timeout 15 -sSL -f --create-dirs " - local curl_exit_code=0; - if [ -z "$out_path" ]; then - curl_output=$(curl $curl_options "$remote_path_with_credential" 2>&1) - curl_exit_code=$? - echo "$curl_output" - else - curl_output=$(curl $curl_options -o "$out_path" "$remote_path_with_credential" 2>&1) - curl_exit_code=$? - fi - - # Regression in curl causes curl with --retry to return a 0 exit code even when it fails to download a file - https://github.com/curl/curl/issues/17554 - if [ $curl_exit_code -eq 0 ] && echo "$curl_output" | grep -q "^curl: ([0-9]*) "; then - curl_exit_code=$(echo "$curl_output" | sed 's/curl: (\([0-9]*\)).*/\1/') - fi - - if [ $curl_exit_code -gt 0 ]; then - download_error_msg="Unable to download $remote_path." - # Check for curl timeout codes - if [[ $curl_exit_code == 7 || $curl_exit_code == 28 ]]; then - download_error_msg+=" Failed to reach the server: connection timeout." - else - local disable_feed_credential=false - local response=$(get_http_header_curl $remote_path $disable_feed_credential) - http_code=$( echo "$response" | awk '/^HTTP/{print $2}' | tail -1 ) - if [[ ! -z $http_code && $http_code != 2* ]]; then - download_error_msg+=" Returned HTTP status code: $http_code." - fi - fi - say_verbose "$download_error_msg" - return 1 - fi - return 0 -} - - -# Updates global variables $http_code and $download_error_msg -downloadwget() { - eval $invocation - unset http_code - unset download_error_msg - local remote_path="$1" - local out_path="${2:-}" - # Append feed_credential as late as possible before calling wget to avoid logging feed_credential - local remote_path_with_credential="${remote_path}${feed_credential}" - local wget_options="--tries 20 " - - local wget_options_extra='' - local wget_result='' - - # Test for options that aren't supported on all wget implementations. - if [[ $(wget -h 2>&1 | grep -E 'waitretry|connect-timeout') ]]; then - wget_options_extra="--waitretry 2 --connect-timeout 15 " - else - say "wget extra options are unavailable for this environment" - fi - - if [ -z "$out_path" ]; then - wget -q $wget_options $wget_options_extra -O - "$remote_path_with_credential" 2>&1 - wget_result=$? - else - wget $wget_options $wget_options_extra -O "$out_path" "$remote_path_with_credential" 2>&1 - wget_result=$? - fi - - if [[ $wget_result != 0 ]]; then - local disable_feed_credential=false - local response=$(get_http_header_wget $remote_path $disable_feed_credential) - http_code=$( echo "$response" | awk '/^ HTTP/{print $2}' | tail -1 ) - download_error_msg="Unable to download $remote_path." - if [[ ! -z $http_code && $http_code != 2* ]]; then - download_error_msg+=" Returned HTTP status code: $http_code." - # wget exit code 4 stands for network-issue - elif [[ $wget_result == 4 ]]; then - download_error_msg+=" Failed to reach the server: connection timeout." - fi - say_verbose "$download_error_msg" - return 1 - fi - - return 0 -} - -get_download_link_from_aka_ms() { - eval $invocation - - #quality is not supported for LTS or STS channel - #STS maps to current - if [[ ! -z "$normalized_quality" && ("$normalized_channel" == "LTS" || "$normalized_channel" == "STS") ]]; then - normalized_quality="" - say_warning "Specifying quality for STS or LTS channel is not supported, the quality will be ignored." - fi - - say_verbose "Retrieving primary payload URL from aka.ms for channel: '$normalized_channel', quality: '$normalized_quality', product: '$normalized_product', os: '$normalized_os', architecture: '$normalized_architecture'." - - #construct aka.ms link - aka_ms_link="https://aka.ms/dotnet" - if [ "$internal" = true ]; then - aka_ms_link="$aka_ms_link/internal" - fi - aka_ms_link="$aka_ms_link/$normalized_channel" - if [[ ! -z "$normalized_quality" ]]; then - aka_ms_link="$aka_ms_link/$normalized_quality" - fi - aka_ms_link="$aka_ms_link/$normalized_product-$normalized_os-$normalized_architecture.tar.gz" - say_verbose "Constructed aka.ms link: '$aka_ms_link'." - - #get HTTP response - #do not pass credentials as a part of the $aka_ms_link and do not apply credentials in the get_http_header function - #otherwise the redirect link would have credentials as well - #it would result in applying credentials twice to the resulting link and thus breaking it, and in echoing credentials to the output as a part of redirect link - disable_feed_credential=true - response="$(get_http_header $aka_ms_link $disable_feed_credential)" - - say_verbose "Received response: $response" - # Get results of all the redirects. - http_codes=$( echo "$response" | awk '$1 ~ /^HTTP/ {print $2}' ) - # Allow intermediate 301 redirects and tolerate proxy-injected 200s - broken_redirects=$( echo "$http_codes" | sed '$d' | grep -vE '^(301|200)$' ) - # The response may end without final code 2xx/4xx/5xx somehow, e.g. network restrictions on www.bing.com causes redirecting to bing.com fails with connection refused. - # In this case it should not exclude the last. - last_http_code=$( echo "$http_codes" | tail -n 1 ) - if ! [[ $last_http_code =~ ^(2|4|5)[0-9][0-9]$ ]]; then - broken_redirects=$( echo "$http_codes" | grep -vE '^(301|200)$' ) - fi - - # All HTTP codes are 301 (Moved Permanently), the redirect link exists. - if [[ -z "$broken_redirects" ]]; then - aka_ms_download_link=$( echo "$response" | awk '$1 ~ /^Location/{print $2}' | tail -1 | tr -d '\r') - - if [[ -z "$aka_ms_download_link" ]]; then - say_verbose "The aka.ms link '$aka_ms_link' is not valid: failed to get redirect location." - return 1 - fi - - say_verbose "The redirect location retrieved: '$aka_ms_download_link'." - return 0 - else - say_verbose "The aka.ms link '$aka_ms_link' is not valid: received HTTP code: $(echo "$broken_redirects" | paste -sd "," -)." - return 1 - fi -} - -get_feeds_to_use() -{ - feeds=( - "https://builds.dotnet.microsoft.com/dotnet" - "https://ci.dot.net/public" - ) - - if [[ -n "$azure_feed" ]]; then - feeds=("$azure_feed") - fi - - if [[ -n "$uncached_feed" ]]; then - feeds=("$uncached_feed") - fi -} - -# THIS FUNCTION MAY EXIT (if the determined version is already installed). -generate_download_links() { - - download_links=() - specific_versions=() - effective_versions=() - link_types=() - - # If generate_akams_links returns false, no fallback to old links. Just terminate. - # This function may also 'exit' (if the determined version is already installed). - generate_akams_links || return - - # Check other feeds only if we haven't been able to find an aka.ms link. - if [[ "${#download_links[@]}" -lt 1 ]]; then - for feed in ${feeds[@]} - do - # generate_regular_links may also 'exit' (if the determined version is already installed). - generate_regular_links $feed || return - done - fi - - if [[ "${#download_links[@]}" -eq 0 ]]; then - say_err "Failed to resolve the exact version number." - return 1 - fi - - say_verbose "Generated ${#download_links[@]} links." - for link_index in ${!download_links[@]} - do - say_verbose "Link $link_index: ${link_types[$link_index]}, ${effective_versions[$link_index]}, ${download_links[$link_index]}" - done -} - -# THIS FUNCTION MAY EXIT (if the determined version is already installed). -generate_akams_links() { - local valid_aka_ms_link=true; - - normalized_version="$(to_lowercase "$version")" - if [[ "$normalized_version" != "latest" ]] && [ -n "$normalized_quality" ]; then - say_err "Quality and Version options are not allowed to be specified simultaneously. See https://learn.microsoft.com/dotnet/core/tools/dotnet-install-script#options for details." - return 1 - fi - - if [[ -n "$json_file" || "$normalized_version" != "latest" ]]; then - # aka.ms links are not needed when exact version is specified via command or json file - return - fi - - get_download_link_from_aka_ms || valid_aka_ms_link=false - - if [[ "$valid_aka_ms_link" == true ]]; then - say_verbose "Retrieved primary payload URL from aka.ms link: '$aka_ms_download_link'." - say_verbose "Downloading using legacy url will not be attempted." - - download_link=$aka_ms_download_link - - #get version from the path - IFS='/' - read -ra pathElems <<< "$download_link" - count=${#pathElems[@]} - specific_version="${pathElems[count-2]}" - unset IFS; - say_verbose "Version: '$specific_version'." - - #Retrieve effective version - effective_version="$(get_specific_product_version "$azure_feed" "$specific_version" "$download_link")" - - # Add link info to arrays - download_links+=($download_link) - specific_versions+=($specific_version) - effective_versions+=($effective_version) - link_types+=("aka.ms") - - # Check if the SDK version is already installed. - if [[ "$dry_run" != true ]] && is_dotnet_package_installed "$install_root" "$asset_relative_path" "$effective_version"; then - say "$asset_name with version '$effective_version' is already installed." - exit 0 - fi - - return 0 - fi - - # if quality is specified - exit with error - there is no fallback approach - if [ ! -z "$normalized_quality" ]; then - say_err "Failed to locate the latest version in the channel '$normalized_channel' with '$normalized_quality' quality for '$normalized_product', os: '$normalized_os', architecture: '$normalized_architecture'." - say_err "Refer to: https://aka.ms/dotnet-os-lifecycle for information on .NET Core support." - return 1 - fi - say_verbose "Falling back to latest.version file approach." -} - -# THIS FUNCTION MAY EXIT (if the determined version is already installed) -# args: -# feed - $1 -generate_regular_links() { - local feed="$1" - local valid_legacy_download_link=true - - specific_version=$(get_specific_version_from_version "$feed" "$channel" "$normalized_architecture" "$version" "$json_file") || specific_version='0' - - if [[ "$specific_version" == '0' ]]; then - say_verbose "Failed to resolve the specific version number using feed '$feed'" - return - fi - - effective_version="$(get_specific_product_version "$feed" "$specific_version")" - say_verbose "specific_version=$specific_version" - - download_link="$(construct_download_link "$feed" "$channel" "$normalized_architecture" "$specific_version" "$normalized_os")" - say_verbose "Constructed primary named payload URL: $download_link" - - # Add link info to arrays - download_links+=($download_link) - specific_versions+=($specific_version) - effective_versions+=($effective_version) - link_types+=("primary") - - legacy_download_link="$(construct_legacy_download_link "$feed" "$channel" "$normalized_architecture" "$specific_version")" || valid_legacy_download_link=false - - if [ "$valid_legacy_download_link" = true ]; then - say_verbose "Constructed legacy named payload URL: $legacy_download_link" - - download_links+=($legacy_download_link) - specific_versions+=($specific_version) - effective_versions+=($effective_version) - link_types+=("legacy") - else - legacy_download_link="" - say_verbose "Could not construct a legacy_download_link; omitting..." - fi - - # Check if the SDK version is already installed. - if [[ "$dry_run" != true ]] && is_dotnet_package_installed "$install_root" "$asset_relative_path" "$effective_version"; then - say "$asset_name with version '$effective_version' is already installed." - exit 0 - fi -} - -print_dry_run() { - - say "Payload URLs:" - - for link_index in "${!download_links[@]}" - do - say "URL #$link_index - ${link_types[$link_index]}: ${download_links[$link_index]}" - done - - resolved_version=${specific_versions[0]} - repeatable_command="./$script_name --version "\""$resolved_version"\"" --install-dir "\""$install_root"\"" --architecture "\""$normalized_architecture"\"" --os "\""$normalized_os"\""" - - if [ ! -z "$normalized_quality" ]; then - repeatable_command+=" --quality "\""$normalized_quality"\""" - fi - - if [[ "$runtime" == "dotnet" ]]; then - repeatable_command+=" --runtime "\""dotnet"\""" - elif [[ "$runtime" == "aspnetcore" ]]; then - repeatable_command+=" --runtime "\""aspnetcore"\""" - fi - - repeatable_command+="$non_dynamic_parameters" - - if [ -n "$feed_credential" ]; then - repeatable_command+=" --feed-credential "\"""\""" - fi - - say "Repeatable invocation: $repeatable_command" -} - -calculate_vars() { - eval $invocation - - script_name=$(basename "$0") - normalized_architecture="$(get_normalized_architecture_from_architecture "$architecture")" - say_verbose "Normalized architecture: '$normalized_architecture'." - normalized_os="$(get_normalized_os "$user_defined_os")" - say_verbose "Normalized OS: '$normalized_os'." - normalized_quality="$(get_normalized_quality "$quality")" - say_verbose "Normalized quality: '$normalized_quality'." - normalized_channel="$(get_normalized_channel "$channel")" - say_verbose "Normalized channel: '$normalized_channel'." - normalized_product="$(get_normalized_product "$runtime")" - say_verbose "Normalized product: '$normalized_product'." - install_root="$(resolve_installation_path "$install_dir")" - say_verbose "InstallRoot: '$install_root'." - - normalized_architecture="$(get_normalized_architecture_for_specific_sdk_version "$version" "$normalized_channel" "$normalized_architecture")" - - if [[ "$runtime" == "dotnet" ]]; then - asset_relative_path="shared/Microsoft.NETCore.App" - asset_name=".NET Core Runtime" - elif [[ "$runtime" == "aspnetcore" ]]; then - asset_relative_path="shared/Microsoft.AspNetCore.App" - asset_name="ASP.NET Core Runtime" - elif [ -z "$runtime" ]; then - asset_relative_path="sdk" - asset_name=".NET Core SDK" - fi - - get_feeds_to_use -} - -install_dotnet() { - eval $invocation - local download_failed=false - local download_completed=false - local remote_file_size=0 - - mkdir -p "$install_root" - zip_path="${zip_path:-$(mktemp "$temporary_file_template")}" - say_verbose "Archive path: $zip_path" - - for link_index in "${!download_links[@]}" - do - download_link="${download_links[$link_index]}" - specific_version="${specific_versions[$link_index]}" - effective_version="${effective_versions[$link_index]}" - link_type="${link_types[$link_index]}" - - say "Attempting to download using $link_type link $download_link" - - # The download function will set variables $http_code and $download_error_msg in case of failure. - download_failed=false - download "$download_link" "$zip_path" 2>&1 || download_failed=true - - if [ "$download_failed" = true ]; then - case ${http_code-} in - 404) - say "The resource at $link_type link '$download_link' is not available." - ;; - *) - say "Failed to download $link_type link '$download_link': ${http_code-} ${download_error_msg-}" - ;; - esac - rm -f "$zip_path" 2>&1 && say_verbose "Temporary archive file $zip_path was removed" - else - download_completed=true - break - fi - done - - if [[ "$download_completed" == false ]]; then - say_err "Could not find \`$asset_name\` with version = $specific_version" - say_err "Refer to: https://aka.ms/dotnet-os-lifecycle for information on .NET Core support" - return 1 - fi - - remote_file_size="$(get_remote_file_size "$download_link")" - - say "Extracting archive from $download_link" - extract_dotnet_package "$zip_path" "$install_root" "$remote_file_size" || return 1 - - # Check if the SDK version is installed; if not, fail the installation. - # if the version contains "RTM" or "servicing"; check if a 'release-type' SDK version is installed. - if [[ $specific_version == *"rtm"* || $specific_version == *"servicing"* ]]; then - IFS='-' - read -ra verArr <<< "$specific_version" - release_version="${verArr[0]}" - unset IFS; - say_verbose "Checking installation: version = $release_version" - if is_dotnet_package_installed "$install_root" "$asset_relative_path" "$release_version"; then - say "Installed version is $effective_version" - return 0 - fi - fi - - # Check if the standard SDK version is installed. - say_verbose "Checking installation: version = $effective_version" - if is_dotnet_package_installed "$install_root" "$asset_relative_path" "$effective_version"; then - say "Installed version is $effective_version" - return 0 - fi - - # Version verification failed. More likely something is wrong either with the downloaded content or with the verification algorithm. - say_err "Failed to verify the version of installed \`$asset_name\`.\nInstallation source: $download_link.\nInstallation location: $install_root.\nReport the bug at https://github.com/dotnet/install-scripts/issues." - say_err "\`$asset_name\` with version = $effective_version failed to install with an error." - return 1 -} - -args=("$@") - -local_version_file_relative_path="/.version" -bin_folder_relative_path="" -temporary_file_template="${TMPDIR:-/tmp}/dotnet.XXXXXXXXX" - -channel="LTS" -version="Latest" -json_file="" -install_dir="" -architecture="" -dry_run=false -no_path=false -azure_feed="" -uncached_feed="" -feed_credential="" -verbose=false -runtime="" -runtime_id="" -quality="" -internal=false -override_non_versioned_files=true -non_dynamic_parameters="" -user_defined_os="" - -while [ $# -ne 0 ] -do - name="$1" - case "$name" in - -c|--channel|-[Cc]hannel) - shift - channel="$1" - ;; - -v|--version|-[Vv]ersion) - shift - version="$1" - ;; - -q|--quality|-[Qq]uality) - shift - quality="$1" - ;; - --internal|-[Ii]nternal) - internal=true - non_dynamic_parameters+=" $name" - ;; - -i|--install-dir|-[Ii]nstall[Dd]ir) - shift - install_dir="$1" - ;; - --arch|--architecture|-[Aa]rch|-[Aa]rchitecture) - shift - architecture="$1" - ;; - --os|-[Oo][SS]) - shift - user_defined_os="$1" - ;; - --shared-runtime|-[Ss]hared[Rr]untime) - say_warning "The --shared-runtime flag is obsolete and may be removed in a future version of this script. The recommended usage is to specify '--runtime dotnet'." - if [ -z "$runtime" ]; then - runtime="dotnet" - fi - ;; - --runtime|-[Rr]untime) - shift - runtime="$1" - if [[ "$runtime" != "dotnet" ]] && [[ "$runtime" != "aspnetcore" ]]; then - say_err "Unsupported value for --runtime: '$1'. Valid values are 'dotnet' and 'aspnetcore'." - if [[ "$runtime" == "windowsdesktop" ]]; then - say_err "WindowsDesktop archives are manufactured for Windows platforms only." - fi - exit 1 - fi - ;; - --dry-run|-[Dd]ry[Rr]un) - dry_run=true - ;; - --no-path|-[Nn]o[Pp]ath) - no_path=true - non_dynamic_parameters+=" $name" - ;; - --verbose|-[Vv]erbose) - verbose=true - non_dynamic_parameters+=" $name" - ;; - --azure-feed|-[Aa]zure[Ff]eed) - shift - azure_feed="$1" - non_dynamic_parameters+=" $name "\""$1"\""" - ;; - --uncached-feed|-[Uu]ncached[Ff]eed) - shift - uncached_feed="$1" - non_dynamic_parameters+=" $name "\""$1"\""" - ;; - --feed-credential|-[Ff]eed[Cc]redential) - shift - feed_credential="$1" - #feed_credential should start with "?", for it to be added to the end of the link. - #adding "?" at the beginning of the feed_credential if needed. - [[ -z "$(echo $feed_credential)" ]] || [[ $feed_credential == \?* ]] || feed_credential="?$feed_credential" - ;; - --runtime-id|-[Rr]untime[Ii]d) - shift - runtime_id="$1" - non_dynamic_parameters+=" $name "\""$1"\""" - say_warning "Use of --runtime-id is obsolete and should be limited to the versions below 2.1. To override architecture, use --architecture option instead. To override OS, use --os option instead." - ;; - --jsonfile|-[Jj][Ss]on[Ff]ile) - shift - json_file="$1" - ;; - --skip-non-versioned-files|-[Ss]kip[Nn]on[Vv]ersioned[Ff]iles) - override_non_versioned_files=false - non_dynamic_parameters+=" $name" - ;; - --keep-zip|-[Kk]eep[Zz]ip) - keep_zip=true - non_dynamic_parameters+=" $name" - ;; - --zip-path|-[Zz]ip[Pp]ath) - shift - zip_path="$1" - ;; - -?|--?|-h|--help|-[Hh]elp) - script_name="dotnet-install.sh" - echo ".NET Tools Installer" - echo "Usage:" - echo " # Install a .NET SDK of a given Quality from a given Channel" - echo " $script_name [-c|--channel ] [-q|--quality ]" - echo " # Install a .NET SDK of a specific public version" - echo " $script_name [-v|--version ]" - echo " $script_name -h|-?|--help" - echo "" - echo "$script_name is a simple command line interface for obtaining dotnet cli." - echo " Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:" - echo " - The SDK needs to be installed without user interaction and without admin rights." - echo " - The SDK installation doesn't need to persist across multiple CI runs." - echo " To set up a development environment or to run apps, use installers rather than this script. Visit https://dotnet.microsoft.com/download to get the installer." - echo "" - echo "Options:" - echo " -c,--channel Download from the channel specified, Defaults to \`$channel\`." - echo " -Channel" - echo " Possible values:" - echo " - STS - the most recent Standard Term Support release" - echo " - LTS - the most recent Long Term Support release" - echo " - 2-part version in a format A.B - represents a specific release" - echo " examples: 2.0; 1.0" - echo " - 3-part version in a format A.B.Cxx - represents a specific SDK release" - echo " examples: 5.0.1xx, 5.0.2xx." - echo " Supported since 5.0 release" - echo " Warning: Value 'Current' is deprecated for the Channel parameter. Use 'STS' instead." - echo " Note: The version parameter overrides the channel parameter when any version other than 'latest' is used." - echo " -v,--version Use specific VERSION, Defaults to \`$version\`." - echo " -Version" - echo " Possible values:" - echo " - latest - the latest build on specific channel" - echo " - 3-part version in a format A.B.C - represents specific version of build" - echo " examples: 2.0.0-preview2-006120; 1.1.0" - echo " -q,--quality Download the latest build of specified quality in the channel." - echo " -Quality" - echo " The possible values are: daily, preview, GA." - echo " Works only in combination with channel. Not applicable for STS and LTS channels and will be ignored if those channels are used." - echo " Supported since 5.0 release." - echo " Note: The version parameter overrides the channel parameter when any version other than 'latest' is used, and therefore overrides the quality." - echo " --internal,-Internal Download internal builds. Requires providing credentials via --feed-credential parameter." - echo " --feed-credential Token to access Azure feed. Used as a query string to append to the Azure feed." - echo " -FeedCredential This parameter typically is not specified." - echo " -i,--install-dir Install under specified location (see Install Location below)" - echo " -InstallDir" - echo " --architecture Architecture of dotnet binaries to be installed, Defaults to \`$architecture\`." - echo " --arch,-Architecture,-Arch" - echo " Possible values: x64, arm, arm64, s390x, ppc64le and loongarch64" - echo " --os Specifies operating system to be used when selecting the installer." - echo " Overrides the OS determination approach used by the script. Supported values: osx, linux, linux-musl, freebsd, rhel.6." - echo " In case any other value is provided, the platform will be determined by the script based on machine configuration." - echo " Not supported for legacy links. Use --runtime-id to specify platform for legacy links." - echo " Refer to: https://aka.ms/dotnet-os-lifecycle for more information." - echo " --runtime Installs a shared runtime only, without the SDK." - echo " -Runtime" - echo " Possible values:" - echo " - dotnet - the Microsoft.NETCore.App shared runtime" - echo " - aspnetcore - the Microsoft.AspNetCore.App shared runtime" - echo " --dry-run,-DryRun Do not perform installation. Display download link." - echo " --no-path, -NoPath Do not set PATH for the current process." - echo " --verbose,-Verbose Display diagnostics information." - echo " --azure-feed,-AzureFeed For internal use only." - echo " Allows using a different storage to download SDK archives from." - echo " --uncached-feed,-UncachedFeed For internal use only." - echo " Allows using a different storage to download SDK archives from." - echo " --skip-non-versioned-files Skips non-versioned files if they already exist, such as the dotnet executable." - echo " -SkipNonVersionedFiles" - echo " --jsonfile Determines the SDK version from a user specified global.json file." - echo " Note: global.json must have a value for 'SDK:Version'" - echo " --keep-zip,-KeepZip If set, downloaded file is kept." - echo " --zip-path, -ZipPath If set, downloaded file is stored at the specified path." - echo " -?,--?,-h,--help,-Help Shows this help message" - echo "" - echo "Install Location:" - echo " Location is chosen in following order:" - echo " - --install-dir option" - echo " - Environmental variable DOTNET_INSTALL_DIR" - echo " - $HOME/.dotnet" - exit 0 - ;; - *) - say_err "Unknown argument \`$name\`" - exit 1 - ;; - esac - - shift -done - -say_verbose "Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:" -say_verbose "- The SDK needs to be installed without user interaction and without admin rights." -say_verbose "- The SDK installation doesn't need to persist across multiple CI runs." -say_verbose "To set up a development environment or to run apps, use installers rather than this script. Visit https://dotnet.microsoft.com/download to get the installer.\n" - -if [ "$internal" = true ] && [ -z "$(echo $feed_credential)" ]; then - message="Provide credentials via --feed-credential parameter." - if [ "$dry_run" = true ]; then - say_warning "$message" - else - say_err "$message" - exit 1 - fi -fi - -check_min_reqs -calculate_vars -# generate_regular_links call below will 'exit' if the determined version is already installed. -generate_download_links - -if [[ "$dry_run" = true ]]; then - print_dry_run - exit 0 -fi - -install_dotnet - -bin_path="$(get_absolute_path "$(combine_paths "$install_root" "$bin_folder_relative_path")")" -if [ "$no_path" = false ]; then - say "Adding to current process PATH: \`$bin_path\`. Note: This change will be visible only when sourcing script." - export PATH="$bin_path":"$PATH" -else - say "Binaries of dotnet can be found in $bin_path" -fi - -say "Note that the script does not resolve dependencies during installation." -say "To check the list of dependencies, go to https://learn.microsoft.com/dotnet/core/install, select your operating system and check the \"Dependencies\" section." -say "Installation finished successfully." diff --git a/src/Aspire.Cli/Telemetry/TelemetryConstants.cs b/src/Aspire.Cli/Telemetry/TelemetryConstants.cs index 7603d2dd8d8..9fda412bff3 100644 --- a/src/Aspire.Cli/Telemetry/TelemetryConstants.cs +++ b/src/Aspire.Cli/Telemetry/TelemetryConstants.cs @@ -83,11 +83,6 @@ internal static class Tags /// public const string SdkMinimumRequiredVersion = "aspire.cli.sdk.minimum_required_version"; - /// - /// Tag indicating the result of the SDK installation attempt. - /// - public const string SdkInstallResult = "aspire.cli.sdk.install_result"; - /// /// Tag indicating the result of the SDK check operation. /// diff --git a/src/Aspire.Cli/Templating/DotNetTemplateFactory.cs b/src/Aspire.Cli/Templating/DotNetTemplateFactory.cs index b42d5faac23..c81def9d9d7 100644 --- a/src/Aspire.Cli/Templating/DotNetTemplateFactory.cs +++ b/src/Aspire.Cli/Templating/DotNetTemplateFactory.cs @@ -431,7 +431,7 @@ private Task ApplySingleFileTemplateWithNoExtraArgsAsync(Callbac private async Task ApplyTemplateAsync(CallbackTemplate template, TemplateInputs inputs, ParseResult parseResult, Func> extraArgsCallback, CancellationToken cancellationToken) { - if (!await SdkInstallHelper.EnsureSdkInstalledAsync(sdkInstaller, interactionService, features, telemetry, hostEnvironment, cancellationToken)) + if (!await SdkInstallHelper.EnsureSdkInstalledAsync(sdkInstaller, interactionService, telemetry, cancellationToken)) { return new TemplateResult(ExitCodeConstants.SdkNotInstalled); } diff --git a/src/Aspire.Cli/Utils/EnvironmentChecker/DotNetSdkCheck.cs b/src/Aspire.Cli/Utils/EnvironmentChecker/DotNetSdkCheck.cs index 7bb8c03dc37..17c4d765d13 100644 --- a/src/Aspire.Cli/Utils/EnvironmentChecker/DotNetSdkCheck.cs +++ b/src/Aspire.Cli/Utils/EnvironmentChecker/DotNetSdkCheck.cs @@ -17,7 +17,7 @@ public async Task> CheckAsync(Cancellation { try { - var (success, highestVersion, minimumRequiredVersion, _) = await sdkInstaller.CheckAsync(cancellationToken); + var (success, highestVersion, minimumRequiredVersion) = await sdkInstaller.CheckAsync(cancellationToken); if (!success) { diff --git a/src/Aspire.Cli/Utils/SdkCheckResult.cs b/src/Aspire.Cli/Utils/SdkCheckResult.cs index 1c5f0be6cdd..273ed41bf2c 100644 --- a/src/Aspire.Cli/Utils/SdkCheckResult.cs +++ b/src/Aspire.Cli/Utils/SdkCheckResult.cs @@ -13,11 +13,6 @@ internal enum SdkCheckResult /// AlreadyInstalled, - /// - /// The SDK check was triggered with force install enabled (for testing purposes). - /// - ForceInstalled, - /// /// The SDK is missing or does not meet the minimum required version. /// diff --git a/src/Aspire.Cli/Utils/SdkInstallHelper.cs b/src/Aspire.Cli/Utils/SdkInstallHelper.cs index 9322d56b2cc..301f3854eff 100644 --- a/src/Aspire.Cli/Utils/SdkInstallHelper.cs +++ b/src/Aspire.Cli/Utils/SdkInstallHelper.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Globalization; -using Aspire.Cli.Configuration; using Aspire.Cli.DotNet; using Aspire.Cli.Interaction; using Aspire.Cli.Resources; @@ -11,166 +10,55 @@ namespace Aspire.Cli.Utils; /// -/// Helper class for managing SDK installation UX and user interaction. +/// Helper class for managing SDK availability checking and user interaction. /// internal static class SdkInstallHelper { /// /// Ensures that the .NET SDK is installed and available, displaying an error message if it's not. - /// If the SDK is missing, prompts the user to install it automatically (if in interactive mode and feature is enabled). /// /// The SDK installer service. /// The interaction service for user communication. - /// The features service for checking if SDK installation is enabled. /// The telemetry service for tracking SDK installation operations. - /// The CLI host environment for detecting interactive capabilities. /// Cancellation token. /// True if the SDK is available, false if it's missing. public static async Task EnsureSdkInstalledAsync( IDotNetSdkInstaller sdkInstaller, IInteractionService interactionService, - IFeatures features, AspireCliTelemetry telemetry, - ICliHostEnvironment? hostEnvironment = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(sdkInstaller); ArgumentNullException.ThrowIfNull(interactionService); - ArgumentNullException.ThrowIfNull(features); ArgumentNullException.ThrowIfNull(telemetry); using var activity = telemetry.StartReportedActivity(name: TelemetryConstants.Activities.EnsureSdkInstalled); - var (success, highestInstalledVersion, minimumRequiredVersion, forceInstall) = await sdkInstaller.CheckAsync(cancellationToken); + var (success, highestInstalledVersion, minimumRequiredVersion) = await sdkInstaller.CheckAsync(cancellationToken); var detectedVersion = highestInstalledVersion ?? "(not found)"; - // Determine and record check result immediately - var checkResult = (success, forceInstall) switch - { - (true, false) => SdkCheckResult.AlreadyInstalled, - (true, true) => SdkCheckResult.ForceInstalled, - _ => SdkCheckResult.NotInstalled - }; + var checkResult = success ? SdkCheckResult.AlreadyInstalled : SdkCheckResult.NotInstalled; activity?.SetTag(TelemetryConstants.Tags.SdkDetectedVersion, detectedVersion); activity?.SetTag(TelemetryConstants.Tags.SdkMinimumRequiredVersion, minimumRequiredVersion.ToString()); activity?.SetTag(TelemetryConstants.Tags.SdkCheckResult, ToTelemetryString(checkResult)); - // Track installation outcome state - var installResult = SdkInstallResult.AlreadyInstalled; - var result = success; - - if (!success || forceInstall) + if (!success) { - // SDK is not available (or force install is requested) - // Only display error if SDK is actually missing - if (!success) - { - var sdkErrorMessage = string.Format(CultureInfo.InvariantCulture, - ErrorStrings.MinimumSdkVersionNotMet, - minimumRequiredVersion, - detectedVersion); - interactionService.DisplayError(sdkErrorMessage); - } - - // Only offer to install if: - // 1. The feature is enabled (default: false) - // 2. We support interactive input OR forceInstall is true (for testing) - var isFeatureEnabled = features.IsFeatureEnabled(KnownFeatures.DotNetSdkInstallationEnabled, defaultValue: false); - var isInteractive = hostEnvironment?.SupportsInteractiveInput == true; - - if (isFeatureEnabled && (isInteractive || forceInstall)) - { - bool shouldInstall; - - if (forceInstall) - { - // When alwaysInstallSdk is true, skip the prompt and install directly - shouldInstall = true; - interactionService.DisplayMessage(KnownEmojis.Information, - "alwaysInstallSdk is enabled - forcing SDK installation for testing purposes."); - } - else - { - // Offer to install the SDK automatically - shouldInstall = await interactionService.ConfirmAsync( - string.Format(CultureInfo.InvariantCulture, - "Would you like to install .NET SDK {0} automatically?", - minimumRequiredVersion), - defaultValue: true, - cancellationToken: cancellationToken); - } - - if (shouldInstall) - { - try - { - await interactionService.ShowStatusAsync( - string.Format(CultureInfo.InvariantCulture, - "Downloading and installing .NET SDK {0}... This may take a few minutes.", - minimumRequiredVersion), - async () => - { - await sdkInstaller.InstallAsync(cancellationToken); - return 0; // Return dummy value for ShowStatusAsync - }); - - interactionService.DisplaySuccess( - string.Format(CultureInfo.InvariantCulture, - ".NET SDK {0} has been installed successfully.", - minimumRequiredVersion)); - - installResult = SdkInstallResult.Installed; - result = true; - } - catch (Exception ex) - { - interactionService.DisplayError( - string.Format(CultureInfo.InvariantCulture, - "Failed to install .NET SDK: {0}", - ex.Message)); - - installResult = SdkInstallResult.InstallError; - result = false; - } - } - else - { - // User declined the installation - installResult = SdkInstallResult.UserDeclined; - } - } - else - { - // Installation not offered - installResult = !isFeatureEnabled - ? SdkInstallResult.FeatureNotEnabled - : SdkInstallResult.NotInteractive; - } + var sdkErrorMessage = string.Format(CultureInfo.InvariantCulture, + ErrorStrings.MinimumSdkVersionNotMet, + minimumRequiredVersion, + detectedVersion); + interactionService.DisplayError(sdkErrorMessage); } - // Record final telemetry - activity?.SetTag(TelemetryConstants.Tags.SdkInstallResult, ToTelemetryString(installResult)); - - return result; + return success; } - private static string ToTelemetryString(SdkInstallResult result) => result switch - { - SdkInstallResult.AlreadyInstalled => "already_installed", - SdkInstallResult.Installed => "installed", - SdkInstallResult.FeatureNotEnabled => "feature_not_enabled", - SdkInstallResult.NotInteractive => "not_interactive", - SdkInstallResult.UserDeclined => "user_declined", - SdkInstallResult.InstallError => "install_error", - _ => result.ToString().ToLowerInvariant() - }; - private static string ToTelemetryString(SdkCheckResult result) => result switch { SdkCheckResult.AlreadyInstalled => "already_installed", - SdkCheckResult.ForceInstalled => "force_installed", SdkCheckResult.NotInstalled => "not_installed", _ => result.ToString().ToLowerInvariant() }; diff --git a/src/Aspire.Cli/Utils/SdkInstallResult.cs b/src/Aspire.Cli/Utils/SdkInstallResult.cs deleted file mode 100644 index fde8fd24ed8..00000000000 --- a/src/Aspire.Cli/Utils/SdkInstallResult.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Aspire.Cli.Utils; - -/// -/// Result of an SDK installation attempt. -/// -internal enum SdkInstallResult -{ - /// - /// A valid SDK was already installed. - /// - AlreadyInstalled, - - /// - /// The SDK was successfully installed during this operation. - /// - Installed, - - /// - /// The SDK installation feature is not enabled. - /// - FeatureNotEnabled, - - /// - /// The CLI is not running in an interactive environment. - /// - NotInteractive, - - /// - /// The user declined the installation prompt. - /// - UserDeclined, - - /// - /// An error occurred during installation. - /// - InstallError -} diff --git a/tests/Aspire.Cli.Tests/Commands/SdkInstallerTests.cs b/tests/Aspire.Cli.Tests/Commands/SdkInstallerTests.cs index 1ae89acd61a..4319ebd574a 100644 --- a/tests/Aspire.Cli.Tests/Commands/SdkInstallerTests.cs +++ b/tests/Aspire.Cli.Tests/Commands/SdkInstallerTests.cs @@ -32,7 +32,7 @@ public async Task RunCommand_WhenSdkNotInstalled_ReturnsCorrectExitCode() { options.DotNetSdkInstallerFactory = _ => new TestDotNetSdkInstaller { - CheckAsyncCallback = _ => (false, null, "9.0.302", false) // SDK not installed + CheckAsyncCallback = _ => (false, null, "9.0.302") // SDK not installed }; // Use TestDotNetCliRunner to avoid real process execution @@ -57,7 +57,7 @@ public async Task AddCommand_WhenSdkNotInstalled_ReturnsCorrectExitCode() { options.DotNetSdkInstallerFactory = _ => new TestDotNetSdkInstaller { - CheckAsyncCallback = _ => (false, null, "9.0.302", false) // SDK not installed + CheckAsyncCallback = _ => (false, null, "9.0.302") // SDK not installed }; options.InteractionServiceFactory = _ => new TestConsoleInteractionService(); @@ -82,7 +82,7 @@ public async Task NewCommand_WhenSdkNotInstalled_OnlyShowsCliTemplates() { options.DotNetSdkInstallerFactory = _ => new TestDotNetSdkInstaller { - CheckAsyncCallback = _ => (false, null, "9.0.302", false) // SDK not installed + CheckAsyncCallback = _ => (false, null, "9.0.302") // SDK not installed }; options.InteractionServiceFactory = _ => new TestConsoleInteractionService(); @@ -119,7 +119,7 @@ public async Task PublishCommand_WhenSdkNotInstalled_ReturnsCorrectExitCode() { options.DotNetSdkInstallerFactory = _ => new TestDotNetSdkInstaller { - CheckAsyncCallback = _ => (false, null, "9.0.302", false) // SDK not installed + CheckAsyncCallback = _ => (false, null, "9.0.302") // SDK not installed }; // Use TestDotNetCliRunner to avoid real process execution @@ -157,7 +157,7 @@ public async Task DeployCommand_WhenSdkNotInstalled_ReturnsCorrectExitCode() { options.DotNetSdkInstallerFactory = _ => new TestDotNetSdkInstaller { - CheckAsyncCallback = _ => (false, null, "9.0.302", false) // SDK not installed + CheckAsyncCallback = _ => (false, null, "9.0.302") // SDK not installed }; // Use TestDotNetCliRunner to avoid real process execution @@ -183,7 +183,7 @@ public async Task ExecCommand_WhenSdkNotInstalled_ReturnsCorrectExitCode() options.EnabledFeatures = [KnownFeatures.ExecCommandEnabled]; options.DotNetSdkInstallerFactory = _ => new TestDotNetSdkInstaller { - CheckAsyncCallback = _ => (false, null, "9.0.302", false) // SDK not installed + CheckAsyncCallback = _ => (false, null, "9.0.302") // SDK not installed }; options.InteractionServiceFactory = _ => new TestConsoleInteractionService(); @@ -205,7 +205,7 @@ public async Task RunCommand_WhenSdkInstalled_ContinuesNormalExecution() { options.DotNetSdkInstallerFactory = _ => new TestDotNetSdkInstaller { - CheckAsyncCallback = _ => (true, "9.0.302", "9.0.302", false) // SDK installed + CheckAsyncCallback = _ => (true, "9.0.302", "9.0.302") // SDK installed }; // Make sure project locator doesn't find projects so it fails at the expected point options.ProjectLocatorFactory = _ => new NoProjectFileProjectLocator(); diff --git a/tests/Aspire.Cli.Tests/DotNetSdkInstallerTests.cs b/tests/Aspire.Cli.Tests/DotNetSdkInstallerTests.cs index 5df502be45e..1fc97211321 100644 --- a/tests/Aspire.Cli.Tests/DotNetSdkInstallerTests.cs +++ b/tests/Aspire.Cli.Tests/DotNetSdkInstallerTests.cs @@ -6,45 +6,20 @@ using Aspire.Cli.Configuration; using Aspire.Cli.DotNet; using Aspire.Cli.Resources; -using Aspire.Cli.Tests.TestServices; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using Semver; namespace Aspire.Cli.Tests; public class DotNetSdkInstallerTests { - private static CliExecutionContext CreateTestExecutionContext() - { - var tempPath = Path.Combine(Path.GetTempPath(), "aspire-cli-tests", Guid.NewGuid().ToString()); - var workingDirectory = new DirectoryInfo(tempPath); - var hivesDirectory = new DirectoryInfo(Path.Combine(tempPath, "hives")); - var cacheDirectory = new DirectoryInfo(Path.Combine(tempPath, "cache")); - var sdksDirectory = new DirectoryInfo(Path.Combine(tempPath, "sdks")); - var logsDirectory = new DirectoryInfo(Path.Combine(tempPath, "logs")); - - return new CliExecutionContext(workingDirectory, hivesDirectory, cacheDirectory, sdksDirectory, logsDirectory, "test.log", debugMode: false); - } - - private static ILogger CreateTestLogger() - { - return NullLogger.Instance; - } - - private static IDotNetCliRunner CreateTestDotNetCliRunner() - { - return new TestDotNetCliRunner(); - } - [Fact] public async Task CheckAsync_WhenDotNetIsAvailable_ReturnsTrue() { - var installer = new DotNetSdkInstaller(new MinimumSdkCheckFeature(), CreateEmptyConfiguration(), CreateTestExecutionContext(), CreateTestDotNetCliRunner(), CreateTestLogger()); + var installer = new DotNetSdkInstaller(new MinimumSdkCheckFeature(), CreateEmptyConfiguration()); // This test assumes the test environment has .NET SDK installed - var (success, _, _, _) = await installer.CheckAsync().DefaultTimeout(); + var (success, _, _) = await installer.CheckAsync().DefaultTimeout(); Assert.True(success); } @@ -55,10 +30,10 @@ public async Task CheckAsync_WithMinimumVersion_WhenDotNetIsAvailable_ReturnsTru var features = new TestFeatures() .SetFeature(KnownFeatures.MinimumSdkCheckEnabled, true); var configuration = CreateConfigurationWithOverride("8.0.0"); - var installer = new DotNetSdkInstaller(features, configuration, CreateTestExecutionContext(), CreateTestDotNetCliRunner(), CreateTestLogger()); + var installer = new DotNetSdkInstaller(features, configuration); // This test assumes the test environment has .NET SDK installed with a version >= 8.0.0 - var (success, _, _, _) = await installer.CheckAsync().DefaultTimeout(); + var (success, _, _) = await installer.CheckAsync().DefaultTimeout(); Assert.True(success); } @@ -69,11 +44,10 @@ public async Task CheckAsync_WithActualMinimumVersion_BehavesCorrectly() var features = new TestFeatures() .SetFeature(KnownFeatures.MinimumSdkCheckEnabled, true); var configuration = CreateConfigurationWithOverride(DotNetSdkInstaller.MinimumSdkVersion); - var installer = new DotNetSdkInstaller(features, configuration, CreateTestExecutionContext(), CreateTestDotNetCliRunner(), CreateTestLogger()); + var installer = new DotNetSdkInstaller(features, configuration); // Use the actual minimum version constant and check the behavior - // Since this test environment has 8.0.117, it should return false for 9.0.302 - var (success, _, _, _) = await installer.CheckAsync().DefaultTimeout(); + var (success, _, _) = await installer.CheckAsync().DefaultTimeout(); // Don't assert the specific result, just ensure the method doesn't throw // The behavior will depend on what SDK versions are actually installed @@ -86,10 +60,10 @@ public async Task CheckAsync_WithHighMinimumVersion_ReturnsFalse() var features = new TestFeatures() .SetFeature(KnownFeatures.MinimumSdkCheckEnabled, true); var configuration = CreateConfigurationWithOverride("99.0.0"); - var installer = new DotNetSdkInstaller(features, configuration, CreateTestExecutionContext(), CreateTestDotNetCliRunner(), CreateTestLogger()); + var installer = new DotNetSdkInstaller(features, configuration); // Use an unreasonably high version that should not exist - var (success, _, _, _) = await installer.CheckAsync().DefaultTimeout(); + var (success, _, _) = await installer.CheckAsync().DefaultTimeout(); Assert.False(success); } @@ -100,73 +74,24 @@ public async Task CheckAsync_WithInvalidMinimumVersion_ReturnsFalse() var features = new TestFeatures() .SetFeature(KnownFeatures.MinimumSdkCheckEnabled, true); var configuration = CreateConfigurationWithOverride("invalid.version"); - var installer = new DotNetSdkInstaller(features, configuration, CreateTestExecutionContext(), CreateTestDotNetCliRunner(), CreateTestLogger()); + var installer = new DotNetSdkInstaller(features, configuration); // Use an invalid version string - var (success, _, _, _) = await installer.CheckAsync().DefaultTimeout(); + var (success, _, _) = await installer.CheckAsync().DefaultTimeout(); Assert.False(success); } - [Fact] - public async Task InstallAsync_CreatesSdksDirectory() - { - var features = new TestFeatures() - .SetFeature(KnownFeatures.MinimumSdkCheckEnabled, true); - var context = CreateTestExecutionContext(); - var configuration = CreateEmptyConfiguration(); - var installer = new DotNetSdkInstaller(features, configuration, context, CreateTestDotNetCliRunner(), CreateTestLogger()); - - // Get the sdks directory path - var sdksDirectory = context.SdksDirectory.FullName; - var sdkVersion = DotNetSdkInstaller.GetEffectiveMinimumSdkVersion(configuration); - var sdkInstallPath = Path.Combine(sdksDirectory, "dotnet", sdkVersion); - - // Clean up if it exists from a previous test - if (Directory.Exists(sdkInstallPath)) - { - Directory.Delete(sdkInstallPath, recursive: true); - } - - // Note: We can't actually test the full installation in unit tests - // because it requires downloading and running install scripts. - // This test just verifies that the method exists and can be called. - // The actual installation would be tested in integration tests. - - // For now, we just verify the method signature exists and doesn't throw - // ArgumentNullException or similar for valid inputs - var installTask = installer.InstallAsync(CancellationToken.None); - - // We expect this to either succeed or fail with a network/download error, - // but not throw NotImplementedException anymore - Assert.NotNull(installTask); - } - - [Fact] - public void GetSdksDirectory_ReturnsValidPath() - { - var context = CreateTestExecutionContext(); - - // Verify the sdks directory from the execution context - var sdksDirectory = context.SdksDirectory.FullName; - - // Verify the path contains the expected components - Assert.Contains("sdks", sdksDirectory); - - // Verify it's a valid path format - Assert.False(string.IsNullOrWhiteSpace(sdksDirectory)); - } - [Fact] public async Task CheckReturnsTrueIfFeatureDisabled() { var features = new TestFeatures() .SetFeature(KnownFeatures.MinimumSdkCheckEnabled, false); var configuration = CreateConfigurationWithOverride("invalid.version"); - var installer = new DotNetSdkInstaller(features, configuration, CreateTestExecutionContext(), CreateTestDotNetCliRunner(), CreateTestLogger()); + var installer = new DotNetSdkInstaller(features, configuration); // Use an invalid version string - var (success, _, _, _) = await installer.CheckAsync().DefaultTimeout(); + var (success, _, _) = await installer.CheckAsync().DefaultTimeout(); Assert.True(success); } @@ -177,11 +102,11 @@ public async Task CheckAsync_UsesArchitectureSpecificCommand() var features = new TestFeatures() .SetFeature(KnownFeatures.MinimumSdkCheckEnabled, true); var configuration = CreateConfigurationWithOverride("8.0.0"); - var installer = new DotNetSdkInstaller(features, configuration, CreateTestExecutionContext(), CreateTestDotNetCliRunner(), CreateTestLogger()); + var installer = new DotNetSdkInstaller(features, configuration); // This test verifies that the architecture-specific command is used // Since the implementation adds --arch flag, it should still work correctly - var (success, _, _, _) = await installer.CheckAsync().DefaultTimeout(); + var (success, _, _) = await installer.CheckAsync().DefaultTimeout(); // The test should pass if the command with --arch flag works Assert.True(success); @@ -191,10 +116,10 @@ public async Task CheckAsync_UsesArchitectureSpecificCommand() public async Task CheckAsync_UsesOverrideMinimumSdkVersion_WhenConfigured() { var configuration = CreateConfigurationWithOverride("8.0.0"); - var installer = new DotNetSdkInstaller(new MinimumSdkCheckFeature(), configuration, CreateTestExecutionContext(), CreateTestDotNetCliRunner(), CreateTestLogger()); + var installer = new DotNetSdkInstaller(new MinimumSdkCheckFeature(), configuration); // The installer should use the override version instead of the constant - var (success, _, _, _) = await installer.CheckAsync().DefaultTimeout(); + var (success, _, _) = await installer.CheckAsync().DefaultTimeout(); // Should use 8.0.0 instead of 9.0.302, which should be available in test environment Assert.True(success); @@ -203,10 +128,10 @@ public async Task CheckAsync_UsesOverrideMinimumSdkVersion_WhenConfigured() [Fact] public async Task CheckAsync_UsesDefaultMinimumSdkVersion_WhenNotConfigured() { - var installer = new DotNetSdkInstaller(new MinimumSdkCheckFeature(), CreateEmptyConfiguration(), CreateTestExecutionContext(), CreateTestDotNetCliRunner(), CreateTestLogger()); + var installer = new DotNetSdkInstaller(new MinimumSdkCheckFeature(), CreateEmptyConfiguration()); // Call the parameterless method that should use the default constant - var (success, _, _, _) = await installer.CheckAsync().DefaultTimeout(); + var (success, _, _) = await installer.CheckAsync().DefaultTimeout(); // The result depends on whether 9.0.302 is installed, but the test ensures no exception is thrown Assert.True(success == true || success == false); @@ -217,11 +142,10 @@ public async Task CheckAsync_UsesMinimumSdkVersion() { var features = new TestFeatures() .SetFeature(KnownFeatures.MinimumSdkCheckEnabled, true); - var context = CreateTestExecutionContext(); - var installer = new DotNetSdkInstaller(features, CreateEmptyConfiguration(), context, CreateTestDotNetCliRunner(), CreateTestLogger()); + var installer = new DotNetSdkInstaller(features, CreateEmptyConfiguration()); // Call the parameterless method that should use the minimum SDK version - var (success, _, _, _) = await installer.CheckAsync().DefaultTimeout(); + var (success, _, _) = await installer.CheckAsync().DefaultTimeout(); // The result depends on whether 10.0.100 is installed, but the test ensures no exception is thrown Assert.True(success == true || success == false); @@ -233,10 +157,10 @@ public async Task CheckAsync_UsesOverrideVersion_WhenOverrideConfigured() var features = new TestFeatures() .SetFeature(KnownFeatures.MinimumSdkCheckEnabled, true); var configuration = CreateConfigurationWithOverride("8.0.0"); - var installer = new DotNetSdkInstaller(features, configuration, CreateTestExecutionContext(), CreateTestDotNetCliRunner(), CreateTestLogger()); + var installer = new DotNetSdkInstaller(features, configuration); // The installer should use the override version instead of the baseline constant - var (success, _, _, _) = await installer.CheckAsync().DefaultTimeout(); + var (success, _, _) = await installer.CheckAsync().DefaultTimeout(); // Should use 8.0.0 instead of 10.0.100, which should be available in test environment Assert.True(success); @@ -352,33 +276,6 @@ private static IConfiguration CreateConfigurationWithOverride(string overrideVer }) .Build(); } - - [Fact] - public void EmbeddedScripts_AreAccessible() - { - // Verify that the embedded scripts can be accessed from the assembly - var assembly = typeof(DotNetSdkInstaller).Assembly; - - var bashScriptResource = assembly.GetManifestResourceStream("Aspire.Cli.Resources.dotnet-install.sh"); - var powershellScriptResource = assembly.GetManifestResourceStream("Aspire.Cli.Resources.dotnet-install.ps1"); - - Assert.NotNull(bashScriptResource); - Assert.NotNull(powershellScriptResource); - - // Verify scripts have content - Assert.True(bashScriptResource.Length > 0, "Bash script should not be empty"); - Assert.True(powershellScriptResource.Length > 0, "PowerShell script should not be empty"); - - // Verify scripts start with expected headers - using var bashReader = new StreamReader(bashScriptResource); - var firstLine = bashReader.ReadLine(); - Assert.NotNull(firstLine); - Assert.Contains("#!/", firstLine); - - using var powershellReader = new StreamReader(powershellScriptResource); - var content = powershellReader.ReadToEnd(); - Assert.Contains("dotnet", content, StringComparison.OrdinalIgnoreCase); - } } public class MinimumSdkCheckFeature(bool enabled = true) : IFeatures diff --git a/tests/Aspire.Cli.Tests/Templating/DotNetTemplateFactoryTests.cs b/tests/Aspire.Cli.Tests/Templating/DotNetTemplateFactoryTests.cs index 9f5bfdf45f8..78f7b45af99 100644 --- a/tests/Aspire.Cli.Tests/Templating/DotNetTemplateFactoryTests.cs +++ b/tests/Aspire.Cli.Tests/Templating/DotNetTemplateFactoryTests.cs @@ -349,7 +349,7 @@ public async Task GetTemplates_WhenDotNetSdkIsUnavailable_ReturnsNoTemplates() var features = new TestFeatures(showAllTemplates: true); var sdkInstaller = new TestDotNetSdkInstaller { - CheckAsyncCallback = _ => (false, null, "10.0.100", false) + CheckAsyncCallback = _ => (false, null, "10.0.100") }; var factory = CreateTemplateFactory(features, sdkInstaller: sdkInstaller); diff --git a/tests/Aspire.Cli.Tests/TestServices/TestDotNetSdkInstaller.cs b/tests/Aspire.Cli.Tests/TestServices/TestDotNetSdkInstaller.cs index edc896e2723..40fbae930fc 100644 --- a/tests/Aspire.Cli.Tests/TestServices/TestDotNetSdkInstaller.cs +++ b/tests/Aspire.Cli.Tests/TestServices/TestDotNetSdkInstaller.cs @@ -7,20 +7,12 @@ namespace Aspire.Cli.Tests.TestServices; internal sealed class TestDotNetSdkInstaller : IDotNetSdkInstaller { - public Func? CheckAsyncCallback { get; set; } - public Func? InstallAsyncCallback { get; set; } + public Func? CheckAsyncCallback { get; set; } - public Task<(bool Success, string? HighestDetectedVersion, string MinimumRequiredVersion, bool ForceInstall)> CheckAsync(CancellationToken cancellationToken = default) + public Task<(bool Success, string? HighestDetectedVersion, string MinimumRequiredVersion)> CheckAsync(CancellationToken cancellationToken = default) { return CheckAsyncCallback != null ? Task.FromResult(CheckAsyncCallback(cancellationToken)) - : Task.FromResult<(bool Success, string? HighestDetectedVersion, string MinimumRequiredVersion, bool ForceInstall)>((true, "9.0.302", "9.0.302", false)); // Default to SDK available - } - - public Task InstallAsync(CancellationToken cancellationToken = default) - { - return InstallAsyncCallback != null - ? InstallAsyncCallback(cancellationToken) - : throw new NotImplementedException(); + : Task.FromResult<(bool Success, string? HighestDetectedVersion, string MinimumRequiredVersion)>((true, "9.0.302", "9.0.302")); // Default to SDK available } } \ No newline at end of file diff --git a/tests/Aspire.Cli.Tests/Utils/SdkInstallHelperTests.cs b/tests/Aspire.Cli.Tests/Utils/SdkInstallHelperTests.cs index efce5f14f2d..3937a2be44c 100644 --- a/tests/Aspire.Cli.Tests/Utils/SdkInstallHelperTests.cs +++ b/tests/Aspire.Cli.Tests/Utils/SdkInstallHelperTests.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Aspire.Cli.Configuration; using Aspire.Cli.Telemetry; using Aspire.Cli.Tests.Telemetry; using Aspire.Cli.Tests.TestServices; @@ -11,29 +10,6 @@ namespace Aspire.Cli.Tests.Utils; public class SdkInstallHelperTests { - private sealed class TestCliHostEnvironment(bool supportsInteractiveInput) : ICliHostEnvironment - { - public bool SupportsInteractiveInput { get; } = supportsInteractiveInput; - public bool SupportsInteractiveOutput => true; - public bool SupportsAnsi => true; - } - - private sealed class TestFeatures : IFeatures - { - private readonly Dictionary _features = new(); - - public TestFeatures SetFeature(string featureName, bool value) - { - _features[featureName] = value; - return this; - } - - public bool IsFeatureEnabled(string featureName, bool defaultValue = false) - { - return _features.TryGetValue(featureName, out var value) ? value : defaultValue; - } - } - [Fact] public async Task EnsureSdkInstalledAsync_WhenSdkAlreadyInstalled_RecordsAlreadyInstalledTelemetry() { @@ -41,16 +17,14 @@ public async Task EnsureSdkInstalledAsync_WhenSdkAlreadyInstalled_RecordsAlready var sdkInstaller = new TestDotNetSdkInstaller { - CheckAsyncCallback = _ => (true, "9.0.302", "9.0.302", false) + CheckAsyncCallback = _ => (true, "9.0.302", "9.0.302") }; var interactionService = new TestConsoleInteractionService(); - var features = new TestFeatures(); var result = await SdkInstallHelper.EnsureSdkInstalledAsync( sdkInstaller, interactionService, - features, fixture.Telemetry); Assert.True(result); @@ -60,27 +34,23 @@ public async Task EnsureSdkInstalledAsync_WhenSdkAlreadyInstalled_RecordsAlready Assert.Equal("9.0.302", tags[TelemetryConstants.Tags.SdkDetectedVersion]); Assert.Equal("9.0.302", tags[TelemetryConstants.Tags.SdkMinimumRequiredVersion]); Assert.Equal("already_installed", tags[TelemetryConstants.Tags.SdkCheckResult]); - Assert.Equal("already_installed", tags[TelemetryConstants.Tags.SdkInstallResult]); } [Fact] - public async Task EnsureSdkInstalledAsync_WhenSdkMissingAndFeatureDisabled_RecordsFeatureNotEnabledTelemetry() + public async Task EnsureSdkInstalledAsync_WhenSdkMissing_RecordsNotInstalledTelemetry() { using var fixture = new TelemetryFixture(); var sdkInstaller = new TestDotNetSdkInstaller { - CheckAsyncCallback = _ => (false, null, "9.0.302", false) + CheckAsyncCallback = _ => (false, null, "9.0.302") }; var interactionService = new TestConsoleInteractionService(); - var features = new TestFeatures() - .SetFeature(KnownFeatures.DotNetSdkInstallationEnabled, false); var result = await SdkInstallHelper.EnsureSdkInstalledAsync( sdkInstaller, interactionService, - features, fixture.Telemetry); Assert.False(result); @@ -90,30 +60,24 @@ public async Task EnsureSdkInstalledAsync_WhenSdkMissingAndFeatureDisabled_Recor Assert.Equal("(not found)", tags[TelemetryConstants.Tags.SdkDetectedVersion]); Assert.Equal("9.0.302", tags[TelemetryConstants.Tags.SdkMinimumRequiredVersion]); Assert.Equal("not_installed", tags[TelemetryConstants.Tags.SdkCheckResult]); - Assert.Equal("feature_not_enabled", tags[TelemetryConstants.Tags.SdkInstallResult]); } [Fact] - public async Task EnsureSdkInstalledAsync_WhenSdkMissingAndNotInteractive_RecordsNotInteractiveTelemetry() + public async Task EnsureSdkInstalledAsync_WhenSdkMissing_DisplaysError() { using var fixture = new TelemetryFixture(); var sdkInstaller = new TestDotNetSdkInstaller { - CheckAsyncCallback = _ => (false, "8.0.100", "9.0.302", false) + CheckAsyncCallback = _ => (false, "8.0.100", "9.0.302") }; var interactionService = new TestConsoleInteractionService(); - var features = new TestFeatures() - .SetFeature(KnownFeatures.DotNetSdkInstallationEnabled, true); - var hostEnvironment = new TestCliHostEnvironment(supportsInteractiveInput: false); var result = await SdkInstallHelper.EnsureSdkInstalledAsync( sdkInstaller, interactionService, - features, - fixture.Telemetry, - hostEnvironment); + fixture.Telemetry); Assert.False(result); Assert.NotNull(fixture.CapturedActivity); @@ -122,136 +86,5 @@ public async Task EnsureSdkInstalledAsync_WhenSdkMissingAndNotInteractive_Record Assert.Equal("8.0.100", tags[TelemetryConstants.Tags.SdkDetectedVersion]); Assert.Equal("9.0.302", tags[TelemetryConstants.Tags.SdkMinimumRequiredVersion]); Assert.Equal("not_installed", tags[TelemetryConstants.Tags.SdkCheckResult]); - Assert.Equal("not_interactive", tags[TelemetryConstants.Tags.SdkInstallResult]); - } - - [Fact] - public async Task EnsureSdkInstalledAsync_WhenUserDeclinesInstallation_RecordsUserDeclinedTelemetry() - { - using var fixture = new TelemetryFixture(); - - var sdkInstaller = new TestDotNetSdkInstaller - { - CheckAsyncCallback = _ => (false, "8.0.100", "9.0.302", false) - }; - - var interactionService = new TestConsoleInteractionService - { - ConfirmCallback = (_, _) => false // User declines - }; - var features = new TestFeatures() - .SetFeature(KnownFeatures.DotNetSdkInstallationEnabled, true); - var hostEnvironment = new TestCliHostEnvironment(supportsInteractiveInput: true); - - var result = await SdkInstallHelper.EnsureSdkInstalledAsync( - sdkInstaller, - interactionService, - features, - fixture.Telemetry, - hostEnvironment); - - Assert.False(result); - Assert.NotNull(fixture.CapturedActivity); - - var tags = fixture.CapturedActivity.Tags.ToDictionary(t => t.Key, t => t.Value); - Assert.Equal("not_installed", tags[TelemetryConstants.Tags.SdkCheckResult]); - Assert.Equal("user_declined", tags[TelemetryConstants.Tags.SdkInstallResult]); - } - - [Fact] - public async Task EnsureSdkInstalledAsync_WhenInstallationSucceeds_RecordsInstalledTelemetry() - { - using var fixture = new TelemetryFixture(); - - var sdkInstaller = new TestDotNetSdkInstaller - { - CheckAsyncCallback = _ => (false, "8.0.100", "9.0.302", false), - InstallAsyncCallback = _ => Task.CompletedTask - }; - - var interactionService = new TestConsoleInteractionService - { - ConfirmCallback = (_, _) => true // User accepts - }; - var features = new TestFeatures() - .SetFeature(KnownFeatures.DotNetSdkInstallationEnabled, true); - var hostEnvironment = new TestCliHostEnvironment(supportsInteractiveInput: true); - - var result = await SdkInstallHelper.EnsureSdkInstalledAsync( - sdkInstaller, - interactionService, - features, - fixture.Telemetry, - hostEnvironment); - - Assert.True(result); - Assert.NotNull(fixture.CapturedActivity); - - var tags = fixture.CapturedActivity.Tags.ToDictionary(t => t.Key, t => t.Value); - Assert.Equal("not_installed", tags[TelemetryConstants.Tags.SdkCheckResult]); - Assert.Equal("installed", tags[TelemetryConstants.Tags.SdkInstallResult]); - } - - [Fact] - public async Task EnsureSdkInstalledAsync_WhenInstallationFails_RecordsInstallErrorTelemetry() - { - using var fixture = new TelemetryFixture(); - - var sdkInstaller = new TestDotNetSdkInstaller - { - CheckAsyncCallback = _ => (false, "8.0.100", "9.0.302", false), - InstallAsyncCallback = _ => throw new InvalidOperationException("Installation failed") - }; - - var interactionService = new TestConsoleInteractionService - { - ConfirmCallback = (_, _) => true // User accepts - }; - var features = new TestFeatures() - .SetFeature(KnownFeatures.DotNetSdkInstallationEnabled, true); - var hostEnvironment = new TestCliHostEnvironment(supportsInteractiveInput: true); - - var result = await SdkInstallHelper.EnsureSdkInstalledAsync( - sdkInstaller, - interactionService, - features, - fixture.Telemetry, - hostEnvironment); - - Assert.False(result); - Assert.NotNull(fixture.CapturedActivity); - - var tags = fixture.CapturedActivity.Tags.ToDictionary(t => t.Key, t => t.Value); - Assert.Equal("not_installed", tags[TelemetryConstants.Tags.SdkCheckResult]); - Assert.Equal("install_error", tags[TelemetryConstants.Tags.SdkInstallResult]); - } - - [Fact] - public async Task EnsureSdkInstalledAsync_WhenForceInstall_RecordsForceInstalledTelemetry() - { - using var fixture = new TelemetryFixture(); - - var sdkInstaller = new TestDotNetSdkInstaller - { - CheckAsyncCallback = _ => (true, "9.0.302", "9.0.302", true), // forceInstall = true - InstallAsyncCallback = _ => Task.CompletedTask - }; - - var interactionService = new TestConsoleInteractionService(); - var features = new TestFeatures() - .SetFeature(KnownFeatures.DotNetSdkInstallationEnabled, true); - - var result = await SdkInstallHelper.EnsureSdkInstalledAsync( - sdkInstaller, - interactionService, - features, - fixture.Telemetry); - - Assert.True(result); - Assert.NotNull(fixture.CapturedActivity); - - var tags = fixture.CapturedActivity.Tags.ToDictionary(t => t.Key, t => t.Value); - Assert.Equal("force_installed", tags[TelemetryConstants.Tags.SdkCheckResult]); - Assert.Equal("installed", tags[TelemetryConstants.Tags.SdkInstallResult]); } }