From c6fce3a672d86e9e54d46cf35a346417f05694f6 Mon Sep 17 00:00:00 2001 From: Danny McCormick Date: Thu, 20 Jun 2019 16:17:52 -0400 Subject: [PATCH] Feedback --- __tests__/installer.test.ts | 48 ++++ externals/install-dotnet.ps1 | 309 +++++++++++++++------ externals/install-dotnet.sh | 508 ++++++++++++++++++++++++----------- src/installer.ts | 26 +- 4 files changed, 629 insertions(+), 262 deletions(-) diff --git a/__tests__/installer.test.ts b/__tests__/installer.test.ts index 9f0952a..2988f8f 100644 --- a/__tests__/installer.test.ts +++ b/__tests__/installer.test.ts @@ -2,6 +2,7 @@ import io = require('@actions/io'); import fs = require('fs'); import os = require('os'); import path = require('path'); +import httpClient = require('typed-rest-client/HttpClient'); const toolDir = path.join(__dirname, 'runner', 'tools'); const tempDir = path.join(__dirname, 'runner', 'temp'); @@ -71,8 +72,55 @@ describe('installer tests', () => { expect(thrown).toBe(true); return; }); + + it('Uses an up to date bash download script', async () => { + var httpCallbackClient = new httpClient.HttpClient( + 'setup-dotnet-test', + [], + {} + ); + const response: httpClient.HttpClientResponse = await httpCallbackClient.get( + 'https://dot.net/v1/dotnet-install.sh' + ); + const upToDateContents: string = await response.readBody(); + const currentContents: string = fs + .readFileSync( + path.join(__dirname, '..', 'externals', 'install-dotnet.sh') + ) + .toString(); + expect(normalizeFileContents(currentContents)).toBe( + normalizeFileContents(upToDateContents) + ); + }); + + it('Uses an up to date powershell download script', async () => { + var httpCallbackClient = new httpClient.HttpClient( + 'setup-dotnet-test', + [], + {} + ); + const response: httpClient.HttpClientResponse = await httpCallbackClient.get( + 'https://dot.net/v1/dotnet-install.ps1' + ); + const upToDateContents: string = await response.readBody(); + const currentContents: string = fs + .readFileSync( + path.join(__dirname, '..', 'externals', 'install-dotnet.ps1') + ) + .toString(); + expect(normalizeFileContents(currentContents)).toBe( + normalizeFileContents(upToDateContents) + ); + }); }); +function normalizeFileContents(contents: string): string { + return contents + .trim() + .replace(new RegExp('\r\n', 'g'), '\n') + .replace(new RegExp('\r', 'g'), '\n'); +} + async function getDotnet(version: string): Promise { const dotnetInstaller = new installer.DotnetCoreInstaller(version); await dotnetInstaller.installDotnet(); diff --git a/externals/install-dotnet.ps1 b/externals/install-dotnet.ps1 index 8540453..f17bd85 100644 --- a/externals/install-dotnet.ps1 +++ b/externals/install-dotnet.ps1 @@ -15,9 +15,10 @@ - Current - most current release - LTS - most current supported release - 2-part version in a format A.B - represents a specific release - examples: 2.0; 1.0 + examples: 2.0, 1.0 - Branch name - examples: release/2.0.0; Master + examples: release/2.0.0, Master + Note: The version parameter overrides the channel parameter. .PARAMETER Version Default: latest Represents a build version on specific channel. Possible values: @@ -25,17 +26,26 @@ - coherent - most latest coherent build on specific channel coherent applies only to SDK downloads - 3-part version in a format A.B.C - represents specific version of build - examples: 2.0.0-preview2-006120; 1.1.0 + examples: 2.0.0-preview2-006120, 1.1.0 .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: , x64 and x86 + 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'. + Default: false - Installs just the shared runtime bits, not the entire SDK + Installs just the shared runtime bits, not the entire SDK. + This is equivalent to specifying `-Runtime dotnet`. +.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 .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 @@ -49,15 +59,23 @@ .PARAMETER AzureFeed Default: https://dotnetcli.azureedge.net/dotnet This parameter typically is not changed by the user. - It allows to change URL for the Azure feed used by this installer. + It allows changing the URL for the Azure feed used by this installer. .PARAMETER UncachedFeed This parameter typically is not changed by the user. - It allows to change URL for the Uncached feed used by this installer. + It allows changing the URL for the Uncached feed used by this installer. +.PARAMETER FeedCredential + Used as a query string to append to the Azure feed. + It allows changing the URL to use non-public blob storage accounts. .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 SkipNonVersionedFiles + Default: false + Skips installing non-versioned files if they already exist, such as dotnet.exe. +.PARAMETER NoCdn + Disable downloading from the Azure CDN, and use the uncached feed directly. #> [cmdletbinding()] param( @@ -65,27 +83,41 @@ param( [string]$Version="Latest", [string]$InstallDir="", [string]$Architecture="", + [ValidateSet("dotnet", "aspnetcore", IgnoreCase = $false)] + [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="https://dotnetcli.azureedge.net/dotnet", [string]$UncachedFeed="https://dotnetcli.blob.core.windows.net/dotnet", + [string]$FeedCredential, [string]$ProxyAddress, - [switch]$ProxyUseDefaultCredentials + [switch]$ProxyUseDefaultCredentials, + [switch]$SkipNonVersionedFiles, + [switch]$NoCdn ) 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=$true +$OverrideNonVersionedFiles = !$SkipNonVersionedFiles function Say($str) { - Write-Output "dotnet-install: $str" + Write-Host "dotnet-install: $str" } function Say-Verbose($str) { @@ -120,11 +152,10 @@ function Invoke-With-Retry([ScriptBlock]$ScriptBlock, [int]$MaxAttempts = 3, [in function Get-Machine-Architecture() { Say-Invocation $MyInvocation - # possible values: AMD64, IA64, x86 + # possible values: amd64, x64, x86, arm64, arm return $ENV:PROCESSOR_ARCHITECTURE } -# TODO: Architecture and CLIArchitecture should be unified function Get-CLIArchitecture-From-Architecture([string]$Architecture) { Say-Invocation $MyInvocation @@ -132,18 +163,27 @@ function Get-CLIArchitecture-From-Architecture([string]$Architecture) { { $_ -eq "" } { return Get-CLIArchitecture-From-Architecture $(Get-Machine-Architecture) } { ($_ -eq "amd64") -or ($_ -eq "x64") } { return "x64" } { $_ -eq "x86" } { return "x86" } - default { throw "Architecture not supported. If you think this is a bug, please report it at https://github.com/dotnet/cli/issues" } + { $_ -eq "arm" } { return "arm" } + { $_ -eq "arm64" } { return "arm64" } + default { throw "Architecture not supported. If you think this is a bug, report it at https://github.com/dotnet/cli/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-Info-From-Version-Text([string]$VersionText) { Say-Invocation $MyInvocation - $Data = @($VersionText.Split([char[]]@(), [StringSplitOptions]::RemoveEmptyEntries)); + $Data = -split $VersionText - $VersionInfo = @{} - $VersionInfo.CommitHash = $Data[0].Trim() - $VersionInfo.Version = $Data[1].Trim() + $VersionInfo = @{ + CommitHash = $(if ($Data.Count -gt 1) { $Data[0] }) + Version = $Data[-1] # last line is always the version number. + } return $VersionInfo } @@ -168,33 +208,39 @@ function GetHTTPResponse([Uri] $Uri) # 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) - { - # 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))){ - $ProxyAddress = $DefaultProxy.GetProxy($Uri).OriginalString - $ProxyUseDefaultCredentials = $true + 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))) { + $ProxyAddress = $DefaultProxy.GetProxy($Uri).OriginalString + $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...") } } - if($ProxyAddress){ + if($ProxyAddress) { $HttpClientHandler = New-Object System.Net.Http.HttpClientHandler $HttpClientHandler.Proxy = New-Object System.Net.WebProxy -Property @{Address=$ProxyAddress;UseDefaultCredentials=$ProxyUseDefaultCredentials} $HttpClient = New-Object System.Net.Http.HttpClient -ArgumentList $HttpClientHandler - } + } else { + $HttpClient = New-Object System.Net.Http.HttpClient } # Default timeout for HttpClient is 100s. For a 50 MB download this assumes 500 KB/s average, any less will time out - # 10 minutes allows it to work over much slower connections. - $HttpClient.Timeout = New-TimeSpan -Minutes 10 - $Response = $HttpClient.GetAsync($Uri).Result - if (($Response -eq $null) -or (-not ($Response.IsSuccessStatusCode))) - { + # 20 minutes allows it to work over much slower connections. + $HttpClient.Timeout = New-TimeSpan -Minutes 20 + $Response = $HttpClient.GetAsync("${Uri}${FeedCredential}").Result + if (($Response -eq $null) -or (-not ($Response.IsSuccessStatusCode))) { + # The feed credential is potentially sensitive info. Do not log FeedCredential to console output. $ErrorMsg = "Failed to download $Uri." - if ($Response -ne $null) - { + if ($Response -ne $null) { $ErrorMsg += " $Response" } @@ -208,7 +254,7 @@ function GetHTTPResponse([Uri] $Uri) $HttpClient.Dispose() } } - }) + }) } @@ -216,10 +262,13 @@ function Get-Latest-Version-Info([string]$AzureFeed, [string]$Channel, [bool]$Co Say-Invocation $MyInvocation $VersionFileUrl = $null - if ($SharedRuntime) { + if ($Runtime -eq "dotnet") { $VersionFileUrl = "$UncachedFeed/Runtime/$Channel/latest.version" } - else { + elseif ($Runtime -eq "aspnetcore") { + $VersionFileUrl = "$UncachedFeed/aspnetcore/Runtime/$Channel/latest.version" + } + elseif (-not $Runtime) { if ($Coherent) { $VersionFileUrl = "$UncachedFeed/Sdk/$Channel/latest.coherent.version" } @@ -227,12 +276,19 @@ function Get-Latest-Version-Info([string]$AzureFeed, [string]$Channel, [bool]$Co $VersionFileUrl = "$UncachedFeed/Sdk/$Channel/latest.version" } } - - $Response = GetHTTPResponse -Uri $VersionFileUrl + else { + throw "Invalid value for `$Runtime" + } + try { + $Response = GetHTTPResponse -Uri $VersionFileUrl + } + catch { + throw "Could not resolve version information." + } $StringContent = $Response.Content.ReadAsStringAsync().Result switch ($Response.Content.Headers.ContentType) { - { ($_ -eq "application/octet-stream") } { $VersionText = [Text.Encoding]::UTF8.GetString($StringContent) } + { ($_ -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." } @@ -260,32 +316,41 @@ function Get-Specific-Version-From-Version([string]$AzureFeed, [string]$Channel, } } -function Get-Download-Link([string]$AzureFeed, [string]$Channel, [string]$SpecificVersion, [string]$CLIArchitecture) { +function Get-Download-Link([string]$AzureFeed, [string]$SpecificVersion, [string]$CLIArchitecture) { Say-Invocation $MyInvocation - - if ($SharedRuntime) { + + if ($Runtime -eq "dotnet") { $PayloadURL = "$AzureFeed/Runtime/$SpecificVersion/dotnet-runtime-$SpecificVersion-win-$CLIArchitecture.zip" } - else { + elseif ($Runtime -eq "aspnetcore") { + $PayloadURL = "$AzureFeed/aspnetcore/Runtime/$SpecificVersion/aspnetcore-runtime-$SpecificVersion-win-$CLIArchitecture.zip" + } + elseif (-not $Runtime) { $PayloadURL = "$AzureFeed/Sdk/$SpecificVersion/dotnet-sdk-$SpecificVersion-win-$CLIArchitecture.zip" } + else { + throw "Invalid value for `$Runtime" + } - Say-Verbose "Constructed primary payload URL: $PayloadURL" + Say-Verbose "Constructed primary named payload URL: $PayloadURL" return $PayloadURL } -function Get-LegacyDownload-Link([string]$AzureFeed, [string]$Channel, [string]$SpecificVersion, [string]$CLIArchitecture) { +function Get-LegacyDownload-Link([string]$AzureFeed, [string]$SpecificVersion, [string]$CLIArchitecture) { Say-Invocation $MyInvocation - - if ($SharedRuntime) { + + 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 { - $PayloadURL = "$AzureFeed/Sdk/$SpecificVersion/dotnet-dev-win-$CLIArchitecture.$SpecificVersion.zip" + return $null } - Say-Verbose "Constructed legacy payload URL: $PayloadURL" + Say-Verbose "Constructed legacy named payload URL: $PayloadURL" return $PayloadURL } @@ -314,7 +379,7 @@ function Get-Version-Info-From-Version-File([string]$InstallRoot, [string]$Relat $VersionFile = Join-Path -Path $InstallRoot -ChildPath $RelativePathToVersionFile Say-Verbose "Local version file: $VersionFile" - + if (Test-Path $VersionFile) { $VersionText = cat $VersionFile Say-Verbose "Local version file text: $VersionText" @@ -328,7 +393,7 @@ function Get-Version-Info-From-Version-File([string]$InstallRoot, [string]$Relat 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: Path to a package: $DotnetPackagePath" return Test-Path $DotnetPackagePath -PathType Container @@ -346,13 +411,13 @@ function Get-Path-Prefix-With-Version($path) { 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 @@ -363,12 +428,12 @@ function Get-List-Of-Directories-And-Versions-To-Unpack-From-Dotnet-Package([Sys } } } - + $ret = $ret | Sort-Object | Get-Unique - + $values = ($ret | foreach { "$_" }) -join ";" Say-Verbose "Directories to unpack: $values" - + return $ret } @@ -392,9 +457,9 @@ function Extract-Dotnet-Package([string]$ZipPath, [string]$OutPath) { 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 (($PathWithVersion -eq $null) -Or ($DirectoriesToUnpack -contains $PathWithVersion)) { @@ -415,11 +480,23 @@ function Extract-Dotnet-Package([string]$ZipPath, [string]$OutPath) { } } -function DownloadFile([Uri]$Uri, [string]$OutPath) { +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 $Uri + $Response = GetHTTPResponse -Uri $Source $Stream = $Response.Content.ReadAsStreamAsync().Result $File = [System.IO.File]::Create($OutPath) $Stream.CopyTo($File) @@ -435,8 +512,13 @@ function DownloadFile([Uri]$Uri, [string]$OutPath) { function Prepend-Sdk-InstallRoot-To-Path([string]$InstallRoot, [string]$BinFolderRelativePath) { $BinPath = Get-Absolute-Path $(Join-Path -Path $InstallRoot -ChildPath $BinFolderRelativePath) if (-Not $NoPath) { - Say "Adding to current process PATH: `"$BinPath`". Note: This change will not be visible if PowerShell was run as a child process." - $env:path = "$BinPath;" + $env:path + $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" @@ -445,24 +527,55 @@ function Prepend-Sdk-InstallRoot-To-Path([string]$InstallRoot, [string]$BinFolde $CLIArchitecture = Get-CLIArchitecture-From-Architecture $Architecture $SpecificVersion = Get-Specific-Version-From-Version -AzureFeed $AzureFeed -Channel $Channel -Version $Version -$DownloadLink = Get-Download-Link -AzureFeed $AzureFeed -Channel $Channel -SpecificVersion $SpecificVersion -CLIArchitecture $CLIArchitecture -$LegacyDownloadLink = Get-LegacyDownload-Link -AzureFeed $AzureFeed -Channel $Channel -SpecificVersion $SpecificVersion -CLIArchitecture $CLIArchitecture - -if ($DryRun) { - Say "Payload URLs:" - Say "Primary - $DownloadLink" - Say "Legacy - $LegacyDownloadLink" - Say "Repeatable invocation: .\$($MyInvocation.MyCommand) -Version $SpecificVersion -Channel $Channel -Architecture $CLIArchitecture -InstallDir $InstallDir" - exit 0 -} +$DownloadLink = Get-Download-Link -AzureFeed $AzureFeed -SpecificVersion $SpecificVersion -CLIArchitecture $CLIArchitecture +$LegacyDownloadLink = Get-LegacyDownload-Link -AzureFeed $AzureFeed -SpecificVersion $SpecificVersion -CLIArchitecture $CLIArchitecture $InstallRoot = Resolve-Installation-Path $InstallDir Say-Verbose "InstallRoot: $InstallRoot" +$ScriptName = $MyInvocation.MyCommand.Name -$IsSdkInstalled = Is-Dotnet-Package-Installed -InstallRoot $InstallRoot -RelativePathToPackage "sdk" -SpecificVersion $SpecificVersion -Say-Verbose ".NET SDK installed? $IsSdkInstalled" -if ($IsSdkInstalled) { - Say ".NET SDK version $SpecificVersion is already installed." +if ($DryRun) { + 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 $MyInvocation.BoundParameters.Keys) { + if (-not (@("Architecture","Channel","DryRun","InstallDir","Runtime","SharedRuntime","Version") -contains $key)) { + $RepeatableCommand+=" -$key `"$($MyInvocation.BoundParameters[$key])`"" + } + } + Say "Repeatable invocation: $RepeatableCommand" + exit 0 +} + +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 (-not $Runtime) { + $assetName = ".NET Core SDK" + $dotnetPackageRelativePath = "sdk" +} +else { + throw "Invalid value for `$Runtime" +} + +# 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 exit 0 } @@ -470,34 +583,56 @@ if ($IsSdkInstalled) { New-Item -ItemType Directory -Force -Path $InstallRoot | Out-Null $installDrive = $((Get-Item $InstallRoot).PSDrive.Name); -Write-Output "${installDrive}:"; -$free = Get-CimInstance -Class win32_logicaldisk | where Deviceid -eq "${installDrive}:" -if ($free.Freespace / 1MB -le 100 ) { +$diskInfo = Get-PSDrive -Name $installDrive +if ($diskInfo.Free / 1MB -le 100) { Say "There is not enough disk space on drive ${installDrive}:" exit 0 } -$ZipPath = [System.IO.Path]::GetTempFileName() +$ZipPath = [System.IO.Path]::combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) Say-Verbose "Zip path: $ZipPath" + +$DownloadFailed = $false Say "Downloading link: $DownloadLink" try { - DownloadFile -Uri $DownloadLink -OutPath $ZipPath + DownloadFile -Source $DownloadLink -OutPath $ZipPath } catch { Say "Cannot download: $DownloadLink" - $DownloadLink = $LegacyDownloadLink - $ZipPath = [System.IO.Path]::GetTempFileName() - Say-Verbose "Legacy zip path: $ZipPath" - Say "Downloading legacy link: $DownloadLink" - DownloadFile -Uri $DownloadLink -OutPath $ZipPath + 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 { + Say "Cannot download: $DownloadLink" + $DownloadFailed = $true + } + } + else { + $DownloadFailed = $true + } +} + +if ($DownloadFailed) { + throw "Could not find/download: `"$assetName`" with version = $SpecificVersion`nRefer to: https://aka.ms/dotnet-os-lifecycle for information on .NET Core support" } Say "Extracting zip from $DownloadLink" Extract-Dotnet-Package -ZipPath $ZipPath -OutPath $InstallRoot +# Check if the SDK version is now installed; if not, fail the installation. +$isAssetInstalled = Is-Dotnet-Package-Installed -InstallRoot $InstallRoot -RelativePathToPackage $dotnetPackageRelativePath -SpecificVersion $SpecificVersion +if (!$isAssetInstalled) { + throw "`"$assetName`" with version = $SpecificVersion failed to install with an unknown error." +} + Remove-Item $ZipPath Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot -BinFolderRelativePath $BinFolderRelativePath Say "Installation finished" -exit 0 \ No newline at end of file +exit 0 diff --git a/externals/install-dotnet.sh b/externals/install-dotnet.sh index 0ea08a7..2217d47 100644 --- a/externals/install-dotnet.sh +++ b/externals/install-dotnet.sh @@ -22,7 +22,7 @@ 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 ]; then +if [ -t 1 ] && command -v tput > /dev/null; then # see if it supports colors ncolors=$(tput colors) if [ -n "$ncolors" ] && [ $ncolors -ge 8 ]; then @@ -39,6 +39,10 @@ if [ -t 1 ]; then fi fi +say_warning() { + printf "%b\n" "${yellow:-}dotnet_install: Warning: $1${normal:-}" +} + say_err() { printf "%b\n" "${red:-}dotnet_install: Error: $1${normal:-}" >&2 } @@ -55,7 +59,10 @@ say_verbose() { fi } -get_os_download_name_from_platform() { +# 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" @@ -68,6 +75,10 @@ get_os_download_name_from_platform() { echo "debian" return 0 ;; + "debian.9") + echo "debian.9" + return 0 + ;; "fedora.23") echo "fedora.23" return 0 @@ -76,6 +87,14 @@ get_os_download_name_from_platform() { 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 @@ -84,6 +103,10 @@ get_os_download_name_from_platform() { echo "opensuse.42.1" return 0 ;; + "opensuse.42.3") + echo "opensuse.42.3" + return 0 + ;; "rhel.7"*) echo "rhel" return 0 @@ -100,6 +123,10 @@ get_os_download_name_from_platform() { echo "ubuntu.16.10" return 0 ;; + "ubuntu.18.04") + echo "ubuntu.18.04" + return 0 + ;; "alpine.3.4.3") echo "alpine" return 0 @@ -108,6 +135,30 @@ get_os_download_name_from_platform() { 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" + return 0 + elif [ -e /etc/redhat-release ]; then + local redhatRelease=$(/dev/null | grep libunwind)" ] && say_warning "Unable to locate libunwind. Probable prerequisite missing; install libunwind." + [ -z "$($LDCONFIG_COMMAND 2>/dev/null | grep libssl)" ] && say_warning "Unable to locate libssl. Probable prerequisite missing; install libssl." + [ -z "$($LDCONFIG_COMMAND 2>/dev/null | grep libicu)" ] && say_warning "Unable to locate libicu. Probable prerequisite missing; install libicu." + [ -z "$($LDCONFIG_COMMAND 2>/dev/null | grep -F libcurl.so)" ] && say_warning "Unable to locate libcurl. Probable prerequisite missing; install libcurl." fi return 0 @@ -217,7 +278,7 @@ to_lowercase() { remove_trailing_slash() { #eval $invocation - local input=${1:-} + local input="${1:-}" echo "${input%/}" return 0 } @@ -227,7 +288,7 @@ remove_trailing_slash() { remove_beginning_slash() { #eval $invocation - local input=${1:-} + local input="${1:-}" echo "${input#/}" return 0 } @@ -244,8 +305,8 @@ combine_paths() { return 1 fi - local root_path=$(remove_trailing_slash $1) - local child_path=$(remove_beginning_slash ${2:-}) + 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" @@ -255,7 +316,21 @@ combine_paths() { get_machine_architecture() { eval $invocation - # Currently the only one supported + if command -v uname > /dev/null; then + CPUName=$(uname -m) + case $CPUName in + armv7l) + echo "arm" + return 0 + ;; + aarch64) + echo "arm64" + return 0 + ;; + esac + fi + + # Always default to 'x64' echo "x64" return 0 } @@ -265,46 +340,43 @@ get_machine_architecture() { get_normalized_architecture_from_architecture() { eval $invocation - local architecture=$(to_lowercase $1) - case $architecture in + local architecture="$(to_lowercase "$1")" + case "$architecture" in \) - echo "$(get_normalized_architecture_from_architecture $(get_machine_architecture))" + echo "$(get_normalized_architecture_from_architecture "$(get_machine_architecture)")" return 0 ;; amd64|x64) echo "x64" return 0 ;; - x86) - say_err "Architecture \`x86\` currently not supported" - return 1 + arm) + echo "arm" + return 0 + ;; + arm64) + echo "arm64" + return 0 ;; esac - say_err "Architecture \`$architecture\` not supported. If you think this is a bug, please report it at https://github.com/dotnet/cli/issues" + say_err "Architecture \`$architecture\` not supported. If you think this is a bug, report it at https://github.com/dotnet/cli/issues" return 1 } -# version_info is a conceptual two line string representing commit hash and 4-part version -# format: +# 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_version_info() { eval $invocation - cat | tail -n 1 - return 0 -} - -# args: -# version_text - stdin -get_commit_hash_from_version_info() { - eval $invocation - - cat | head -n 1 + cat | tail -n 1 | sed 's/\r$//' return 0 } @@ -315,11 +387,11 @@ get_commit_hash_from_version_info() { is_dotnet_package_installed() { eval $invocation - local install_root=$1 - local relative_path_to_package=$2 - local specific_version=${3//[$'\t\r\n']} + 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) + 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 @@ -333,27 +405,33 @@ is_dotnet_package_installed() { # azure_feed - $1 # channel - $2 # normalized_architecture - $3 +# coherent - $4 get_latest_version_info() { eval $invocation - local azure_feed=$1 - local channel=$2 - local normalized_architecture=$3 - local coherent=$4 + local azure_feed="$1" + local channel="$2" + local normalized_architecture="$3" + local coherent="$4" local version_file_url=null - if [ "$shared_runtime" = true ]; then + if [[ "$runtime" == "dotnet" ]]; then version_file_url="$uncached_feed/Runtime/$channel/latest.version" - else + elif [[ "$runtime" == "aspnetcore" ]]; then + version_file_url="$uncached_feed/aspnetcore/Runtime/$channel/latest.version" + elif [ -z "$runtime" ]; then if [ "$coherent" = true ]; then version_file_url="$uncached_feed/Sdk/$channel/latest.coherent.version" else version_file_url="$uncached_feed/Sdk/$channel/latest.version" fi + else + say_err "Invalid value for \$runtime" + return 1 fi say_verbose "get_latest_version_info: latest url: $version_file_url" - download $version_file_url + download "$version_file_url" return $? } @@ -365,28 +443,28 @@ get_latest_version_info() { get_specific_version_from_version() { eval $invocation - local azure_feed=$1 - local channel=$2 - local normalized_architecture=$3 - local version=$(to_lowercase $4) + local azure_feed="$1" + local channel="$2" + local normalized_architecture="$3" + local version="$(to_lowercase "$4")" - case $version in + case "$version" in latest) local version_info - version_info="$(get_latest_version_info $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_version_info return 0 ;; coherent) local version_info - version_info="$(get_latest_version_info $azure_feed $channel $normalized_architecture true)" || return 1 + version_info="$(get_latest_version_info "$azure_feed" "$channel" "$normalized_architecture" true)" || return 1 say_verbose "get_specific_version_from_version: version_info=$version_info" echo "$version_info" | get_version_from_version_info return 0 ;; *) - echo $version + echo "$version" return 0 ;; esac @@ -400,19 +478,23 @@ get_specific_version_from_version() { construct_download_link() { eval $invocation - local azure_feed=$1 - local channel=$2 - local normalized_architecture=$3 - local specific_version=${4//[$'\t\r\n']} + local azure_feed="$1" + local channel="$2" + local normalized_architecture="$3" + local specific_version="${4//[$'\t\r\n']}" local osname - osname=$(get_current_os_name) || return 1 + osname="$(get_current_os_name)" || return 1 local download_link=null - if [ "$shared_runtime" = true ]; then + if [[ "$runtime" == "dotnet" ]]; then download_link="$azure_feed/Runtime/$specific_version/dotnet-runtime-$specific_version-$osname-$normalized_architecture.tar.gz" - else + elif [[ "$runtime" == "aspnetcore" ]]; then + download_link="$azure_feed/aspnetcore/Runtime/$specific_version/aspnetcore-runtime-$specific_version-$osname-$normalized_architecture.tar.gz" + elif [ -z "$runtime" ]; then download_link="$azure_feed/Sdk/$specific_version/dotnet-sdk-$specific_version-$osname-$normalized_architecture.tar.gz" + else + return 1 fi echo "$download_link" @@ -427,19 +509,21 @@ construct_download_link() { 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 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_distro_specific_os_name) || return 1 + distro_specific_osname="$(get_legacy_os_name)" || return 1 local legacy_download_link=null - if [ "$shared_runtime" = true ]; then + if [[ "$runtime" == "dotnet" ]]; then legacy_download_link="$azure_feed/Runtime/$specific_version/dotnet-$distro_specific_osname-$normalized_architecture.$specific_version.tar.gz" - else + 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" @@ -450,7 +534,7 @@ get_user_install_path() { eval $invocation if [ ! -z "${DOTNET_INSTALL_DIR:-}" ]; then - echo $DOTNET_INSTALL_DIR + echo "$DOTNET_INSTALL_DIR" else echo "$HOME/.dotnet" fi @@ -464,7 +548,7 @@ resolve_installation_path() { local install_dir=$1 if [ "$install_dir" = "" ]; then - local user_install_path=$(get_user_install_path) + 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 @@ -479,11 +563,11 @@ resolve_installation_path() { get_installed_version_info() { eval $invocation - local install_root=$1 - local version_file=$(combine_paths "$install_root" "$local_version_file_relative_path") + local install_root="$1" + local version_file="$(combine_paths "$install_root" "$local_version_file_relative_path")" say_verbose "Local version file: $version_file" if [ ! -z "$version_file" ] | [ -r "$version_file" ]; then - local version_info="$(cat $version_file)" + local version_info="$(cat "$version_file")" echo "$version_info" return 0 fi @@ -498,7 +582,7 @@ get_absolute_path() { eval $invocation local relative_or_absolute_path=$1 - echo $(cd $(dirname "$1") && pwd -P)/$(basename "$1") + echo "$(cd "$(dirname "$1")" && pwd -P)/$(basename "$1")" return 0 } @@ -510,17 +594,25 @@ get_absolute_path() { 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=$(if [ "$override" = false ]; then printf -- "-n"; fi) + local root_path="$(remove_trailing_slash "$1")" + local out_path="$(remove_trailing_slash "$2")" + local override="$3" + local osname="$(get_current_os_name)" + local override_switch=$( + if [ "$override" = false ]; then + if [[ "$osname" == "linux-musl" ]]; then + printf -- "-u"; + else + printf -- "-n"; + fi + fi) cat | uniq | while read -r file_path; do - local path=$(remove_beginning_slash ${file_path#$root_path}) - local target=$out_path/$path + 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) - cp -R $override_switch $root_path/$path $target + mkdir -p "$out_path/$(dirname "$path")" + cp -R $override_switch "$root_path/$path" "$target" fi done } @@ -531,19 +623,19 @@ copy_files_or_dirs_from_list() { extract_dotnet_package() { eval $invocation - local zip_path=$1 - local out_path=$2 + local zip_path="$1" + local out_path="$2" - local temp_out_path=$(mktemp -d $temporary_file_template) + 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 | 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 true + 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" - rm -rf $temp_out_path + rm -rf "$temp_out_path" if [ "$failed" = true ]; then say_err "Extraction failed" @@ -557,14 +649,19 @@ extract_dotnet_package() { download() { eval $invocation - local remote_path=$1 - local out_path=${2:-} + local remote_path="$1" + local out_path="${2:-}" + + if [[ "$remote_path" != "http"* ]]; then + cp "$remote_path" "$out_path" + return $? + fi local failed=false if machine_has "curl"; then - downloadcurl $remote_path $out_path || failed=true + downloadcurl "$remote_path" "$out_path" || failed=true elif machine_has "wget"; then - downloadwget $remote_path $out_path || failed=true + downloadwget "$remote_path" "$out_path" || failed=true else failed=true fi @@ -577,14 +674,17 @@ download() { downloadcurl() { eval $invocation - local remote_path=$1 - local out_path=${2:-} + local remote_path="$1" + local out_path="${2:-}" + + # Append feed_credential as late as possible before calling curl to avoid logging feed_credential + remote_path="${remote_path}${feed_credential}" local failed=false if [ -z "$out_path" ]; then - curl --retry 10 -sSL -f --create-dirs $remote_path || failed=true + curl --retry 10 -sSL -f --create-dirs "$remote_path" || failed=true else - curl --retry 10 -sSL -f --create-dirs -o $out_path $remote_path || failed=true + curl --retry 10 -sSL -f --create-dirs -o "$out_path" "$remote_path" || failed=true fi if [ "$failed" = true ]; then say_verbose "Curl download failed" @@ -595,14 +695,17 @@ downloadcurl() { downloadwget() { eval $invocation - local remote_path=$1 - local out_path=${2:-} + local remote_path="$1" + local out_path="${2:-}" + + # Append feed_credential as late as possible before calling wget to avoid logging feed_credential + remote_path="${remote_path}${feed_credential}" local failed=false if [ -z "$out_path" ]; then - wget -q --tries 10 $remote_path || failed=true + wget -q --tries 10 -O - "$remote_path" || failed=true else - wget -v --tries 10 -O $out_path $remote_path || failed=true + wget --tries 10 -O "$out_path" "$remote_path" || failed=true fi if [ "$failed" = true ]; then say_verbose "Wget download failed" @@ -615,63 +718,105 @@ calculate_vars() { eval $invocation valid_legacy_download_link=true - normalized_architecture=$(get_normalized_architecture_from_architecture "$architecture") + normalized_architecture="$(get_normalized_architecture_from_architecture "$architecture")" say_verbose "normalized_architecture=$normalized_architecture" - specific_version=$(get_specific_version_from_version $azure_feed $channel $normalized_architecture $version) + specific_version="$(get_specific_version_from_version "$azure_feed" "$channel" "$normalized_architecture" "$version")" say_verbose "specific_version=$specific_version" if [ -z "$specific_version" ]; then - say_err "Could not get version information." + say_err "Could not resolve version information." return 1 fi - download_link=$(construct_download_link $azure_feed $channel $normalized_architecture $specific_version) - say_verbose "download_link=$download_link" + download_link="$(construct_download_link "$azure_feed" "$channel" "$normalized_architecture" "$specific_version")" + say_verbose "Constructed primary named payload URL: $download_link" - legacy_download_link=$(construct_legacy_download_link $azure_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 "legacy_download_link=$legacy_download_link" + say_verbose "Constructed legacy named payload URL: $legacy_download_link" else say_verbose "Cound not construct a legacy_download_link; omitting..." fi - install_root=$(resolve_installation_path $install_dir) - say_verbose "install_root=$install_root" + install_root="$(resolve_installation_path "$install_dir")" + say_verbose "InstallRoot: $install_root" } install_dotnet() { eval $invocation local download_failed=false + local asset_name='' + local asset_relative_path='' - if is_dotnet_package_installed $install_root "sdk" $specific_version; then - say ".NET SDK version $specific_version is already installed." + 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" + else + say_err "Invalid value for \$runtime" + return 1 + fi + + # 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) + mkdir -p "$install_root" + zip_path="$(mktemp "$temporary_file_template")" say_verbose "Zip path: $zip_path" say "Downloading link: $download_link" - download "$download_link" $zip_path || download_failed=true + + # 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. + download "$download_link" "$zip_path" 2>&1 || download_failed=true # if the download fails, download the legacy_download_link - if [ "$download_failed" = true ] && [ "$valid_legacy_download_link" = true ]; then + if [ "$download_failed" = true ]; then say "Cannot download: $download_link" - download_link=$legacy_download_link - zip_path=$(mktemp $temporary_file_template) - say_verbose "Legacy zip path: $zip_path" - say "Downloading legacy link: $download_link" - download "$download_link" $zip_path + + 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" + download "$download_link" "$zip_path" 2>&1 || download_failed=true + + if [ "$download_failed" = true ]; then + say "Cannot download: $download_link" + fi + fi + fi + + if [ "$download_failed" = true ]; then + say_err "Could not find/download: \`$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 say "Extracting zip from $download_link" - extract_dotnet_package $zip_path $install_root + extract_dotnet_package "$zip_path" "$install_root" + + # Check if the SDK version is now installed; if not, fail the installation. + if ! is_dotnet_package_installed "$install_root" "$asset_relative_path" "$specific_version"; then + say_err "\`$asset_name\` with version = $specific_version failed to install with an unknown error." + return 1 + fi return 0 } +args=("$@") + local_version_file_relative_path="/.version" bin_folder_relative_path="" temporary_file_template="${TMPDIR:-/tmp}/dotnet.XXXXXXXXX" @@ -682,19 +827,23 @@ install_dir="" architecture="" dry_run=false no_path=false +no_cdn=false azure_feed="https://dotnetcli.azureedge.net/dotnet" uncached_feed="https://dotnetcli.blob.core.windows.net/dotnet" +feed_credential="" verbose=false -shared_runtime=false +runtime="" runtime_id="" +override_non_versioned_files=true +non_dynamic_parameters="" while [ $# -ne 0 ] do - name=$1 - case $name in + name="$1" + case "$name" in -c|--channel|-[Cc]hannel) shift - channel=$1 + channel="$1" ;; -v|--version|-[Vv]ersion) shift @@ -709,31 +858,60 @@ do architecture="$1" ;; --shared-runtime|-[Ss]hared[Rr]untime) - shared_runtime=true + 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'." + 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" + ;; + --no-cdn|-[Nn]o[Cc]dn) + no_cdn=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" + non_dynamic_parameters+=" $name "\""$1"\""" ;; --runtime-id|-[Rr]untime[Ii]d) shift runtime_id="$1" + non_dynamic_parameters+=" $name "\""$1"\""" + ;; + --skip-non-versioned-files|-[Ss]kip[Nn]on[Vv]ersioned[Ff]iles) + override_non_versioned_files=false + non_dynamic_parameters+=" $name" ;; -?|--?|-h|--help|-[Hh]elp) - script_name="$(basename $0)" + script_name="$(basename "$0")" echo ".NET Tools Installer" echo "Usage: $script_name [-c|--channel ] [-v|--version ] [-p|--prefix ]" echo " $script_name -h|-?|--help" @@ -741,7 +919,7 @@ do echo "$script_name is a simple command line interface for obtaining dotnet cli." echo "" echo "Options:" - echo " -c,--channel Download from the CHANNEL specified, Defaults to \`$channel\`." + echo " -c,--channel Download from the channel specified, Defaults to \`$channel\`." echo " -Channel" echo " Possible values:" echo " - Current - most current release" @@ -750,6 +928,7 @@ do echo " examples: 2.0; 1.0" echo " - Branch name" echo " examples: release/2.0.0; Master" + echo " Note: The version parameter overrides the channel parameter." echo " -v,--version Use specific VERSION, Defaults to \`$version\`." echo " -Version" echo " Possible values:" @@ -758,20 +937,32 @@ do echo " coherent applies only to SDK downloads" 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 " -i,--install-dir Install under specified location (see Install Location below)" + echo " -i,--install-dir Install under specified location (see Install Location below)" echo " -InstallDir" - echo " --architecture Architecture of .NET Tools. Currently only x64 is supported." + echo " --architecture Architecture of dotnet binaries to be installed, Defaults to \`$architecture\`." echo " --arch,-Architecture,-Arch" - echo " --shared-runtime Installs just the shared runtime bits, not the entire SDK." - echo " -SharedRuntime" - 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 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 " --runtime-id Installs the .NET Tools for the given platform (use linux-x64 for portable linux)." + echo " Possible values: x64, arm, and arm64" + 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 " --skip-non-versioned-files Skips non-versioned files if they already exist, such as the dotnet executable." + echo " -SkipNonVersionedFiles" + 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 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 " --no-cdn,-NoCdn Disable downloading from the Azure CDN, and use the uncached feed directly." + echo " --feed-credential,-FeedCredential Azure feed shared access token. This parameter typically is not specified." + echo " --runtime-id Installs the .NET Tools for the given platform (use linux-x64 for portable linux)." echo " -RuntimeId" - echo " -?,--?,-h,--help,-Help Shows this help message" + echo " -?,--?,-h,--help,-Help Shows this help message" + echo "" + echo "Obsolete parameters:" + echo " --shared-runtime The recommended alternative is '--runtime dotnet'." + echo " -SharedRuntime Installs just the shared runtime bits, not the entire SDK." echo "" echo "Install Location:" echo " Location is chosen in following order:" @@ -789,27 +980,40 @@ do shift done +if [ "$no_cdn" = true ]; then + azure_feed="$uncached_feed" +fi + check_min_reqs calculate_vars +script_name=$(basename "$0") if [ "$dry_run" = true ]; then - say "Payload URL: $download_link" + say "Payload URLs:" + say "Primary named payload URL: $download_link" if [ "$valid_legacy_download_link" = true ]; then - say "Legacy payload URL: $legacy_download_link" + say "Legacy named payload URL: $legacy_download_link" fi - say "Repeatable invocation: ./$(basename $0) --version $specific_version --channel $channel --install-dir $install_dir" + repeatable_command="./$script_name --version "\""$specific_version"\"" --install-dir "\""$install_root"\"" --architecture "\""$normalized_architecture"\""" + if [[ "$runtime" == "dotnet" ]]; then + repeatable_command+=" --runtime "\""dotnet"\""" + elif [[ "$runtime" == "aspnetcore" ]]; then + repeatable_command+=" --runtime "\""aspnetcore"\""" + fi + repeatable_command+="$non_dynamic_parameters" + say "Repeatable invocation: $repeatable_command" exit 0 fi check_pre_reqs install_dotnet -bin_path=$(get_absolute_path $(combine_paths $install_root $bin_folder_relative_path)) +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 + export PATH="$bin_path":"$PATH" else say "Binaries of dotnet can be found in $bin_path" fi -say "Installation finished successfully." \ No newline at end of file +say "Installation finished successfully." diff --git a/src/installer.ts b/src/installer.ts index f90dc37..0ae3cf9 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -5,7 +5,6 @@ import * as exec from '@actions/exec'; import * as io from '@actions/io'; import * as tc from '@actions/tool-cache'; import httpClient = require('typed-rest-client/HttpClient'); -import httpInterfaces = require('typed-rest-client/Interfaces'); import {HttpClientResponse} from 'typed-rest-client/HttpClient'; import {chmodSync} from 'fs'; import * as os from 'os'; @@ -61,26 +60,6 @@ export class DotnetCoreInstaller { // Prepend the tools path. instructs the agent to prepend for future tasks core.addPath(toolPath); - - try { - let globalToolPath: string = ''; - if (IS_WINDOWS) { - globalToolPath = path.join( - process.env.USERPROFILE || '', - '.dotnet\\tools' - ); - } else { - globalToolPath = path.join(process.env.HOME || '', '.dotnet/tools'); - } - - await io.mkdirP(globalToolPath); - core.addPath(globalToolPath); - } catch (error) { - //nop - } - - // Set DOTNET_ROOT for dotnet core Apphost to find runtime since it is installed to a non well-known location. - core.exportVariable('DOTNET_ROOT', toolPath); } private getLocalTool(): string { @@ -140,7 +119,7 @@ export class DotnetCoreInstaller { } if (resultCode != 0) { - throw `Failed to detect os with result code ${resultCode}`; + throw `Failed to detect os with result code ${resultCode}. Output: ${output}`; } let index; @@ -205,6 +184,7 @@ export class DotnetCoreInstaller { ): Promise { let downloadUrls = []; let releasesJSON = await this.getReleasesJson(); + core.debug('Releases: ' + releasesJSON); let releasesInfo = JSON.parse(await releasesJSON.readBody()); releasesInfo = releasesInfo.filter((releaseInfo: any) => { @@ -320,7 +300,7 @@ export class DotnetCoreInstaller { } if (resultCode != 0) { - throw `Failed to get download urls with result code ${resultCode}`; + throw `Failed to get download urls with result code ${resultCode}. ${output}`; } let primaryUrl: string = '';