From fc62eecf4c0cabb4342832f4fb66a2ec2db20146 Mon Sep 17 00:00:00 2001 From: Matt Mitchell Date: Tue, 11 Jan 2022 14:40:43 -0800 Subject: [PATCH] Revert "Search archives in multiple feeds (#233)" This reverts commit 75b3a2f049438a063ae84f72a80a3623450a3cf0. --- src/dotnet-install.ps1 | 500 ++++++++--------- src/dotnet-install.sh | 520 ++++++++---------- ...ivenThatIWantToInstallDotnetFromAScript.cs | 143 +---- .../Utils/CommandResultAssertions.cs | 4 +- 4 files changed, 455 insertions(+), 712 deletions(-) diff --git a/src/dotnet-install.ps1 b/src/dotnet-install.ps1 index 913cbcb3c9..8f81eddba2 100644 --- a/src/dotnet-install.ps1 +++ b/src/dotnet-install.ps1 @@ -66,13 +66,11 @@ Displays diagnostics information. .PARAMETER AzureFeed Default: https://dotnetcli.azureedge.net/dotnet - For internal use only. - Allows using a different storage to download SDK archives from. - This parameter is only used if $NoCdn is false. + This parameter typically is not changed by the user. + It allows changing the URL for the Azure feed used by this installer. .PARAMETER UncachedFeed - For internal use only. - Allows using a different storage to download SDK archives from. - This parameter is only used if $NoCdn is true. + This parameter typically is not changed by the user. + It allows changing the URL for the Uncached feed used by this installer. .PARAMETER ProxyAddress If set, the installer will use the proxy when making web requests .PARAMETER ProxyUseDefaultCredentials @@ -103,11 +101,11 @@ param( [string]$Architecture="", [string]$Runtime, [Obsolete("This parameter may be removed in a future version of this script. The recommended alternative is '-Runtime dotnet'.")] - [Parameter(DontShow=$true)][switch]$SharedRuntime, + [switch]$SharedRuntime, [switch]$DryRun, [switch]$NoPath, - [Parameter(DontShow=$true)][string]$AzureFeed, - [Parameter(DontShow=$true)][string]$UncachedFeed, + [string]$AzureFeed="https://dotnetcli.azureedge.net/dotnet", + [string]$UncachedFeed="https://dotnetcli.blob.core.windows.net/dotnet", [string]$FeedCredential, [string]$ProxyAddress, [switch]$ProxyUseDefaultCredentials, @@ -121,6 +119,20 @@ Set-StrictMode -Version Latest $ErrorActionPreference="Stop" $ProgressPreference="SilentlyContinue" +if ($NoCdn) { + $AzureFeed = $UncachedFeed +} + +$BinFolderRelativePath="" + +if ($SharedRuntime -and (-not $Runtime)) { + $Runtime = "dotnet" +} + +# example path with regex: shared/1.0.0-beta-12345/somepath +$VersionRegEx="/\d+\.\d+[^/]+/" +$OverrideNonVersionedFiles = !$SkipNonVersionedFiles + function Say($str) { try { Write-Host "dotnet-install: $str" @@ -195,7 +207,9 @@ function Get-Machine-Architecture() { # 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 ) { + + if( $ENV:PROCESSOR_ARCHITEW6432 -ne $null ) + { return $ENV:PROCESSOR_ARCHITEW6432 } @@ -205,11 +219,8 @@ function Get-Machine-Architecture() { function Get-CLIArchitecture-From-Architecture([string]$Architecture) { Say-Invocation $MyInvocation - if ($Architecture -eq "") { - $Architecture = Get-Machine-Architecture - } - switch ($Architecture.ToLowerInvariant()) { + { $_ -eq "" } { return Get-CLIArchitecture-From-Architecture $(Get-Machine-Architecture) } { ($_ -eq "amd64") -or ($_ -eq "x64") } { return "x64" } { $_ -eq "x86" } { return "x86" } { $_ -eq "arm" } { return "arm" } @@ -218,25 +229,7 @@ function Get-CLIArchitecture-From-Architecture([string]$Architecture) { } } -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 @@ -289,7 +282,7 @@ function Get-NormalizedProduct([string]$Runtime) { # 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) { +function Get-Version-Info-From-Version-Text([string]$VersionText) { Say-Invocation $MyInvocation $Data = -split $VersionText @@ -328,11 +321,7 @@ function GetHTTPResponse([Uri] $Uri, [bool]$HeaderOnly, [bool]$DisableRedirect, # 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 - } + $ProxyAddress = $DefaultProxy.GetProxy($Uri).OriginalString $ProxyUseDefaultCredentials = $true } } catch { @@ -435,21 +424,21 @@ function GetHTTPResponse([Uri] $Uri, [bool]$HeaderOnly, [bool]$DisableRedirect, } } -function Get-Version-From-LatestVersion-File([string]$AzureFeed, [string]$Channel) { +function Get-Latest-Version-Info([string]$AzureFeed, [string]$Channel) { Say-Invocation $MyInvocation $VersionFileUrl = $null if ($Runtime -eq "dotnet") { - $VersionFileUrl = "$AzureFeed/Runtime/$Channel/latest.version" + $VersionFileUrl = "$UncachedFeed/Runtime/$Channel/latest.version" } elseif ($Runtime -eq "aspnetcore") { - $VersionFileUrl = "$AzureFeed/aspnetcore/Runtime/$Channel/latest.version" + $VersionFileUrl = "$UncachedFeed/aspnetcore/Runtime/$Channel/latest.version" } elseif ($Runtime -eq "windowsdesktop") { - $VersionFileUrl = "$AzureFeed/WindowsDesktop/$Channel/latest.version" + $VersionFileUrl = "$UncachedFeed/WindowsDesktop/$Channel/latest.version" } elseif (-not $Runtime) { - $VersionFileUrl = "$AzureFeed/Sdk/$Channel/latest.version" + $VersionFileUrl = "$UncachedFeed/Sdk/$Channel/latest.version" } else { throw "Invalid value for `$Runtime" @@ -461,7 +450,7 @@ function Get-Version-From-LatestVersion-File([string]$AzureFeed, [string]$Channe $Response = GetHTTPResponse -Uri $VersionFileUrl } catch { - Say-Verbose "Failed to download latest.version file." + Say-Error "Could not resolve version information." throw } $StringContent = $Response.Content.ReadAsStringAsync().Result @@ -473,7 +462,7 @@ function Get-Version-From-LatestVersion-File([string]$AzureFeed, [string]$Channe default { throw "``$Response.Content.Headers.ContentType`` is an unknown .version file content type." } } - $VersionInfo = Get-Version-From-LatestVersion-File-Content $VersionText + $VersionInfo = Get-Version-Info-From-Version-Text $VersionText return $VersionInfo } @@ -520,7 +509,7 @@ function Get-Specific-Version-From-Version([string]$AzureFeed, [string]$Channel, if (-not $JSonFile) { if ($Version.ToLowerInvariant() -eq "latest") { - $LatestVersionInfo = Get-Version-From-LatestVersion-File -AzureFeed $AzureFeed -Channel $Channel + $LatestVersionInfo = Get-Latest-Version-Info -AzureFeed $AzureFeed -Channel $Channel return $LatestVersionInfo.Version } else { @@ -733,8 +722,7 @@ function Get-Absolute-Path([string]$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+[^/]+/") + $match = [regex]::match($path, $VersionRegEx) if ($match.Success) { return $entry.FullName.Substring(0, $match.Index + $match.Length) } @@ -748,7 +736,7 @@ function Get-List-Of-Directories-And-Versions-To-Unpack-From-Dotnet-Package([Sys $ret = @() foreach ($entry in $Zip.Entries) { $dir = Get-Path-Prefix-With-Version $entry.FullName - if ($null -ne $dir) { + if ($dir -ne $null) { $path = Get-Absolute-Path $(Join-Path -Path $OutPath -ChildPath $dir) if (-Not (Test-Path $path -PathType Container)) { $ret += $dir @@ -789,7 +777,7 @@ function Extract-Dotnet-Package([string]$ZipPath, [string]$OutPath) { foreach ($entry in $Zip.Entries) { $PathWithVersion = Get-Path-Prefix-With-Version $entry.FullName - if (($null -eq $PathWithVersion) -Or ($DirectoriesToUnpack -contains $PathWithVersion)) { + if (($PathWithVersion -eq $null) -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)) @@ -801,7 +789,7 @@ function Extract-Dotnet-Package([string]$ZipPath, [string]$OutPath) { } } finally { - if ($null -ne $Zip) { + if ($Zip -ne $null) { $Zip.Dispose() } } @@ -830,7 +818,7 @@ function DownloadFile($Source, [string]$OutPath) { $File.Close() } finally { - if ($null -ne $Stream) { + if ($Stream -ne $null) { $Stream.Dispose() } } @@ -853,8 +841,8 @@ function SafeRemoveFile($Path) { } } -function Prepend-Sdk-InstallRoot-To-Path([string]$InstallRoot) { - $BinPath = Get-Absolute-Path $(Join-Path -Path $InstallRoot -ChildPath "") +function Prepend-Sdk-InstallRoot-To-Path([string]$InstallRoot, [string]$BinFolderRelativePath) { + $BinPath = Get-Absolute-Path $(Join-Path -Path $InstallRoot -ChildPath $BinFolderRelativePath) if (-Not $NoPath) { $SuffixedBinPath = "$BinPath;" if (-Not $env:path.Contains($SuffixedBinPath)) { @@ -869,36 +857,6 @@ function Prepend-Sdk-InstallRoot-To-Path([string]$InstallRoot) { } } -function PrintDryRunOutput($Invocation, [string] $DownloadLink, [string] $LegacyDownloadLink, [string] $SpecificVersion, [string] $EffectiveVersion) -{ - Say "Payload URLs:" - Say "Primary named payload URL: ${DownloadLink}" - if ($LegacyDownloadLink) { - Say "Legacy named payload URL: ${LegacyDownloadLink}" - } - $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 @@ -972,281 +930,279 @@ function Get-AkaMSDownloadLink([string]$Channel, [string]$Quality, [bool]$Intern } -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 +Say "Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:" +Say "- The SDK needs to be installed without user interaction and without admin rights." +Say "- The SDK installation doesn't need to persist across multiple CI runs." +Say "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 ($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 +} + +$CLIArchitecture = Get-CLIArchitecture-From-Architecture $Architecture +$NormalizedQuality = Get-NormalizedQuality $Quality +Say-Verbose "Normalized quality: '$NormalizedQuality'" +$NormalizedChannel = Get-NormalizedChannel $Channel +Say-Verbose "Normalized channel: '$NormalizedChannel'" +$NormalizedProduct = Get-NormalizedProduct $Runtime +Say-Verbose "Normalized product: '$NormalizedProduct'" +$DownloadLink = $null + +#try to get download location from aka.ms link +#not applicable when exact version is specified via command or json file +if ([string]::IsNullOrEmpty($JSonFile) -and ($Version -eq "latest")) { + $AkaMsDownloadLink = Get-AkaMSDownloadLink -Channel $NormalizedChannel -Quality $NormalizedQuality -Internal $Internal -Product $NormalizedProduct -Architecture $CLIArchitecture 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 "Failed to locate the latest version in the channel '$NormalizedChannel' with '$NormalizedQuality' quality for '$NormalizedProduct', os: 'win', architecture: '$CLIArchitecture'." 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'." + $DownloadLink = $AkaMsDownloadLink Say-Verbose "Downloading using legacy url will not be attempted." + $LegacyDownloadLink = $null #get version from the path - $pathParts = $AkaMsDownloadLink.Split('/') + $pathParts = $DownloadLink.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) + Say-Error "Failed to extract the version from download link '$DownloadLink'." } #retrieve effective (product) version - $EffectiveVersion = Get-Product-Version -SpecificVersion $SpecificVersion -PackageDownloadLink $AkaMsDownloadLink + $EffectiveVersion = Get-Product-Version -AzureFeed $AzureFeed -SpecificVersion $SpecificVersion -PackageDownloadLink $DownloadLink Say-Verbose "Product version: '$EffectiveVersion'." - - return ($AkaMsDownloadLink, $SpecificVersion, $EffectiveVersion); } } -function Get-Feeds-To-Use() -{ - $feeds = @( - "https://dotnetcli.azureedge.net/dotnet", - "https://dotnetbuilds.azureedge.net/public" - ) +if ([string]::IsNullOrEmpty($DownloadLink)) { + $SpecificVersion = Get-Specific-Version-From-Version -AzureFeed $AzureFeed -Channel $Channel -Version $Version -JSonFile $JSonFile + $DownloadLink, $EffectiveVersion = Get-Download-Link -AzureFeed $AzureFeed -SpecificVersion $SpecificVersion -CLIArchitecture $CLIArchitecture + $LegacyDownloadLink = Get-LegacyDownload-Link -AzureFeed $AzureFeed -SpecificVersion $SpecificVersion -CLIArchitecture $CLIArchitecture +} - if (-not [string]::IsNullOrEmpty($AzureFeed)) { - $feeds = @($AzureFeed) - } - if ($NoCdn) { - $feeds = @( - "https://dotnetcli.blob.core.windows.net/dotnet", - "https://dotnetbuilds.blob.core.windows.net/public" - ) +$InstallRoot = Resolve-Installation-Path $InstallDir +Say-Verbose "InstallRoot: $InstallRoot" +$ScriptName = $MyInvocation.MyCommand.Name - if (-not [string]::IsNullOrEmpty($UncachedFeed)) { - $feeds = @($UncachedFeed) - } +if ($DryRun) { + Say "Payload URLs:" + Say "Primary named payload URL: ${DownloadLink}" + if ($LegacyDownloadLink) { + Say "Legacy named payload URL: ${LegacyDownloadLink}" } - - return $feeds -} - -function Resolve-AssetName-And-RelativePath([string] $Runtime) { - + $RepeatableCommand = ".\$ScriptName -Version `"$SpecificVersion`" -InstallDir `"$InstallRoot`" -Architecture `"$CLIArchitecture`"" if ($Runtime -eq "dotnet") { - $assetName = ".NET Core Runtime" - $dotnetPackageRelativePath = "shared\Microsoft.NETCore.App" + $RepeatableCommand+=" -Runtime `"dotnet`"" } 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" + $RepeatableCommand+=" -Runtime `"aspnetcore`"" } - return ($assetName, $dotnetPackageRelativePath) -} - -function Prepare-Install-Directory { - New-Item -ItemType Directory -Force -Path $InstallRoot | Out-Null + if (-not [string]::IsNullOrEmpty($NormalizedQuality)) + { + $RepeatableCommand+=" -Quality `"$NormalizedQuality`"" + } - $installDrive = $((Get-Item $InstallRoot -Force).PSDrive.Name); - $diskInfo = $null - try{ - $diskInfo = Get-PSDrive -Name $installDrive + foreach ($key in $MyInvocation.BoundParameters.Keys) { + if (-not (@("Architecture","Channel","DryRun","InstallDir","Runtime","SharedRuntime","Version","Quality","FeedCredential") -contains $key)) { + $RepeatableCommand+=" -$key `"$($MyInvocation.BoundParameters[$key])`"" + } } - catch{ - Say-Warning "Failed to check the disk space. Installation will continue, but it may fail if you do not have enough disk space." + if ($MyInvocation.BoundParameters.Keys -contains "FeedCredential") { + $RepeatableCommand+=" -FeedCredential `"`"" } - - if ( ($null -ne $diskInfo) -and ($diskInfo.Free / 1MB -le 100)) { - throw "There is not enough disk space on drive ${installDrive}:" + 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'" } -} - -Say "Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:" -Say "- The SDK needs to be installed without user interaction and without admin rights." -Say "- The SDK installation doesn't need to persist across multiple CI runs." -Say "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" + return } -$OverrideNonVersionedFiles = !$SkipNonVersionedFiles - -$CLIArchitecture = Get-CLIArchitecture-From-Architecture $Architecture -$NormalizedQuality = Get-NormalizedQuality $Quality -Say-Verbose "Normalized quality: '$NormalizedQuality'" -$NormalizedChannel = Get-NormalizedChannel $Channel -Say-Verbose "Normalized channel: '$NormalizedChannel'" -$NormalizedProduct = Get-NormalizedProduct $Runtime -Say-Verbose "Normalized product: '$NormalizedProduct'" -$FeedCredential = ValidateFeedCredential $FeedCredential - -$InstallRoot = Resolve-Installation-Path $InstallDir -Say-Verbose "InstallRoot: $InstallRoot" -$ScriptName = $MyInvocation.MyCommand.Name -($assetName, $dotnetPackageRelativePath) = Resolve-AssetName-And-RelativePath -Runtime $Runtime - -$feeds = Get-Feeds-To-Use -$DownloadLinks = @() - -# 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 ($DryRun) { - PrintDryRunOutput $MyInvocation $DownloadLink $null $SpecificVersion $EffectiveVersion - return - } - - Say-Verbose "Checking if the version $EffectiveVersion is already installed" - if (Is-Dotnet-Package-Installed -InstallRoot $InstallRoot -RelativePathToPackage $dotnetPackageRelativePath -SpecificVersion $EffectiveVersion) - { - Say "$assetName version $EffectiveVersion is already installed." - Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot - return - } - } +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" } -# 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) +if ($SpecificVersion -ne $EffectiveVersion) { - 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 $DownloadLink with version $EffectiveVersion" - } - - if ($DryRun) { - PrintDryRunOutput $MyInvocation $DownloadLink $LegacyDownloadLink $SpecificVersion $EffectiveVersion - return - } - - Say-Verbose "Checking if the version $EffectiveVersion is already installed" - if (Is-Dotnet-Package-Installed -InstallRoot $InstallRoot -RelativePathToPackage $dotnetPackageRelativePath -SpecificVersion $EffectiveVersion) - { - Say "$assetName version $EffeciveVersion is already installed." - Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot - return - } - } - catch - { - Say-Verbose "Failed to acquire download links from feed $feed. Exception: $_" - } - } + Say "Performing installation checks for effective version: $EffectiveVersion" + $SpecificVersion = $EffectiveVersion } -if ($DownloadLinks.count -eq 0) { - throw "Failed to resolve the exact version number." +# Check if the SDK version is already installed. +$isAssetInstalled = Is-Dotnet-Package-Installed -InstallRoot $InstallRoot -RelativePathToPackage $dotnetPackageRelativePath -SpecificVersion $SpecificVersion +if ($isAssetInstalled) { + Say "$assetName version $SpecificVersion is already installed." + Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot -BinFolderRelativePath $BinFolderRelativePath + 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 "Failed to check the disk space. Installation will continue, but it may fail if you do not have enough disk space." +} -Prepare-Install-Directory +if ( ($diskInfo -ne $null) -and ($diskInfo.Free / 1MB -le 100)) { + throw "There is not enough disk space on drive ${installDrive}:" +} $ZipPath = [System.IO.Path]::combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) Say-Verbose "Zip path: $ZipPath" -$DownloadSucceeded = $false -$DownloadedLink = $null -$ErrorMessages = @() +$DownloadFailed = $false -foreach ($link in $DownloadLinks) -{ - Say-Verbose "Downloading `"$($link.type)`" link $($link.downloadLink)" +$PrimaryDownloadStatusCode = 0 +$LegacyDownloadStatusCode = 0 - try { - DownloadFile -Source $link.downloadLink -OutPath $ZipPath - Say-Verbose "Download succeeded." - $DownloadSucceeded = $true - $DownloadedLink = $link - break +$PrimaryDownloadFailedMsg = "" +$LegacyDownloadFailedMsg = "" + +Say "Downloading primary link $DownloadLink" +try { + DownloadFile -Source $DownloadLink -OutPath $ZipPath +} +catch { + if ($PSItem.Exception.Data.Contains("StatusCode")) { + $PrimaryDownloadStatusCode = $PSItem.Exception.Data["StatusCode"] } - 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 - } + if ($PSItem.Exception.Data.Contains("ErrorMessage")) { + $PrimaryDownloadFailedMsg = $PSItem.Exception.Data["ErrorMessage"] + } else { + $PrimaryDownloadFailedMsg = $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" + if ($PrimaryDownloadStatusCode -eq 404) { + Say "The resource at $DownloadLink is not available." + } else { + Say $PSItem.Exception.Message } - # This link failed. Clean up before trying the next one. SafeRemoveFile -Path $ZipPath -} -if (-not $DownloadSucceeded) { - foreach ($ErrorMessage in $ErrorMessages) { - Say-Error $ErrorMessages + if ($LegacyDownloadLink) { + $DownloadLink = $LegacyDownloadLink + $ZipPath = [System.IO.Path]::combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) + Say-Verbose "Legacy zip path: $ZipPath" + Say "Downloading legacy link $DownloadLink" + try { + DownloadFile -Source $DownloadLink -OutPath $ZipPath + } + catch { + if ($PSItem.Exception.Data.Contains("StatusCode")) { + $LegacyDownloadStatusCode = $PSItem.Exception.Data["StatusCode"] + } + + if ($PSItem.Exception.Data.Contains("ErrorMessage")) { + $LegacyDownloadFailedMsg = $PSItem.Exception.Data["ErrorMessage"] + } else { + $LegacyDownloadFailedMsg = $PSItem.Exception.Message + } + + if ($LegacyDownloadStatusCode -eq 404) { + Say "The resource at $DownloadLink is not available." + } else { + Say $PSItem.Exception.Message + } + + SafeRemoveFile -Path $ZipPath + $DownloadFailed = $true + } } + else { + $DownloadFailed = $true + } +} - throw "Could not find `"$assetName`" with version = $($DownloadLinks[0].effectiveVersion)`nRefer to: https://aka.ms/dotnet-os-lifecycle for information on .NET support" +if ($DownloadFailed) { + if (($PrimaryDownloadStatusCode -eq 404) -and ((-not $LegacyDownloadLink) -or ($LegacyDownloadStatusCode -eq 404))) { + throw "Could not find `"$assetName`" with version = $SpecificVersion`nRefer to: https://aka.ms/dotnet-os-lifecycle for information on .NET Core support" + } else { + # 404-NotFound is an expected response if it goes from only one of the links, do not show that error. + # If primary path is available (not 404-NotFound) then show the primary error else show the legacy error. + if ($PrimaryDownloadStatusCode -ne 404) { + throw "Could not download `"$assetName`" with version = $SpecificVersion`r`n$PrimaryDownloadFailedMsg" + } + if (($LegacyDownloadLink) -and ($LegacyDownloadStatusCode -ne 404)) { + throw "Could not download `"$assetName`" with version = $SpecificVersion`r`n$LegacyDownloadFailedMsg" + } + throw "Could not download `"$assetName`" with version = $SpecificVersion" + } } -Say "Extracting the archive." +Say "Extracting zip from $DownloadLink" 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] +if ($SpecificVersion -Match "rtm" -or $SpecificVersion -Match "servicing") { + $ReleaseVersion = $SpecificVersion.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 + Say-Verbose "Checking installation: version = $SpecificVersion" + $isAssetInstalled = Is-Dotnet-Package-Installed -InstallRoot $InstallRoot -RelativePathToPackage $dotnetPackageRelativePath -SpecificVersion $SpecificVersion } # 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." + Say-Error "Failed to verify the version of installed `"$assetName`".`nInstallation source: $DownloadLink.`nInstallation location: $InstallRoot.`nReport the bug at https://github.com/dotnet/install-scripts/issues." + throw "`"$assetName`" with version = $SpecificVersion failed to install with an unknown error." } SafeRemoveFile -Path $ZipPath -Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot +Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot -BinFolderRelativePath $BinFolderRelativePath Say "Note that the script does not resolve dependencies during installation." Say "To check the list of dependencies, go to https://docs.microsoft.com/dotnet/core/install/windows#dependencies" diff --git a/src/dotnet-install.sh b/src/dotnet-install.sh index b80ff63934..9b46690241 100755 --- a/src/dotnet-install.sh +++ b/src/dotnet-install.sh @@ -135,31 +135,6 @@ get_legacy_os_name_from_platform() { 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 @@ -221,6 +196,31 @@ get_current_os_name() { 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 +} + machine_has() { eval $invocation @@ -228,6 +228,7 @@ machine_has() { return $? } + check_min_reqs() { local hasMinimum=false if machine_has "curl"; then @@ -320,13 +321,11 @@ get_normalized_architecture_from_architecture() { eval $invocation local architecture="$(to_lowercase "$1")" - - if [[ $architecture == \ ]]; then - echo "$(get_machine_architecture)" - return 0 - fi - case "$architecture" in + \) + echo "$(get_normalized_architecture_from_architecture "$(get_machine_architecture)")" + return 0 + ;; amd64|x64) echo "x64" return 0 @@ -426,7 +425,6 @@ get_normalized_channel() { get_normalized_product() { eval $invocation - local product="" local runtime="$(to_lowercase "$1")" if [[ "$runtime" == "dotnet" ]]; then product="dotnet-runtime" @@ -448,7 +446,7 @@ get_normalized_product() { # args: # version_text - stdin -get_version_from_latestversion_file_content() { +get_version_from_version_info() { eval $invocation cat | tail -n 1 | sed 's/\r$//' @@ -480,7 +478,7 @@ is_dotnet_package_installed() { # azure_feed - $1 # channel - $2 # normalized_architecture - $3 -get_version_from_latestversion_file() { +get_latest_version_info() { eval $invocation local azure_feed="$1" @@ -489,16 +487,16 @@ get_version_from_latestversion_file() { local version_file_url=null if [[ "$runtime" == "dotnet" ]]; then - version_file_url="$azure_feed/Runtime/$channel/latest.version" + version_file_url="$uncached_feed/Runtime/$channel/latest.version" elif [[ "$runtime" == "aspnetcore" ]]; then - version_file_url="$azure_feed/aspnetcore/Runtime/$channel/latest.version" + version_file_url="$uncached_feed/aspnetcore/Runtime/$channel/latest.version" elif [ -z "$runtime" ]; then - version_file_url="$azure_feed/Sdk/$channel/latest.version" + version_file_url="$uncached_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" + say_verbose "get_latest_version_info: latest url: $version_file_url" download "$version_file_url" || return $? return 0 @@ -506,7 +504,7 @@ get_version_from_latestversion_file() { # args: # json_file - $1 -parse_globaljson_file_for_version() { +parse_jsonfile_for_version() { eval $invocation local json_file="$1" @@ -562,9 +560,9 @@ get_specific_version_from_version() { 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 + version_info="$(get_latest_version_info "$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 + echo "$version_info" | get_version_from_version_info return 0 else echo "$version" @@ -572,7 +570,7 @@ get_specific_version_from_version() { fi else local version_info - version_info="$(parse_globaljson_file_for_version "$json_file")" || return 1 + version_info="$(parse_jsonfile_for_version "$json_file")" || return 1 echo "$version_info" return 0 fi @@ -637,7 +635,7 @@ get_specific_product_version() { if machine_has "curl" then - specific_product_version=$(curl -s --fail "${download_link}${feed_credential}" 2>&1) + specific_product_version=$(curl -s --fail "${download_link}${feed_credential}") if [ $? = 0 ]; then echo "${specific_product_version//[$'\t\r\n']}" return 0 @@ -909,7 +907,7 @@ get_http_header_curl() { fi curl_options="-I -sSL --retry 5 --retry-delay 2 --connect-timeout 15 " - curl $curl_options "$remote_path_with_credential" 2>&1 || return 1 + curl $curl_options "$remote_path_with_credential" || return 1 return 0 } @@ -1001,9 +999,9 @@ downloadcurl() { local curl_options="--retry 20 --retry-delay 2 --connect-timeout 15 -sSL -f --create-dirs " local failed=false if [ -z "$out_path" ]; then - curl $curl_options "$remote_path_with_credential" 2>&1 || failed=true + curl $curl_options "$remote_path_with_credential" || failed=true else - curl $curl_options -o "$out_path" "$remote_path_with_credential" 2>&1 || failed=true + curl $curl_options -o "$out_path" "$remote_path_with_credential" || failed=true fi if [ "$failed" = true ]; then local disable_feed_credential=false @@ -1035,20 +1033,20 @@ downloadwget() { local wget_result='' if [ -z "$out_path" ]; then - wget -q $wget_options $wget_options_extra -O - "$remote_path_with_credential" 2>&1 + wget -q $wget_options $wget_options_extra -O - "$remote_path_with_credential" wget_result=$? else - wget $wget_options $wget_options_extra -O "$out_path" "$remote_path_with_credential" 2>&1 + wget $wget_options $wget_options_extra -O "$out_path" "$remote_path_with_credential" wget_result=$? fi if [[ $wget_result == 2 ]]; then # Parsing of the command has failed. Exclude potentially unrecognized options and retry. if [ -z "$out_path" ]; then - wget -q $wget_options -O - "$remote_path_with_credential" 2>&1 + wget -q $wget_options -O - "$remote_path_with_credential" wget_result=$? else - wget $wget_options -O "$out_path" "$remote_path_with_credential" 2>&1 + wget $wget_options -O "$out_path" "$remote_path_with_credential" wget_result=$? fi fi @@ -1068,7 +1066,7 @@ downloadwget() { return 0 } -get_download_link_from_aka_ms() { +get_download_link_from_aka_ms() { eval $invocation #quality is not supported for LTS or current channel @@ -1121,225 +1119,93 @@ get_download_link_from_aka_ms() { fi } -get_feeds_to_use() -{ - feeds=( - "https://dotnetcli.azureedge.net/dotnet" - "https://dotnetbuilds.azureedge.net/public" - ) - - if [[ -n "$azure_feed" ]]; then - feeds=("$azure_feed") - fi - - if [[ "$no_cdn" == "true" ]]; then - feeds=( - "https://dotnetcli.blob.core.windows.net/dotnet" - "https://dotnetbuilds.blob.core.windows.net/public" - ) - - if [[ -n "$uncached_feed" ]]; then - feeds=("$uncached_feed") - fi - fi -} - -generate_download_links() { - - download_links=() - specific_versions=() - effective_versions=() - link_types=() - - # If generate_akams_links returns false, no fallback to old links. Just terminate. - 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 $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 -} +calculate_vars() { + eval $invocation + valid_legacy_download_link=true -# returns: -# 0 - if operation succeeded and the execution should continue as normal -# 1 - if the script has reached a concluding state and the execution should stop. -generate_akams_links() { - local valid_aka_ms_link=true; + #normalize input variables + 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'." + #try to get download location from aka.ms link + #not applicable when exact version is specified via command or json file normalized_version="$(to_lowercase "$version")" - 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") - - if [[ "$dry_run" == true ]]; then - print_dry_run "$download_link" "" "$effective_version" - return 1 - fi - - # Check if the SDK version is already installed. - if is_dotnet_package_installed "$install_root" "$asset_relative_path" "$effective_version"; then - say "$asset_name with version '$effective_version' is already installed." - return 1 - 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 + if [[ -z "$json_file" && "$normalized_version" == "latest" ]]; then + + valid_aka_ms_link=true; + get_download_link_from_aka_ms || valid_aka_ms_link=false + + if [ "$valid_aka_ms_link" == false ]; then + # 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." + else + say_verbose "Retrieved primary payload URL from aka.ms link: '$aka_ms_download_link'." + download_link=$aka_ms_download_link + + say_verbose "Downloading using legacy url will not be attempted." + valid_legacy_download_link=false + + #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 product specific version + specific_product_version="$(get_specific_product_version "$azure_feed" "$specific_version" "$download_link")" + say_verbose "Product specific version: '$specific_product_version'." + + install_root="$(resolve_installation_path "$install_dir")" + say_verbose "InstallRoot: '$install_root'." + return + fi fi - say_verbose "Falling back to latest.version file approach." -} - -# args: -# feed - $1 -# returns: -# 0 - if operation succeded and the execution should continue as normal -# 1 - if the script has reached a concluding state and the execution should stop -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' + specific_version=$(get_specific_version_from_version "$azure_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 + say_err "Could not resolve version information." + return 1 fi - effective_version="$(get_specific_product_version "$feed" "$specific_version")" + specific_product_version="$(get_specific_product_version "$azure_feed" "$specific_version")" say_verbose "specific_version=$specific_version" - download_link="$(construct_download_link "$feed" "$channel" "$normalized_architecture" "$specific_version" "$normalized_os")" + download_link="$(construct_download_link "$azure_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 + legacy_download_link="$(construct_legacy_download_link "$azure_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+=($download_link) - specific_versions+=($specific_version) - effective_versions+=($effective_version) - link_types+=("legacy") else - legacy_download_link="" say_verbose "Cound not construct a legacy_download_link; omitting..." fi - if [[ "$dry_run" == true ]]; then - print_dry_run "$download_link" "$legacy_download_link" "$effective_version" - return 1 - fi - - # Check if the SDK version is already installed. - if is_dotnet_package_installed "$install_root" "$asset_relative_path" "$effective_version"; then - say "$asset_name with version '$effective_version' is already installed." - return 1 - fi -} - -# args: -# download_link - $1 -# legacy_download_link - $2 - can be empty -# specific_version -print_dry_run() { - local download_link="$1" - local legacy_download_link="$2" - local specific_version="$3" - - say "Payload URLs:" - say "Primary named payload URL: ${download_link}" - if [ -n "$legacy_download_link" ]; then - say "Legacy named payload URL: ${legacy_download_link}" - fi - repeatable_command="./$script_name --version "\""$specific_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" - exit 0 + install_root="$(resolve_installation_path "$install_dir")" + say_verbose "InstallRoot: $install_root" } -calculate_vars() { +install_dotnet() { 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'." + local download_failed=false + local asset_name='' + local asset_relative_path='' if [[ "$runtime" == "dotnet" ]]; then asset_relative_path="shared/Microsoft.NETCore.App" @@ -1350,52 +1216,84 @@ calculate_vars() { elif [ -z "$runtime" ]; then asset_relative_path="sdk" asset_name=".NET Core SDK" + else + say_err "Invalid value for \$runtime" + return 1 fi - get_feeds_to_use -} - -install_dotnet() { - eval $invocation - local download_failed=false - local download_completed=false + # Check if the SDK version is already installed. + if is_dotnet_package_installed "$install_root" "$asset_relative_path" "$specific_version"; then + say "$asset_name version $specific_version is already installed." + return 0 + fi mkdir -p "$install_root" zip_path="$(mktemp "$temporary_file_template")" say_verbose "Zip 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" + # Failures are normal in the non-legacy case for ultimately legacy downloads. + # Do not output to stderr, since output to stderr is considered an error. + say "Downloading primary 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 + # The download function will set variables $http_code and $download_error_msg in case of failure. + 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': $download_error_msg" - ;; - esac - rm -f "$zip_path" 2>&1 && say_verbose "Temporary zip file $zip_path was removed" - else - download_completed=true - break + # if the download fails, download the legacy_download_link + if [ "$download_failed" = true ]; then + primary_path_http_code="$http_code"; primary_path_download_error_msg="$download_error_msg" + case $primary_path_http_code in + 404) + say "The resource at $download_link is not available." + ;; + *) + say "$primary_path_download_error_msg" + ;; + esac + rm -f "$zip_path" 2>&1 && say_verbose "Temporary zip file $zip_path was removed" + if [ "$valid_legacy_download_link" = true ]; then + download_failed=false + download_link="$legacy_download_link" + zip_path="$(mktemp "$temporary_file_template")" + say_verbose "Legacy zip path: $zip_path" + + say "Downloading legacy link $download_link" + + # The download function will set variables $http_code and $download_error_msg in case of failure. + download "$download_link" "$zip_path" 2>&1 || download_failed=true + + if [ "$download_failed" = true ]; then + legacy_path_http_code="$http_code"; legacy_path_download_error_msg="$download_error_msg" + case $legacy_path_http_code in + 404) + say "The resource at $download_link is not available." + ;; + *) + say "$legacy_path_download_error_msg" + ;; + esac + rm -f "$zip_path" 2>&1 && say_verbose "Temporary zip file $zip_path was removed" + fi fi - done + fi - 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" + if [ "$download_failed" = true ]; then + if [[ "$primary_path_http_code" = "404" && ( "$valid_legacy_download_link" = false || "$legacy_path_http_code" = "404") ]]; 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" + else + say_err "Could not download: \`$asset_name\` with version = $specific_version" + # 404-NotFound is an expected response if it goes from only one of the links, do not show that error. + # If primary path is available (not 404-NotFound) then show the primary error else show the legacy error. + if [ "$primary_path_http_code" != "404" ]; then + say_err "$primary_path_download_error_msg" + return 1 + fi + if [[ "$valid_legacy_download_link" = true && "$legacy_path_http_code" != "404" ]]; then + say_err "$legacy_path_download_error_msg" + return 1 + fi + fi return 1 fi @@ -1416,14 +1314,14 @@ install_dotnet() { 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_verbose "Checking installation: version = $specific_product_version" + if is_dotnet_package_installed "$install_root" "$asset_relative_path" "$specific_product_version"; then 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." + say_err "\`$asset_name\` with version = $specific_product_version failed to install with an unknown error." return 1 } @@ -1441,8 +1339,8 @@ architecture="" dry_run=false no_path=false no_cdn=false -azure_feed="" -uncached_feed="" +azure_feed="https://dotnetcli.azureedge.net/dotnet" +uncached_feed="https://dotnetcli.blob.core.windows.net/dotnet" feed_credential="" verbose=false runtime="" @@ -1602,12 +1500,8 @@ do 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 " This parameter is only used if --no-cdn is false." - echo " --uncached-feed,-UncachedFeed For internal use only." - echo " Allows using a different storage to download SDK archives from." - echo " This parameter is only used if --no-cdn is true." + echo " --azure-feed,-AzureFeed Azure feed location. Defaults to $azure_feed, This parameter typically is not changed by the user." + echo " --uncached-feed,-UncachedFeed Uncached feed location. This parameter typically is not changed by the user." echo " --skip-non-versioned-files Skips non-versioned files if they already exist, such as the dotnet executable." echo " -SkipNonVersionedFiles" echo " --no-cdn,-NoCdn Disable downloading from the Azure CDN, and use the uncached feed directly." @@ -1615,6 +1509,14 @@ do echo " Note: global.json must have a value for 'SDK:Version'" echo " -?,--?,-h,--help,-Help Shows this help message" echo "" + echo "Obsolete parameters:" + echo " --shared-runtime The recommended alternative is '--runtime dotnet'." + echo " This parameter is obsolete and may be removed in a future version of this script." + echo " Installs just the shared runtime bits, not the entire SDK." + echo " --runtime-id Installs the .NET Tools for the given platform (use linux-x64 for portable linux)." + echo " -RuntimeId" The parameter is obsolete and may be removed in a future version of this script. Should be used only for versions below 2.1. + echo " For primary links to override OS or/and architecture, use --os and --architecture option instead." + echo "" echo "Install Location:" echo " Location is chosen in following order:" echo " - --install-dir option" @@ -1631,6 +1533,10 @@ do shift done +if [ "$no_cdn" = true ]; then + azure_feed="$uncached_feed" +fi + say "Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:" say "- The SDK needs to be installed without user interaction and without admin rights." say "- The SDK installation doesn't need to persist across multiple CI runs." @@ -1648,12 +1554,34 @@ fi check_min_reqs calculate_vars -generate_download_links - +script_name=$(basename "$0") if [ "$dry_run" = true ]; then - # Don't continue to installation step in dry_run mode. - return + say "Payload URLs:" + say "Primary named payload URL: ${download_link}" + if [ "$valid_legacy_download_link" = true ]; then + say "Legacy named payload URL: ${legacy_download_link}" + fi + repeatable_command="./$script_name --version "\""$specific_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" + exit 0 fi install_dotnet diff --git a/tests/Install-Scripts.Test/GivenThatIWantToInstallDotnetFromAScript.cs b/tests/Install-Scripts.Test/GivenThatIWantToInstallDotnetFromAScript.cs index 28312762fc..6d5886fc39 100644 --- a/tests/Install-Scripts.Test/GivenThatIWantToInstallDotnetFromAScript.cs +++ b/tests/Install-Scripts.Test/GivenThatIWantToInstallDotnetFromAScript.cs @@ -69,7 +69,6 @@ public class GivenThatIWantToInstallDotnetFromAScript : IDisposable ("6.0.1xx-preview2", "6\\.0\\.1.*", Quality.Daily | Quality.Signed), ("6.0.1xx-preview3", "6\\.0\\.1.*", Quality.Daily), ("6.0.1xx-preview4", "6\\.0\\.1.*", Quality.Daily), - ("7.0.1xx", "7\\.0\\..*", Quality.Daily), }; public static IEnumerable InstallSdkFromChannelTestCases @@ -374,139 +373,6 @@ public void WhenInstallingDotnetRuntimeWithFeedCredential(string channel, string commandResult.Should().NotHaveStdOutContainingIgnoreCase(feedCredential); } - [Theory] - [Trait("MonitoringTest", "true")] - [InlineData("5.0.404-servicing.21560.14", "5.0.404")] - [InlineData("6.0.100-preview.6.21364.34")] - [InlineData("7.0.100-alpha.1.22054.9")] - public void WhenInstallingASpecificVersionOfTheSdk(string version, string? effectiveVersion = null) - { - // Run install script to download and install. - var args = GetInstallScriptArgs(channel:null, runtime: null, quality:null, _sdkInstallationDirectory, version: version); - - var commandResult = CreateInstallCommand(args) - .CaptureStdOut() - .CaptureStdErr() - .Execute(); - - commandResult.Should().HaveStdOutContaining("Installation finished"); - - // Run dotnet to verify that the version is installed into correct folder. - var dotnetArgs = new List { "--info" }; - - var dotnetCommandResult = CreateDotnetCommand(_sdkInstallationDirectory, dotnetArgs) - .CaptureStdOut() - .CaptureStdErr() - .Execute(); - - // On MacOS, installation directory has an extra /private at the beginning. - string installPathRegex = "\\[(/private)?" + Regex.Escape(Path.Combine(_sdkInstallationDirectory, "sdk")) + "\\]"; - string regex = Regex.Escape(" " + (effectiveVersion ?? version) + " ") + installPathRegex; - dotnetCommandResult.Should().HaveStdOutMatching(regex); - commandResult.Should().NotHaveStdErr(); - - TestOutputHelper.PopulateTestLoggerOutput(outputHelper, commandResult); - } - - [Theory] - [Trait("MonitoringTest", "true")] - [InlineData("5.0.13-servicing.21560.6", "5.0.13")] - [InlineData("6.0.0-preview.4.21176.7")] - [InlineData("7.0.0-alpha.1.21528.8")] - public void WhenInstallingASpecificVersionOfDotnetRuntime(string version, string? effectiveVersion = null) - { - // Run install script to download and install. - var args = GetInstallScriptArgs(channel: null, "dotnet", quality: null, _sdkInstallationDirectory, version: version); - - var commandResult = CreateInstallCommand(args) - .CaptureStdOut() - .CaptureStdErr() - .Execute(); - - commandResult.Should().HaveStdOutContaining("Installation finished"); - - // Run dotnet to verify that the version is installed into correct folder. - var dotnetArgs = new List { "--info" }; - - var dotnetCommandResult = CreateDotnetCommand(_sdkInstallationDirectory, dotnetArgs) - .CaptureStdOut() - .CaptureStdErr() - .Execute(); - - string lineStartRegex = Regex.Escape(" Microsoft.NETCore.App "); - string lineEndRegex = "\\ \\[(/private)?" + Regex.Escape(Path.Combine(_sdkInstallationDirectory, "shared", "Microsoft.NETCore.App")) + "\\]"; - string regex = lineStartRegex + Regex.Escape(effectiveVersion ?? version) + lineEndRegex; - dotnetCommandResult.Should().HaveStdOutMatching(regex); - commandResult.Should().NotHaveStdErr(); - - TestOutputHelper.PopulateTestLoggerOutput(outputHelper, commandResult); - } - - [Theory] - [Trait("MonitoringTest", "true")] - [InlineData("5.0.13-servicing.21552.32", "5.0.13")] - [InlineData("6.0.0-preview.4.21176.7")] - [InlineData("7.0.0-alpha.1.21567.15")] - public void WhenInstallingASpecificVersionOfAspNetCoreRuntime(string version, string? effectiveVersion = null) - { - // Run install script to download and install. - var args = GetInstallScriptArgs(channel: null, "aspnetcore", quality: null, _sdkInstallationDirectory, version: version); - - var commandResult = CreateInstallCommand(args) - .CaptureStdOut() - .CaptureStdErr() - .Execute(); - - commandResult.Should().HaveStdOutContaining("Installation finished"); - - // Run dotnet to verify that the version is installed into correct folder. - var dotnetArgs = new List { "--info" }; - - var dotnetCommandResult = CreateDotnetCommand(_sdkInstallationDirectory, dotnetArgs) - .CaptureStdOut() - .CaptureStdErr() - .Execute(); - - string lineStartRegex = Regex.Escape(" Microsoft.AspNetCore.App "); - string lineEndRegex = "\\ \\[(/private)?" + Regex.Escape(Path.Combine(_sdkInstallationDirectory, "shared", "Microsoft.AspNetCore.App")) + "\\]"; - string regex = lineStartRegex + Regex.Escape(effectiveVersion ?? version) + lineEndRegex; - dotnetCommandResult.Should().HaveStdOutMatching(regex); - commandResult.Should().NotHaveStdErr(); - - TestOutputHelper.PopulateTestLoggerOutput(outputHelper, commandResult); - } - - [Theory] - [Trait("MonitoringTest", "true")] - // productVersion files are broken prior to 6.0 release. - // [InlineData("5.0.14-servicing.21614.9")] - [InlineData("6.0.1-servicing.21568.2")] - [InlineData("7.0.0-alpha.1.21472.1")] - public void WhenInstallingASpecificVersionOfWindowsdesktopRuntime(string version) - { - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - // Don't install windowsdesktop if not on Windows. - return; - } - - // Run install script to download and install. - var args = GetInstallScriptArgs(channel: null, "windowsdesktop", quality: null, _sdkInstallationDirectory, version: version); - - var commandResult = CreateInstallCommand(args) - .CaptureStdOut() - .CaptureStdErr() - .Execute(); - - commandResult.Should().NotHaveStdErr(); - commandResult.Should().HaveStdOutContaining("Installation finished"); - - TestOutputHelper.PopulateTestLoggerOutput(outputHelper, commandResult); - - // Dotnet CLI is not included in the windowsdesktop runtime. Therefore, version validation cannot be tested. - // Add the validation once the becomes available in the artifacts. - } - [Theory] [InlineData(null, "2.4", "ga")] [InlineData(null, "3.9", null)] @@ -541,8 +407,7 @@ private static IEnumerable GetInstallScriptArgs( string? quality, string? installDir, string? feedCredentials = null, - bool verboseLogging = false, - string? version = null) + bool verboseLogging = false) { if (!string.IsNullOrWhiteSpace(channel)) { @@ -574,12 +439,6 @@ private static IEnumerable GetInstallScriptArgs( yield return feedCredentials; } - if (!string.IsNullOrWhiteSpace(version)) - { - yield return "-Version"; - yield return version; - } - if (verboseLogging) { yield return "-Verbose"; diff --git a/tests/Install-Scripts.Test/Utils/CommandResultAssertions.cs b/tests/Install-Scripts.Test/Utils/CommandResultAssertions.cs index 45b54e2dc4..221eee0791 100644 --- a/tests/Install-Scripts.Test/Utils/CommandResultAssertions.cs +++ b/tests/Install-Scripts.Test/Utils/CommandResultAssertions.cs @@ -151,14 +151,14 @@ public AndConstraint HaveStdErrMatching(string pattern, public AndConstraint NotHaveStdOut() { Execute.Assertion.ForCondition(string.IsNullOrEmpty(_commandResult.StdOut)) - .FailWith(AppendDiagnosticsTo($"Expected command to not output to stdout but it did:")); + .FailWith(AppendDiagnosticsTo($"Expected command to not output to stdout but it was not:")); return new AndConstraint(this); } public AndConstraint NotHaveStdErr() { Execute.Assertion.ForCondition(string.IsNullOrEmpty(_commandResult.StdErr)) - .FailWith(AppendDiagnosticsTo("Expected command to not output to stderr but it did:")); + .FailWith(AppendDiagnosticsTo("Expected command to not output to stderr but it was not:")); return new AndConstraint(this); }