mirror of
				https://github.com/actions/setup-dotnet.git
				synced 2025-10-31 23:03:45 +00:00 
			
		
		
		
	Add installer (#1)
* Add installer * Add tests * install-dotnet script should be in externals * merge * Fix tests * Fix naming * Clean up * Feedback
This commit is contained in:
		
							parent
							
								
									9629dab57e
								
							
						
					
					
						commit
						187843cd79
					
				| @ -2,6 +2,7 @@ import io = require('@actions/io'); | |||||||
| import fs = require('fs'); | import fs = require('fs'); | ||||||
| import os = require('os'); | import os = require('os'); | ||||||
| import path = require('path'); | import path = require('path'); | ||||||
|  | import httpClient = require('typed-rest-client/HttpClient'); | ||||||
| 
 | 
 | ||||||
| const toolDir = path.join(__dirname, 'runner', 'tools'); | const toolDir = path.join(__dirname, 'runner', 'tools'); | ||||||
| const tempDir = path.join(__dirname, 'runner', 'temp'); | const tempDir = path.join(__dirname, 'runner', 'temp'); | ||||||
| @ -10,12 +11,117 @@ process.env['RUNNER_TOOLSDIRECTORY'] = toolDir; | |||||||
| process.env['RUNNER_TEMPDIRECTORY'] = tempDir; | process.env['RUNNER_TEMPDIRECTORY'] = tempDir; | ||||||
| import * as installer from '../src/installer'; | import * as installer from '../src/installer'; | ||||||
| 
 | 
 | ||||||
|  | const IS_WINDOWS = process.platform === 'win32'; | ||||||
|  | 
 | ||||||
| describe('installer tests', () => { | describe('installer tests', () => { | ||||||
|   beforeAll(() => {}); |  | ||||||
|   beforeAll(async () => { |   beforeAll(async () => { | ||||||
|     await io.rmRF(toolDir); |     await io.rmRF(toolDir); | ||||||
|     await io.rmRF(tempDir); |     await io.rmRF(tempDir); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   it('TODO - Add tests', async () => {}); |   afterAll(async () => { | ||||||
|  |     try { | ||||||
|  |       await io.rmRF(toolDir); | ||||||
|  |       await io.rmRF(tempDir); | ||||||
|  |     } catch { | ||||||
|  |       console.log('Failed to remove test directories'); | ||||||
|  |     } | ||||||
|  |   }, 100000); | ||||||
|  | 
 | ||||||
|  |   it('Acquires version of dotnet if no matching version is installed', async () => { | ||||||
|  |     await getDotnet('2.2.104'); | ||||||
|  |     const dotnetDir = path.join(toolDir, 'dncs', '2.2.104', os.arch()); | ||||||
|  | 
 | ||||||
|  |     expect(fs.existsSync(`${dotnetDir}.complete`)).toBe(true); | ||||||
|  |     if (IS_WINDOWS) { | ||||||
|  |       expect(fs.existsSync(path.join(dotnetDir, 'dotnet.exe'))).toBe(true); | ||||||
|  |     } else { | ||||||
|  |       expect(fs.existsSync(path.join(dotnetDir, 'dotnet'))).toBe(true); | ||||||
|  |     } | ||||||
|  |   }, 100000); | ||||||
|  | 
 | ||||||
|  |   it('Throws if no location contains correct dotnet version', async () => { | ||||||
|  |     let thrown = false; | ||||||
|  |     try { | ||||||
|  |       await getDotnet('1000.0.0'); | ||||||
|  |     } catch { | ||||||
|  |       thrown = true; | ||||||
|  |     } | ||||||
|  |     expect(thrown).toBe(true); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   it('Uses version of dotnet installed in cache', async () => { | ||||||
|  |     const dotnetDir: string = path.join(toolDir, 'dncs', '250.0.0', os.arch()); | ||||||
|  |     await io.mkdirP(dotnetDir); | ||||||
|  |     fs.writeFileSync(`${dotnetDir}.complete`, 'hello'); | ||||||
|  |     // This will throw if it doesn't find it in the cache (because no such version exists)
 | ||||||
|  |     await getDotnet('250.0.0'); | ||||||
|  |     return; | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   it('Doesnt use version of dotnet that was only partially installed in cache', async () => { | ||||||
|  |     const dotnetDir: string = path.join(toolDir, 'dncs', '251.0.0', os.arch()); | ||||||
|  |     await io.mkdirP(dotnetDir); | ||||||
|  |     let thrown = false; | ||||||
|  |     try { | ||||||
|  |       // This will throw if it doesn't find it in the cache (because no such version exists)
 | ||||||
|  |       await getDotnet('251.0.0'); | ||||||
|  |     } catch { | ||||||
|  |       thrown = true; | ||||||
|  |     } | ||||||
|  |     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<void> { | ||||||
|  |   const dotnetInstaller = new installer.DotnetCoreInstaller(version); | ||||||
|  |   await dotnetInstaller.installDotnet(); | ||||||
|  | } | ||||||
|  | |||||||
							
								
								
									
										192
									
								
								externals/get-os-distro.sh
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										192
									
								
								externals/get-os-distro.sh
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,192 @@ | |||||||
|  | #!/usr/bin/env bash | ||||||
|  | # Copyright (c) .NET Foundation and contributors. All rights reserved. | ||||||
|  | # Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||||||
|  | # | ||||||
|  | 
 | ||||||
|  | # Stop script on NZEC | ||||||
|  | set -e | ||||||
|  | # Stop script if unbound variable found (use ${var:-} if intentional) | ||||||
|  | set -u | ||||||
|  | # By default cmd1 | cmd2 returns exit code of cmd2 regardless of cmd1 success | ||||||
|  | # This is causing it to fail | ||||||
|  | set -o pipefail | ||||||
|  | 
 | ||||||
|  | # Use in the the functions: eval $invocation | ||||||
|  | invocation='say_verbose "Calling: ${yellow:-}${FUNCNAME[0]} ${green:-}$*${normal:-}"' | ||||||
|  | 
 | ||||||
|  | # standard output may be used as a return value in the functions | ||||||
|  | # we need a way to write text on the screen in the functions so that | ||||||
|  | # it won't interfere with the return value. | ||||||
|  | # Exposing stream 3 as a pipe to standard output of the script itself | ||||||
|  | exec 3>&1 | ||||||
|  | 
 | ||||||
|  | say_err() { | ||||||
|  |     printf "%b\n" "get-os-distro: Error: $1" >&2 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # 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() { | ||||||
|  | 
 | ||||||
|  |     platform="$1" | ||||||
|  |     case "$platform" in | ||||||
|  |         "centos.7") | ||||||
|  |             echo "centos" | ||||||
|  |             return 0 | ||||||
|  |             ;; | ||||||
|  |         "debian.8") | ||||||
|  |             echo "debian" | ||||||
|  |             return 0 | ||||||
|  |             ;; | ||||||
|  |         "fedora.23") | ||||||
|  |             echo "fedora.23" | ||||||
|  |             return 0 | ||||||
|  |             ;; | ||||||
|  |         "fedora.27") | ||||||
|  |             echo "fedora.27" | ||||||
|  |             return 0 | ||||||
|  |             ;; | ||||||
|  |         "fedora.24") | ||||||
|  |             echo "fedora.24" | ||||||
|  |             return 0 | ||||||
|  |             ;; | ||||||
|  |         "opensuse.13.2") | ||||||
|  |             echo "opensuse.13.2" | ||||||
|  |             return 0 | ||||||
|  |             ;; | ||||||
|  |         "opensuse.42.1") | ||||||
|  |             echo "opensuse.42.1" | ||||||
|  |             return 0 | ||||||
|  |             ;; | ||||||
|  |         "opensuse.42.3") | ||||||
|  |             echo "opensuse.42.3" | ||||||
|  |             return 0 | ||||||
|  |             ;; | ||||||
|  |         "rhel.7"*) | ||||||
|  |             echo "rhel" | ||||||
|  |             return 0 | ||||||
|  |             ;; | ||||||
|  |         "ubuntu.14.04") | ||||||
|  |             echo "ubuntu" | ||||||
|  |             return 0 | ||||||
|  |             ;; | ||||||
|  |         "ubuntu.16.04") | ||||||
|  |             echo "ubuntu.16.04" | ||||||
|  |             return 0 | ||||||
|  |             ;; | ||||||
|  |         "ubuntu.16.10") | ||||||
|  |             echo "ubuntu.16.10" | ||||||
|  |             return 0 | ||||||
|  |             ;; | ||||||
|  |         "ubuntu.18.04") | ||||||
|  |             echo "ubuntu.18.04" | ||||||
|  |             return 0 | ||||||
|  |             ;; | ||||||
|  |         "alpine.3.4.3") | ||||||
|  |             echo "alpine" | ||||||
|  |             return 0 | ||||||
|  |             ;; | ||||||
|  |     esac | ||||||
|  |     return 1 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | get_linux_platform_name() { | ||||||
|  | 
 | ||||||
|  |     if [ -e /etc/os-release ]; then | ||||||
|  |         . /etc/os-release | ||||||
|  |         echo "$ID.$VERSION_ID" | ||||||
|  |         return 0 | ||||||
|  |     elif [ -e /etc/redhat-release ]; then | ||||||
|  |         local redhatRelease=$(</etc/redhat-release) | ||||||
|  |         if [[ $redhatRelease == "CentOS release 6."* || $redhatRelease == "Red Hat Enterprise Linux Server release 6."* ]]; then | ||||||
|  |             echo "rhel.6" | ||||||
|  |             return 0 | ||||||
|  |         fi | ||||||
|  |     fi | ||||||
|  | 
 | ||||||
|  |     say_err "Linux specific platform name and version could not be detected: UName = $uname" | ||||||
|  |     return 1 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | get_current_os_name() { | ||||||
|  | 
 | ||||||
|  |     local uname=$(uname) | ||||||
|  |     if [ "$uname" = "Darwin" ]; then | ||||||
|  |         echo "mac" | ||||||
|  |         return 0 | ||||||
|  |     elif [ "$uname" = "Linux" ]; then | ||||||
|  |         local linux_platform_name | ||||||
|  |         linux_platform_name="$(get_linux_platform_name)" || { echo "linux" && return 0 ; } | ||||||
|  | 
 | ||||||
|  |         if [[ $linux_platform_name == "rhel.6" ]]; then | ||||||
|  |             echo "$linux_platform_name" | ||||||
|  |             return 0 | ||||||
|  |         elif [[ $linux_platform_name == alpine* ]]; then | ||||||
|  |             echo "linux-musl" | ||||||
|  |             return 0 | ||||||
|  |         else | ||||||
|  |             echo "linux" | ||||||
|  |             return 0 | ||||||
|  |         fi | ||||||
|  |     fi | ||||||
|  | 
 | ||||||
|  |     say_err "OS name could not be detected: UName = $uname" | ||||||
|  |     return 1 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | get_legacy_os_name() { | ||||||
|  | 
 | ||||||
|  |     local uname=$(uname) | ||||||
|  |     if [ "$uname" = "Darwin" ]; then | ||||||
|  |         echo "mac" | ||||||
|  |         return 0 | ||||||
|  |     else | ||||||
|  |         if [ -e /etc/os-release ]; then | ||||||
|  |             . /etc/os-release | ||||||
|  |             os=$(get_legacy_os_name_from_platform "$ID.$VERSION_ID" || echo "") | ||||||
|  |             if [ -n "$os" ]; then | ||||||
|  |                 echo "$os" | ||||||
|  |                 return 0 | ||||||
|  |             fi | ||||||
|  |         fi | ||||||
|  |     fi | ||||||
|  | 
 | ||||||
|  |     say_err "Distribution specific OS name and version could not be detected: UName = $uname" | ||||||
|  |     return 1 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | get_machine_architecture() { | ||||||
|  | 
 | ||||||
|  |     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 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | osName=$(get_current_os_name || echo "") | ||||||
|  | legacyOsName=$(get_legacy_os_name || echo "") | ||||||
|  | arch=$(get_machine_architecture || echo "") | ||||||
|  | 
 | ||||||
|  | primaryName="$osName-$arch" | ||||||
|  | legacyName="$legacyOsName" | ||||||
|  | 
 | ||||||
|  | echo "Primary:$primaryName" | ||||||
|  | echo "Legacy:$legacyName" | ||||||
|  | 
 | ||||||
|  | if [ -z "$osName" ] && [ -z "$legacyOsName" ];then | ||||||
|  |     exit 1 | ||||||
|  | fi | ||||||
							
								
								
									
										18
									
								
								externals/get-os-platform.ps1
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								externals/get-os-platform.ps1
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | |||||||
|  | function Get-Machine-Architecture() | ||||||
|  | { | ||||||
|  |     # possible values: AMD64, IA64, x86 | ||||||
|  |     return $ENV:PROCESSOR_ARCHITECTURE | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function Get-CLIArchitecture-From-Architecture([string]$Architecture) | ||||||
|  | { | ||||||
|  |     switch ($Architecture.ToLower()) | ||||||
|  |     { | ||||||
|  |         { ($_ -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" } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | $CLIArchitecture = Get-CLIArchitecture-From-Architecture $(Get-Machine-Architecture) | ||||||
|  | Write-Output "Primary:win-$CLIArchitecture" | ||||||
							
								
								
									
										638
									
								
								externals/install-dotnet.ps1
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										638
									
								
								externals/install-dotnet.ps1
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,638 @@ | |||||||
|  | # | ||||||
|  | # Copyright (c) .NET Foundation and contributors. All rights reserved. | ||||||
|  | # Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||||||
|  | # | ||||||
|  | 
 | ||||||
|  | <# | ||||||
|  | .SYNOPSIS | ||||||
|  |     Installs dotnet cli | ||||||
|  | .DESCRIPTION | ||||||
|  |     Installs dotnet cli. If dotnet installation already exists in the given directory | ||||||
|  |     it will update it only if the requested version differs from the one already installed. | ||||||
|  | .PARAMETER Channel | ||||||
|  |     Default: LTS | ||||||
|  |     Download from the Channel specified. Possible values: | ||||||
|  |     - 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 | ||||||
|  |     - Branch name | ||||||
|  |           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: | ||||||
|  |     - latest - most latest build on specific channel | ||||||
|  |     - 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 | ||||||
|  | .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: <auto> - this value represents currently running OS architecture | ||||||
|  |     Architecture of dotnet binaries to be installed. | ||||||
|  |     Possible values are: <auto>, 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. | ||||||
|  |     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 | ||||||
|  |     with specific version so that this command can be used deterministicly in a build script. | ||||||
|  |     It also displays binaries location if you prefer to install or download it yourself. | ||||||
|  | .PARAMETER NoPath | ||||||
|  |     By default this script will set environment variable PATH for the current process to the binaries folder inside installation folder. | ||||||
|  |     If set it will display binaries location but not set any environment variable. | ||||||
|  | .PARAMETER Verbose | ||||||
|  |     Displays diagnostics information. | ||||||
|  | .PARAMETER AzureFeed | ||||||
|  |     Default: https://dotnetcli.azureedge.net/dotnet | ||||||
|  |     This parameter typically is not changed by the user. | ||||||
|  |     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 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( | ||||||
|  |    [string]$Channel="LTS", | ||||||
|  |    [string]$Version="Latest", | ||||||
|  |    [string]$InstallDir="<auto>", | ||||||
|  |    [string]$Architecture="<auto>", | ||||||
|  |    [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]$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 = !$SkipNonVersionedFiles | ||||||
|  | 
 | ||||||
|  | function Say($str) { | ||||||
|  |     Write-Host "dotnet-install: $str" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function Say-Verbose($str) { | ||||||
|  |     Write-Verbose "dotnet-install: $str" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function Say-Invocation($Invocation) { | ||||||
|  |     $command = $Invocation.MyCommand; | ||||||
|  |     $args = (($Invocation.BoundParameters.Keys | foreach { "-$_ `"$($Invocation.BoundParameters[$_])`"" }) -join " ") | ||||||
|  |     Say-Verbose "$command $args" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function Invoke-With-Retry([ScriptBlock]$ScriptBlock, [int]$MaxAttempts = 3, [int]$SecondsBetweenAttempts = 1) { | ||||||
|  |     $Attempts = 0 | ||||||
|  | 
 | ||||||
|  |     while ($true) { | ||||||
|  |         try { | ||||||
|  |             return $ScriptBlock.Invoke() | ||||||
|  |         } | ||||||
|  |         catch { | ||||||
|  |             $Attempts++ | ||||||
|  |             if ($Attempts -lt $MaxAttempts) { | ||||||
|  |                 Start-Sleep $SecondsBetweenAttempts | ||||||
|  |             } | ||||||
|  |             else { | ||||||
|  |                 throw | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function Get-Machine-Architecture() { | ||||||
|  |     Say-Invocation $MyInvocation | ||||||
|  | 
 | ||||||
|  |     # possible values: amd64, x64, x86, arm64, arm | ||||||
|  |     return $ENV:PROCESSOR_ARCHITECTURE | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function Get-CLIArchitecture-From-Architecture([string]$Architecture) { | ||||||
|  |     Say-Invocation $MyInvocation | ||||||
|  | 
 | ||||||
|  |     switch ($Architecture.ToLower()) { | ||||||
|  |         { $_ -eq "<auto>" } { return Get-CLIArchitecture-From-Architecture $(Get-Machine-Architecture) } | ||||||
|  |         { ($_ -eq "amd64") -or ($_ -eq "x64") } { return "x64" } | ||||||
|  |         { $_ -eq "x86" } { return "x86" } | ||||||
|  |         { $_ -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 = -split $VersionText | ||||||
|  | 
 | ||||||
|  |     $VersionInfo = @{ | ||||||
|  |         CommitHash = $(if ($Data.Count -gt 1) { $Data[0] }) | ||||||
|  |         Version = $Data[-1] # last line is always the version number. | ||||||
|  |     } | ||||||
|  |     return $VersionInfo | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function Load-Assembly([string] $Assembly) { | ||||||
|  |     try { | ||||||
|  |         Add-Type -Assembly $Assembly | Out-Null | ||||||
|  |     } | ||||||
|  |     catch { | ||||||
|  |         # On Nano Server, Powershell Core Edition is used.  Add-Type is unable to resolve base class assemblies because they are not GAC'd. | ||||||
|  |         # Loading the base class assemblies is not unnecessary as the types will automatically get resolved. | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function GetHTTPResponse([Uri] $Uri) | ||||||
|  | { | ||||||
|  |     Invoke-With-Retry( | ||||||
|  |     { | ||||||
|  | 
 | ||||||
|  |         $HttpClient = $null | ||||||
|  | 
 | ||||||
|  |         try { | ||||||
|  |             # HttpClient is used vs Invoke-WebRequest in order to support Nano Server which doesn't support the Invoke-WebRequest cmdlet. | ||||||
|  |             Load-Assembly -Assembly System.Net.Http | ||||||
|  | 
 | ||||||
|  |             if(-not $ProxyAddress) { | ||||||
|  |                 try { | ||||||
|  |                     # Despite no proxy being explicitly specified, we may still be behind a default proxy | ||||||
|  |                     $DefaultProxy = [System.Net.WebRequest]::DefaultWebProxy; | ||||||
|  |                     if($DefaultProxy -and (-not $DefaultProxy.IsBypassed($Uri))) { | ||||||
|  |                         $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) { | ||||||
|  |                 $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 | ||||||
|  |             # 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) { | ||||||
|  |                     $ErrorMsg += "  $Response" | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 throw $ErrorMsg | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |              return $Response | ||||||
|  |         } | ||||||
|  |         finally { | ||||||
|  |              if ($HttpClient -ne $null) { | ||||||
|  |                 $HttpClient.Dispose() | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | function Get-Latest-Version-Info([string]$AzureFeed, [string]$Channel, [bool]$Coherent) { | ||||||
|  |     Say-Invocation $MyInvocation | ||||||
|  | 
 | ||||||
|  |     $VersionFileUrl = $null | ||||||
|  |     if ($Runtime -eq "dotnet") { | ||||||
|  |         $VersionFileUrl = "$UncachedFeed/Runtime/$Channel/latest.version" | ||||||
|  |     } | ||||||
|  |     elseif ($Runtime -eq "aspnetcore") { | ||||||
|  |         $VersionFileUrl = "$UncachedFeed/aspnetcore/Runtime/$Channel/latest.version" | ||||||
|  |     } | ||||||
|  |     elseif (-not $Runtime) { | ||||||
|  |         if ($Coherent) { | ||||||
|  |             $VersionFileUrl = "$UncachedFeed/Sdk/$Channel/latest.coherent.version" | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |             $VersionFileUrl = "$UncachedFeed/Sdk/$Channel/latest.version" | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     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 = $StringContent } | ||||||
|  |         { ($_ -eq "text/plain") } { $VersionText = $StringContent } | ||||||
|  |         { ($_ -eq "text/plain; charset=UTF-8") } { $VersionText = $StringContent } | ||||||
|  |         default { throw "``$Response.Content.Headers.ContentType`` is an unknown .version file content type." } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     $VersionInfo = Get-Version-Info-From-Version-Text $VersionText | ||||||
|  | 
 | ||||||
|  |     return $VersionInfo | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | function Get-Specific-Version-From-Version([string]$AzureFeed, [string]$Channel, [string]$Version) { | ||||||
|  |     Say-Invocation $MyInvocation | ||||||
|  | 
 | ||||||
|  |     switch ($Version.ToLower()) { | ||||||
|  |         { $_ -eq "latest" } { | ||||||
|  |             $LatestVersionInfo = Get-Latest-Version-Info -AzureFeed $AzureFeed -Channel $Channel -Coherent $False | ||||||
|  |             return $LatestVersionInfo.Version | ||||||
|  |         } | ||||||
|  |         { $_ -eq "coherent" } { | ||||||
|  |             $LatestVersionInfo = Get-Latest-Version-Info -AzureFeed $AzureFeed -Channel $Channel -Coherent $True | ||||||
|  |             return $LatestVersionInfo.Version | ||||||
|  |         } | ||||||
|  |         default { return $Version } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function Get-Download-Link([string]$AzureFeed, [string]$SpecificVersion, [string]$CLIArchitecture) { | ||||||
|  |     Say-Invocation $MyInvocation | ||||||
|  | 
 | ||||||
|  |     if ($Runtime -eq "dotnet") { | ||||||
|  |         $PayloadURL = "$AzureFeed/Runtime/$SpecificVersion/dotnet-runtime-$SpecificVersion-win-$CLIArchitecture.zip" | ||||||
|  |     } | ||||||
|  |     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 named payload URL: $PayloadURL" | ||||||
|  | 
 | ||||||
|  |     return $PayloadURL | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function Get-LegacyDownload-Link([string]$AzureFeed, [string]$SpecificVersion, [string]$CLIArchitecture) { | ||||||
|  |     Say-Invocation $MyInvocation | ||||||
|  | 
 | ||||||
|  |     if (-not $Runtime) { | ||||||
|  |         $PayloadURL = "$AzureFeed/Sdk/$SpecificVersion/dotnet-dev-win-$CLIArchitecture.$SpecificVersion.zip" | ||||||
|  |     } | ||||||
|  |     elseif ($Runtime -eq "dotnet") { | ||||||
|  |         $PayloadURL = "$AzureFeed/Runtime/$SpecificVersion/dotnet-win-$CLIArchitecture.$SpecificVersion.zip" | ||||||
|  |     } | ||||||
|  |     else { | ||||||
|  |         return $null | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     Say-Verbose "Constructed legacy named payload URL: $PayloadURL" | ||||||
|  | 
 | ||||||
|  |     return $PayloadURL | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function Get-User-Share-Path() { | ||||||
|  |     Say-Invocation $MyInvocation | ||||||
|  | 
 | ||||||
|  |     $InstallRoot = $env:DOTNET_INSTALL_DIR | ||||||
|  |     if (!$InstallRoot) { | ||||||
|  |         $InstallRoot = "$env:LocalAppData\Microsoft\dotnet" | ||||||
|  |     } | ||||||
|  |     return $InstallRoot | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function Resolve-Installation-Path([string]$InstallDir) { | ||||||
|  |     Say-Invocation $MyInvocation | ||||||
|  | 
 | ||||||
|  |     if ($InstallDir -eq "<auto>") { | ||||||
|  |         return Get-User-Share-Path | ||||||
|  |     } | ||||||
|  |     return $InstallDir | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function Get-Version-Info-From-Version-File([string]$InstallRoot, [string]$RelativePathToVersionFile) { | ||||||
|  |     Say-Invocation $MyInvocation | ||||||
|  | 
 | ||||||
|  |     $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" | ||||||
|  |         return Get-Version-Info-From-Version-Text $VersionText | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     Say-Verbose "Local version file not found." | ||||||
|  | 
 | ||||||
|  |     return $null | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function Get-Absolute-Path([string]$RelativeOrAbsolutePath) { | ||||||
|  |     # Too much spam | ||||||
|  |     # Say-Invocation $MyInvocation | ||||||
|  | 
 | ||||||
|  |     return $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($RelativeOrAbsolutePath) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function Get-Path-Prefix-With-Version($path) { | ||||||
|  |     $match = [regex]::match($path, $VersionRegEx) | ||||||
|  |     if ($match.Success) { | ||||||
|  |         return $entry.FullName.Substring(0, $match.Index + $match.Length) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return $null | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function Get-List-Of-Directories-And-Versions-To-Unpack-From-Dotnet-Package([System.IO.Compression.ZipArchive]$Zip, [string]$OutPath) { | ||||||
|  |     Say-Invocation $MyInvocation | ||||||
|  | 
 | ||||||
|  |     $ret = @() | ||||||
|  |     foreach ($entry in $Zip.Entries) { | ||||||
|  |         $dir = Get-Path-Prefix-With-Version $entry.FullName | ||||||
|  |         if ($dir -ne $null) { | ||||||
|  |             $path = Get-Absolute-Path $(Join-Path -Path $OutPath -ChildPath $dir) | ||||||
|  |             if (-Not (Test-Path $path -PathType Container)) { | ||||||
|  |                 $ret += $dir | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     $ret = $ret | Sort-Object | Get-Unique | ||||||
|  | 
 | ||||||
|  |     $values = ($ret | foreach { "$_" }) -join ";" | ||||||
|  |     Say-Verbose "Directories to unpack: $values" | ||||||
|  | 
 | ||||||
|  |     return $ret | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # Example zip content and extraction algorithm: | ||||||
|  | # Rule: files if extracted are always being extracted to the same relative path locally | ||||||
|  | # .\ | ||||||
|  | #       a.exe   # file does not exist locally, extract | ||||||
|  | #       b.dll   # file exists locally, override only if $OverrideFiles set | ||||||
|  | #       aaa\    # same rules as for files | ||||||
|  | #           ... | ||||||
|  | #       abc\1.0.0\  # directory contains version and exists locally | ||||||
|  | #           ...     # do not extract content under versioned part | ||||||
|  | #       abc\asd\    # same rules as for files | ||||||
|  | #            ... | ||||||
|  | #       def\ghi\1.0.1\  # directory contains version and does not exist locally | ||||||
|  | #           ...         # extract content | ||||||
|  | function Extract-Dotnet-Package([string]$ZipPath, [string]$OutPath) { | ||||||
|  |     Say-Invocation $MyInvocation | ||||||
|  | 
 | ||||||
|  |     Load-Assembly -Assembly System.IO.Compression.FileSystem | ||||||
|  |     Set-Variable -Name Zip | ||||||
|  |     try { | ||||||
|  |         $Zip = [System.IO.Compression.ZipFile]::OpenRead($ZipPath) | ||||||
|  | 
 | ||||||
|  |         $DirectoriesToUnpack = Get-List-Of-Directories-And-Versions-To-Unpack-From-Dotnet-Package -Zip $Zip -OutPath $OutPath | ||||||
|  | 
 | ||||||
|  |         foreach ($entry in $Zip.Entries) { | ||||||
|  |             $PathWithVersion = Get-Path-Prefix-With-Version $entry.FullName | ||||||
|  |             if (($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)) | ||||||
|  |                 if ((-Not $DestinationPath.EndsWith("\")) -And $OverrideFiles) { | ||||||
|  |                     New-Item -ItemType Directory -Force -Path $DestinationDir | Out-Null | ||||||
|  |                     [System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $DestinationPath, $OverrideNonVersionedFiles) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     finally { | ||||||
|  |         if ($Zip -ne $null) { | ||||||
|  |             $Zip.Dispose() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function DownloadFile($Source, [string]$OutPath) { | ||||||
|  |     if ($Source -notlike "http*") { | ||||||
|  |         #  Using System.IO.Path.GetFullPath to get the current directory | ||||||
|  |         #    does not work in this context - $pwd gives the current directory | ||||||
|  |         if (![System.IO.Path]::IsPathRooted($Source)) { | ||||||
|  |             $Source = $(Join-Path -Path $pwd -ChildPath $Source) | ||||||
|  |         } | ||||||
|  |         $Source = Get-Absolute-Path $Source | ||||||
|  |         Say "Copying file from $Source to $OutPath" | ||||||
|  |         Copy-Item $Source $OutPath | ||||||
|  |         return | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     $Stream = $null | ||||||
|  | 
 | ||||||
|  |     try { | ||||||
|  |         $Response = GetHTTPResponse -Uri $Source | ||||||
|  |         $Stream = $Response.Content.ReadAsStreamAsync().Result | ||||||
|  |         $File = [System.IO.File]::Create($OutPath) | ||||||
|  |         $Stream.CopyTo($File) | ||||||
|  |         $File.Close() | ||||||
|  |     } | ||||||
|  |     finally { | ||||||
|  |         if ($Stream -ne $null) { | ||||||
|  |             $Stream.Dispose() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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)) { | ||||||
|  |             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" | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | $CLIArchitecture = Get-CLIArchitecture-From-Architecture $Architecture | ||||||
|  | $SpecificVersion = Get-Specific-Version-From-Version -AzureFeed $AzureFeed -Channel $Channel -Version $Version | ||||||
|  | $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 | ||||||
|  | 
 | ||||||
|  | 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 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | New-Item -ItemType Directory -Force -Path $InstallRoot | Out-Null | ||||||
|  | 
 | ||||||
|  | $installDrive = $((Get-Item $InstallRoot).PSDrive.Name); | ||||||
|  | $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]::combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) | ||||||
|  | Say-Verbose "Zip path: $ZipPath" | ||||||
|  | 
 | ||||||
|  | $DownloadFailed = $false | ||||||
|  | Say "Downloading link: $DownloadLink" | ||||||
|  | try { | ||||||
|  |     DownloadFile -Source $DownloadLink -OutPath $ZipPath | ||||||
|  | } | ||||||
|  | catch { | ||||||
|  |     Say "Cannot download: $DownloadLink" | ||||||
|  |     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 | ||||||
							
								
								
									
										1019
									
								
								externals/install-dotnet.sh
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1019
									
								
								externals/install-dotnet.sh
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										317
									
								
								lib/installer.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										317
									
								
								lib/installer.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,317 @@ | |||||||
|  | "use strict"; | ||||||
|  | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||||||
|  |     return new (P || (P = Promise))(function (resolve, reject) { | ||||||
|  |         function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||||||
|  |         function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||||||
|  |         function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } | ||||||
|  |         step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||||||
|  |     }); | ||||||
|  | }; | ||||||
|  | var __importStar = (this && this.__importStar) || function (mod) { | ||||||
|  |     if (mod && mod.__esModule) return mod; | ||||||
|  |     var result = {}; | ||||||
|  |     if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; | ||||||
|  |     result["default"] = mod; | ||||||
|  |     return result; | ||||||
|  | }; | ||||||
|  | Object.defineProperty(exports, "__esModule", { value: true }); | ||||||
|  | // Load tempDirectory before it gets wiped by tool-cache
 | ||||||
|  | let tempDirectory = process.env['RUNNER_TEMPDIRECTORY'] || ''; | ||||||
|  | const core = __importStar(require("@actions/core")); | ||||||
|  | const exec = __importStar(require("@actions/exec")); | ||||||
|  | const io = __importStar(require("@actions/io")); | ||||||
|  | const tc = __importStar(require("@actions/tool-cache")); | ||||||
|  | const httpClient = require("typed-rest-client/HttpClient"); | ||||||
|  | const fs_1 = require("fs"); | ||||||
|  | const os = __importStar(require("os")); | ||||||
|  | const path = __importStar(require("path")); | ||||||
|  | const semver = __importStar(require("semver")); | ||||||
|  | const util = __importStar(require("util")); | ||||||
|  | const IS_WINDOWS = process.platform === 'win32'; | ||||||
|  | if (!tempDirectory) { | ||||||
|  |     let baseLocation; | ||||||
|  |     if (IS_WINDOWS) { | ||||||
|  |         // On windows use the USERPROFILE env variable
 | ||||||
|  |         baseLocation = process.env['USERPROFILE'] || 'C:\\'; | ||||||
|  |     } | ||||||
|  |     else { | ||||||
|  |         if (process.platform === 'darwin') { | ||||||
|  |             baseLocation = '/Users'; | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |             baseLocation = '/home'; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     tempDirectory = path.join(baseLocation, 'actions', 'temp'); | ||||||
|  | } | ||||||
|  | class DotnetCoreInstaller { | ||||||
|  |     constructor(version) { | ||||||
|  |         if (semver.valid(semver.clean(version) || '') == null) { | ||||||
|  |             throw 'Implicit version not permitted'; | ||||||
|  |         } | ||||||
|  |         this.version = version; | ||||||
|  |         this.cachedToolName = 'dncs'; | ||||||
|  |         this.arch = 'x64'; | ||||||
|  |     } | ||||||
|  |     installDotnet() { | ||||||
|  |         return __awaiter(this, void 0, void 0, function* () { | ||||||
|  |             // Check cache
 | ||||||
|  |             let toolPath; | ||||||
|  |             let osSuffixes = yield this.detectMachineOS(); | ||||||
|  |             let parts = osSuffixes[0].split('-'); | ||||||
|  |             if (parts.length > 1) { | ||||||
|  |                 this.arch = parts[1]; | ||||||
|  |             } | ||||||
|  |             toolPath = this.getLocalTool(); | ||||||
|  |             if (!toolPath) { | ||||||
|  |                 // download, extract, cache
 | ||||||
|  |                 console.log('Getting a download url', this.version); | ||||||
|  |                 let downloadUrls = yield this.getDownloadUrls(osSuffixes, this.version); | ||||||
|  |                 toolPath = yield this.downloadAndInstall(downloadUrls); | ||||||
|  |             } | ||||||
|  |             else { | ||||||
|  |                 console.log('Using cached tool'); | ||||||
|  |             } | ||||||
|  |             // Prepend the tools path. instructs the agent to prepend for future tasks
 | ||||||
|  |             core.addPath(toolPath); | ||||||
|  |             try { | ||||||
|  |                 let globalToolPath = ''; | ||||||
|  |                 if (IS_WINDOWS) { | ||||||
|  |                     globalToolPath = path.join(process.env.USERPROFILE || '', '.dotnet\\tools'); | ||||||
|  |                 } | ||||||
|  |                 else { | ||||||
|  |                     globalToolPath = path.join(process.env.HOME || '', '.dotnet/tools'); | ||||||
|  |                 } | ||||||
|  |                 yield 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); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |     getLocalTool() { | ||||||
|  |         console.log('Checking tool cache'); | ||||||
|  |         return tc.find(this.cachedToolName, this.version, this.arch); | ||||||
|  |     } | ||||||
|  |     detectMachineOS() { | ||||||
|  |         return __awaiter(this, void 0, void 0, function* () { | ||||||
|  |             let osSuffix = []; | ||||||
|  |             let output = ''; | ||||||
|  |             let resultCode = 0; | ||||||
|  |             if (IS_WINDOWS) { | ||||||
|  |                 let escapedScript = path | ||||||
|  |                     .join(__dirname, '..', 'externals', 'get-os-platform.ps1') | ||||||
|  |                     .replace(/'/g, "''"); | ||||||
|  |                 let command = `& '${escapedScript}'`; | ||||||
|  |                 const powershellPath = yield io.which('powershell', true); | ||||||
|  |                 resultCode = yield exec.exec(`"${powershellPath}"`, [ | ||||||
|  |                     '-NoLogo', | ||||||
|  |                     '-Sta', | ||||||
|  |                     '-NoProfile', | ||||||
|  |                     '-NonInteractive', | ||||||
|  |                     '-ExecutionPolicy', | ||||||
|  |                     'Unrestricted', | ||||||
|  |                     '-Command', | ||||||
|  |                     command | ||||||
|  |                 ], { | ||||||
|  |                     listeners: { | ||||||
|  |                         stdout: (data) => { | ||||||
|  |                             output += data.toString(); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 }); | ||||||
|  |             } | ||||||
|  |             else { | ||||||
|  |                 let scriptPath = path.join(__dirname, '..', 'externals', 'get-os-distro.sh'); | ||||||
|  |                 fs_1.chmodSync(scriptPath, '777'); | ||||||
|  |                 const toolPath = yield io.which(scriptPath, true); | ||||||
|  |                 resultCode = yield exec.exec(`"${toolPath}"`, [], { | ||||||
|  |                     listeners: { | ||||||
|  |                         stdout: (data) => { | ||||||
|  |                             output += data.toString(); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 }); | ||||||
|  |             } | ||||||
|  |             if (resultCode != 0) { | ||||||
|  |                 throw `Failed to detect os with result code ${resultCode}`; | ||||||
|  |             } | ||||||
|  |             let index; | ||||||
|  |             if ((index = output.indexOf('Primary:')) >= 0) { | ||||||
|  |                 let primary = output.substr(index + 'Primary:'.length).split(os.EOL)[0]; | ||||||
|  |                 osSuffix.push(primary); | ||||||
|  |             } | ||||||
|  |             if ((index = output.indexOf('Legacy:')) >= 0) { | ||||||
|  |                 let legacy = output.substr(index + 'Legacy:'.length).split(os.EOL)[0]; | ||||||
|  |                 osSuffix.push(legacy); | ||||||
|  |             } | ||||||
|  |             if (osSuffix.length == 0) { | ||||||
|  |                 throw 'Could not detect platform'; | ||||||
|  |             } | ||||||
|  |             return osSuffix; | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |     downloadAndInstall(downloadUrls) { | ||||||
|  |         return __awaiter(this, void 0, void 0, function* () { | ||||||
|  |             let downloaded = false; | ||||||
|  |             let downloadPath = ''; | ||||||
|  |             for (const url of downloadUrls) { | ||||||
|  |                 try { | ||||||
|  |                     downloadPath = yield tc.downloadTool(url); | ||||||
|  |                     downloaded = true; | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |                 catch (error) { | ||||||
|  |                     console.log('Could Not Download', url, JSON.stringify(error)); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             if (!downloaded) { | ||||||
|  |                 throw 'Failed to download package'; | ||||||
|  |             } | ||||||
|  |             // extract
 | ||||||
|  |             console.log('Extracting Package', downloadPath); | ||||||
|  |             let extPath = IS_WINDOWS | ||||||
|  |                 ? yield tc.extractZip(downloadPath) | ||||||
|  |                 : yield tc.extractTar(downloadPath); | ||||||
|  |             // cache tool
 | ||||||
|  |             console.log('Caching tool'); | ||||||
|  |             let cachedDir = yield tc.cacheDir(extPath, this.cachedToolName, this.version, this.arch); | ||||||
|  |             console.log('Successfully installed', this.version); | ||||||
|  |             return cachedDir; | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |     // OsSuffixes - The suffix which is a part of the file name ex- linux-x64, windows-x86
 | ||||||
|  |     // Type - SDK / Runtime
 | ||||||
|  |     // Version - Version of the SDK/Runtime
 | ||||||
|  |     getDownloadUrls(osSuffixes, version) { | ||||||
|  |         return __awaiter(this, void 0, void 0, function* () { | ||||||
|  |             let downloadUrls = []; | ||||||
|  |             let releasesJSON = yield this.getReleasesJson(); | ||||||
|  |             let releasesInfo = JSON.parse(yield releasesJSON.readBody()); | ||||||
|  |             releasesInfo = releasesInfo.filter((releaseInfo) => { | ||||||
|  |                 return (releaseInfo['version-sdk'] === version || | ||||||
|  |                     releaseInfo['version-sdk-display'] === version); | ||||||
|  |             }); | ||||||
|  |             if (releasesInfo.length != 0) { | ||||||
|  |                 let release = releasesInfo[0]; | ||||||
|  |                 let blobUrl = release['blob-sdk']; | ||||||
|  |                 let dlcUrl = release['dlc--sdk']; | ||||||
|  |                 let fileName = release['sdk-' + osSuffixes[0]] | ||||||
|  |                     ? release['sdk-' + osSuffixes[0]] | ||||||
|  |                     : release['sdk-' + osSuffixes[1]]; | ||||||
|  |                 if (!!fileName) { | ||||||
|  |                     fileName = fileName.trim(); | ||||||
|  |                     // For some latest version, the filename itself can be full download url.
 | ||||||
|  |                     // Do a very basic check for url(instead of regex) as the url is only for downloading and
 | ||||||
|  |                     // is coming from .net core releases json and not some ransom user input
 | ||||||
|  |                     if (fileName.toLowerCase().startsWith('https://')) { | ||||||
|  |                         downloadUrls.push(fileName); | ||||||
|  |                     } | ||||||
|  |                     else { | ||||||
|  |                         if (!!blobUrl) { | ||||||
|  |                             downloadUrls.push(util.format('%s%s', blobUrl.trim(), fileName)); | ||||||
|  |                         } | ||||||
|  |                         if (!!dlcUrl) { | ||||||
|  |                             downloadUrls.push(util.format('%s%s', dlcUrl.trim(), fileName)); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 else { | ||||||
|  |                     throw `The specified version's download links are not correctly formed in the supported versions document => ${DotNetCoreReleasesUrl}`; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             else { | ||||||
|  |                 console.log(`Could not fetch download information for version ${version}`); | ||||||
|  |                 downloadUrls = yield this.getFallbackDownloadUrls(version); | ||||||
|  |             } | ||||||
|  |             if (downloadUrls.length == 0) { | ||||||
|  |                 throw `Could not construct download URL. Please ensure that specified version ${version} is valid.`; | ||||||
|  |             } | ||||||
|  |             return downloadUrls; | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |     getReleasesJson() { | ||||||
|  |         var httpCallbackClient = new httpClient.HttpClient('setup-dotnet', [], {}); | ||||||
|  |         return httpCallbackClient.get(DotNetCoreReleasesUrl); | ||||||
|  |     } | ||||||
|  |     getFallbackDownloadUrls(version) { | ||||||
|  |         return __awaiter(this, void 0, void 0, function* () { | ||||||
|  |             let primaryUrlSearchString; | ||||||
|  |             let legacyUrlSearchString; | ||||||
|  |             let output = ''; | ||||||
|  |             let resultCode = 0; | ||||||
|  |             if (IS_WINDOWS) { | ||||||
|  |                 let escapedScript = path | ||||||
|  |                     .join(__dirname, '..', 'externals', 'install-dotnet.ps1') | ||||||
|  |                     .replace(/'/g, "''"); | ||||||
|  |                 let command = `& '${escapedScript}' -Version ${version} -DryRun`; | ||||||
|  |                 const powershellPath = yield io.which('powershell', true); | ||||||
|  |                 resultCode = yield exec.exec(`"${powershellPath}"`, [ | ||||||
|  |                     '-NoLogo', | ||||||
|  |                     '-Sta', | ||||||
|  |                     '-NoProfile', | ||||||
|  |                     '-NonInteractive', | ||||||
|  |                     '-ExecutionPolicy', | ||||||
|  |                     'Unrestricted', | ||||||
|  |                     '-Command', | ||||||
|  |                     command | ||||||
|  |                 ], { | ||||||
|  |                     listeners: { | ||||||
|  |                         stdout: (data) => { | ||||||
|  |                             output += data.toString(); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 }); | ||||||
|  |                 primaryUrlSearchString = 'dotnet-install: Primary - '; | ||||||
|  |                 legacyUrlSearchString = 'dotnet-install: Legacy - '; | ||||||
|  |             } | ||||||
|  |             else { | ||||||
|  |                 let escapedScript = path | ||||||
|  |                     .join(__dirname, '..', 'externals', 'install-dotnet.sh') | ||||||
|  |                     .replace(/'/g, "''"); | ||||||
|  |                 fs_1.chmodSync(escapedScript, '777'); | ||||||
|  |                 const scriptPath = yield io.which(escapedScript, true); | ||||||
|  |                 resultCode = yield exec.exec(`"${scriptPath}"`, ['--version', version, '--dry-run'], { | ||||||
|  |                     listeners: { | ||||||
|  |                         stdout: (data) => { | ||||||
|  |                             output += data.toString(); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 }); | ||||||
|  |                 primaryUrlSearchString = 'dotnet-install: Payload URL: '; | ||||||
|  |                 legacyUrlSearchString = 'dotnet-install: Legacy payload URL: '; | ||||||
|  |             } | ||||||
|  |             if (resultCode != 0) { | ||||||
|  |                 throw `Failed to get download urls with result code ${resultCode}`; | ||||||
|  |             } | ||||||
|  |             let primaryUrl = ''; | ||||||
|  |             let legacyUrl = ''; | ||||||
|  |             if (!!output && output.length > 0) { | ||||||
|  |                 let lines = output.split(os.EOL); | ||||||
|  |                 if (!!lines && lines.length > 0) { | ||||||
|  |                     lines.forEach((line) => { | ||||||
|  |                         if (!line) { | ||||||
|  |                             return; | ||||||
|  |                         } | ||||||
|  |                         var primarySearchStringIndex = line.indexOf(primaryUrlSearchString); | ||||||
|  |                         if (primarySearchStringIndex > -1) { | ||||||
|  |                             primaryUrl = line.substring(primarySearchStringIndex + primaryUrlSearchString.length); | ||||||
|  |                             return; | ||||||
|  |                         } | ||||||
|  |                         var legacySearchStringIndex = line.indexOf(legacyUrlSearchString); | ||||||
|  |                         if (legacySearchStringIndex > -1) { | ||||||
|  |                             legacyUrl = line.substring(legacySearchStringIndex + legacyUrlSearchString.length); | ||||||
|  |                             return; | ||||||
|  |                         } | ||||||
|  |                     }); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             return [primaryUrl, legacyUrl]; | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | exports.DotnetCoreInstaller = DotnetCoreInstaller; | ||||||
|  | const DotNetCoreReleasesUrl = 'https://raw.githubusercontent.com/dotnet/core/master/release-notes/releases.json'; | ||||||
							
								
								
									
										40
									
								
								lib/setup-dotnet.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								lib/setup-dotnet.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,40 @@ | |||||||
|  | "use strict"; | ||||||
|  | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||||||
|  |     return new (P || (P = Promise))(function (resolve, reject) { | ||||||
|  |         function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||||||
|  |         function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||||||
|  |         function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } | ||||||
|  |         step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||||||
|  |     }); | ||||||
|  | }; | ||||||
|  | var __importStar = (this && this.__importStar) || function (mod) { | ||||||
|  |     if (mod && mod.__esModule) return mod; | ||||||
|  |     var result = {}; | ||||||
|  |     if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; | ||||||
|  |     result["default"] = mod; | ||||||
|  |     return result; | ||||||
|  | }; | ||||||
|  | Object.defineProperty(exports, "__esModule", { value: true }); | ||||||
|  | const core = __importStar(require("@actions/core")); | ||||||
|  | const installer = __importStar(require("./installer")); | ||||||
|  | function run() { | ||||||
|  |     return __awaiter(this, void 0, void 0, function* () { | ||||||
|  |         try { | ||||||
|  |             //
 | ||||||
|  |             // Version is optional.  If supplied, install / use from the tool cache
 | ||||||
|  |             // If not supplied then task is still used to setup proxy, auth, etc...
 | ||||||
|  |             //
 | ||||||
|  |             const version = core.getInput('version'); | ||||||
|  |             if (version) { | ||||||
|  |                 const dotnetInstaller = new installer.DotnetCoreInstaller(version); | ||||||
|  |                 yield dotnetInstaller.installDotnet(); | ||||||
|  |             } | ||||||
|  |             // TODO: setup proxy from runner proxy config
 | ||||||
|  |             // TODO: problem matchers registered
 | ||||||
|  |         } | ||||||
|  |         catch (error) { | ||||||
|  |             core.setFailed(error.message); | ||||||
|  |         } | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | run(); | ||||||
							
								
								
									
										343
									
								
								src/installer.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										343
									
								
								src/installer.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,343 @@ | |||||||
|  | // Load tempDirectory before it gets wiped by tool-cache
 | ||||||
|  | let tempDirectory = process.env['RUNNER_TEMPDIRECTORY'] || ''; | ||||||
|  | import * as core from '@actions/core'; | ||||||
|  | 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 {HttpClientResponse} from 'typed-rest-client/HttpClient'; | ||||||
|  | import {chmodSync} from 'fs'; | ||||||
|  | import * as os from 'os'; | ||||||
|  | import * as path from 'path'; | ||||||
|  | import * as semver from 'semver'; | ||||||
|  | import * as util from 'util'; | ||||||
|  | 
 | ||||||
|  | const IS_WINDOWS = process.platform === 'win32'; | ||||||
|  | 
 | ||||||
|  | if (!tempDirectory) { | ||||||
|  |   let baseLocation; | ||||||
|  |   if (IS_WINDOWS) { | ||||||
|  |     // On windows use the USERPROFILE env variable
 | ||||||
|  |     baseLocation = process.env['USERPROFILE'] || 'C:\\'; | ||||||
|  |   } else { | ||||||
|  |     if (process.platform === 'darwin') { | ||||||
|  |       baseLocation = '/Users'; | ||||||
|  |     } else { | ||||||
|  |       baseLocation = '/home'; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   tempDirectory = path.join(baseLocation, 'actions', 'temp'); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class DotnetCoreInstaller { | ||||||
|  |   constructor(version: string) { | ||||||
|  |     if (semver.valid(semver.clean(version) || '') == null) { | ||||||
|  |       throw 'Implicit version not permitted'; | ||||||
|  |     } | ||||||
|  |     this.version = version; | ||||||
|  |     this.cachedToolName = 'dncs'; | ||||||
|  |     this.arch = 'x64'; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   public async installDotnet() { | ||||||
|  |     // Check cache
 | ||||||
|  |     let toolPath: string; | ||||||
|  |     let osSuffixes = await this.detectMachineOS(); | ||||||
|  |     let parts = osSuffixes[0].split('-'); | ||||||
|  |     if (parts.length > 1) { | ||||||
|  |       this.arch = parts[1]; | ||||||
|  |     } | ||||||
|  |     toolPath = this.getLocalTool(); | ||||||
|  | 
 | ||||||
|  |     if (!toolPath) { | ||||||
|  |       // download, extract, cache
 | ||||||
|  |       console.log('Getting a download url', this.version); | ||||||
|  |       let downloadUrls = await this.getDownloadUrls(osSuffixes, this.version); | ||||||
|  |       toolPath = await this.downloadAndInstall(downloadUrls); | ||||||
|  |     } else { | ||||||
|  |       console.log('Using cached tool'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Prepend the tools path. instructs the agent to prepend for future tasks
 | ||||||
|  |     core.addPath(toolPath); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private getLocalTool(): string { | ||||||
|  |     console.log('Checking tool cache'); | ||||||
|  |     return tc.find(this.cachedToolName, this.version, this.arch); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private async detectMachineOS(): Promise<string[]> { | ||||||
|  |     let osSuffix = []; | ||||||
|  |     let output = ''; | ||||||
|  | 
 | ||||||
|  |     let resultCode = 0; | ||||||
|  |     if (IS_WINDOWS) { | ||||||
|  |       let escapedScript = path | ||||||
|  |         .join(__dirname, '..', 'externals', 'get-os-platform.ps1') | ||||||
|  |         .replace(/'/g, "''"); | ||||||
|  |       let command = `& '${escapedScript}'`; | ||||||
|  | 
 | ||||||
|  |       const powershellPath = await io.which('powershell', true); | ||||||
|  |       resultCode = await exec.exec( | ||||||
|  |         `"${powershellPath}"`, | ||||||
|  |         [ | ||||||
|  |           '-NoLogo', | ||||||
|  |           '-Sta', | ||||||
|  |           '-NoProfile', | ||||||
|  |           '-NonInteractive', | ||||||
|  |           '-ExecutionPolicy', | ||||||
|  |           'Unrestricted', | ||||||
|  |           '-Command', | ||||||
|  |           command | ||||||
|  |         ], | ||||||
|  |         { | ||||||
|  |           listeners: { | ||||||
|  |             stdout: (data: Buffer) => { | ||||||
|  |               output += data.toString(); | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       ); | ||||||
|  |     } else { | ||||||
|  |       let scriptPath = path.join( | ||||||
|  |         __dirname, | ||||||
|  |         '..', | ||||||
|  |         'externals', | ||||||
|  |         'get-os-distro.sh' | ||||||
|  |       ); | ||||||
|  |       chmodSync(scriptPath, '777'); | ||||||
|  | 
 | ||||||
|  |       const toolPath = await io.which(scriptPath, true); | ||||||
|  |       resultCode = await exec.exec(`"${toolPath}"`, [], { | ||||||
|  |         listeners: { | ||||||
|  |           stdout: (data: Buffer) => { | ||||||
|  |             output += data.toString(); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (resultCode != 0) { | ||||||
|  |       throw `Failed to detect os with result code ${resultCode}. Output: ${output}`; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let index; | ||||||
|  |     if ((index = output.indexOf('Primary:')) >= 0) { | ||||||
|  |       let primary = output.substr(index + 'Primary:'.length).split(os.EOL)[0]; | ||||||
|  |       osSuffix.push(primary); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if ((index = output.indexOf('Legacy:')) >= 0) { | ||||||
|  |       let legacy = output.substr(index + 'Legacy:'.length).split(os.EOL)[0]; | ||||||
|  |       osSuffix.push(legacy); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (osSuffix.length == 0) { | ||||||
|  |       throw 'Could not detect platform'; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return osSuffix; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private async downloadAndInstall(downloadUrls: string[]) { | ||||||
|  |     let downloaded = false; | ||||||
|  |     let downloadPath = ''; | ||||||
|  |     for (const url of downloadUrls) { | ||||||
|  |       try { | ||||||
|  |         downloadPath = await tc.downloadTool(url); | ||||||
|  |         downloaded = true; | ||||||
|  |         break; | ||||||
|  |       } catch (error) { | ||||||
|  |         console.log('Could Not Download', url, JSON.stringify(error)); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (!downloaded) { | ||||||
|  |       throw 'Failed to download package'; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // extract
 | ||||||
|  |     console.log('Extracting Package', downloadPath); | ||||||
|  |     let extPath: string = IS_WINDOWS | ||||||
|  |       ? await tc.extractZip(downloadPath) | ||||||
|  |       : await tc.extractTar(downloadPath); | ||||||
|  | 
 | ||||||
|  |     // cache tool
 | ||||||
|  |     console.log('Caching tool'); | ||||||
|  |     let cachedDir = await tc.cacheDir( | ||||||
|  |       extPath, | ||||||
|  |       this.cachedToolName, | ||||||
|  |       this.version, | ||||||
|  |       this.arch | ||||||
|  |     ); | ||||||
|  |     console.log('Successfully installed', this.version); | ||||||
|  |     return cachedDir; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // OsSuffixes - The suffix which is a part of the file name ex- linux-x64, windows-x86
 | ||||||
|  |   // Type - SDK / Runtime
 | ||||||
|  |   // Version - Version of the SDK/Runtime
 | ||||||
|  |   private async getDownloadUrls( | ||||||
|  |     osSuffixes: string[], | ||||||
|  |     version: string | ||||||
|  |   ): Promise<string[]> { | ||||||
|  |     let downloadUrls = []; | ||||||
|  |     let releasesJSON = await this.getReleasesJson(); | ||||||
|  |     core.debug('Releases: ' + releasesJSON); | ||||||
|  | 
 | ||||||
|  |     let releasesInfo = JSON.parse(await releasesJSON.readBody()); | ||||||
|  |     releasesInfo = releasesInfo.filter((releaseInfo: any) => { | ||||||
|  |       return ( | ||||||
|  |         releaseInfo['version-sdk'] === version || | ||||||
|  |         releaseInfo['version-sdk-display'] === version | ||||||
|  |       ); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     if (releasesInfo.length != 0) { | ||||||
|  |       let release = releasesInfo[0]; | ||||||
|  |       let blobUrl: string = release['blob-sdk']; | ||||||
|  |       let dlcUrl: string = release['dlc--sdk']; | ||||||
|  |       let fileName: string = release['sdk-' + osSuffixes[0]] | ||||||
|  |         ? release['sdk-' + osSuffixes[0]] | ||||||
|  |         : release['sdk-' + osSuffixes[1]]; | ||||||
|  | 
 | ||||||
|  |       if (!!fileName) { | ||||||
|  |         fileName = fileName.trim(); | ||||||
|  |         // For some latest version, the filename itself can be full download url.
 | ||||||
|  |         // Do a very basic check for url(instead of regex) as the url is only for downloading and
 | ||||||
|  |         // is coming from .net core releases json and not some ransom user input
 | ||||||
|  |         if (fileName.toLowerCase().startsWith('https://')) { | ||||||
|  |           downloadUrls.push(fileName); | ||||||
|  |         } else { | ||||||
|  |           if (!!blobUrl) { | ||||||
|  |             downloadUrls.push(util.format('%s%s', blobUrl.trim(), fileName)); | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |           if (!!dlcUrl) { | ||||||
|  |             downloadUrls.push(util.format('%s%s', dlcUrl.trim(), fileName)); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } else { | ||||||
|  |         throw `The specified version's download links are not correctly formed in the supported versions document => ${DotNetCoreReleasesUrl}`; | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       console.log( | ||||||
|  |         `Could not fetch download information for version ${version}` | ||||||
|  |       ); | ||||||
|  |       downloadUrls = await this.getFallbackDownloadUrls(version); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (downloadUrls.length == 0) { | ||||||
|  |       throw `Could not construct download URL. Please ensure that specified version ${version} is valid.`; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return downloadUrls; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private getReleasesJson(): Promise<HttpClientResponse> { | ||||||
|  |     var httpCallbackClient = new httpClient.HttpClient('setup-dotnet', [], {}); | ||||||
|  |     return httpCallbackClient.get(DotNetCoreReleasesUrl); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private async getFallbackDownloadUrls(version: string): Promise<string[]> { | ||||||
|  |     let primaryUrlSearchString: string; | ||||||
|  |     let legacyUrlSearchString: string; | ||||||
|  |     let output = ''; | ||||||
|  |     let resultCode = 0; | ||||||
|  | 
 | ||||||
|  |     if (IS_WINDOWS) { | ||||||
|  |       let escapedScript = path | ||||||
|  |         .join(__dirname, '..', 'externals', 'install-dotnet.ps1') | ||||||
|  |         .replace(/'/g, "''"); | ||||||
|  |       let command = `& '${escapedScript}' -Version ${version} -DryRun`; | ||||||
|  | 
 | ||||||
|  |       const powershellPath = await io.which('powershell', true); | ||||||
|  |       resultCode = await exec.exec( | ||||||
|  |         `"${powershellPath}"`, | ||||||
|  |         [ | ||||||
|  |           '-NoLogo', | ||||||
|  |           '-Sta', | ||||||
|  |           '-NoProfile', | ||||||
|  |           '-NonInteractive', | ||||||
|  |           '-ExecutionPolicy', | ||||||
|  |           'Unrestricted', | ||||||
|  |           '-Command', | ||||||
|  |           command | ||||||
|  |         ], | ||||||
|  |         { | ||||||
|  |           listeners: { | ||||||
|  |             stdout: (data: Buffer) => { | ||||||
|  |               output += data.toString(); | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       ); | ||||||
|  | 
 | ||||||
|  |       primaryUrlSearchString = 'dotnet-install: Primary - '; | ||||||
|  |       legacyUrlSearchString = 'dotnet-install: Legacy - '; | ||||||
|  |     } else { | ||||||
|  |       let escapedScript = path | ||||||
|  |         .join(__dirname, '..', 'externals', 'install-dotnet.sh') | ||||||
|  |         .replace(/'/g, "''"); | ||||||
|  |       chmodSync(escapedScript, '777'); | ||||||
|  | 
 | ||||||
|  |       const scriptPath = await io.which(escapedScript, true); | ||||||
|  |       resultCode = await exec.exec( | ||||||
|  |         `"${scriptPath}"`, | ||||||
|  |         ['--version', version, '--dry-run'], | ||||||
|  |         { | ||||||
|  |           listeners: { | ||||||
|  |             stdout: (data: Buffer) => { | ||||||
|  |               output += data.toString(); | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       ); | ||||||
|  | 
 | ||||||
|  |       primaryUrlSearchString = 'dotnet-install: Payload URL: '; | ||||||
|  |       legacyUrlSearchString = 'dotnet-install: Legacy payload URL: '; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (resultCode != 0) { | ||||||
|  |       throw `Failed to get download urls with result code ${resultCode}. ${output}`; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let primaryUrl: string = ''; | ||||||
|  |     let legacyUrl: string = ''; | ||||||
|  |     if (!!output && output.length > 0) { | ||||||
|  |       let lines: string[] = output.split(os.EOL); | ||||||
|  |       if (!!lines && lines.length > 0) { | ||||||
|  |         lines.forEach((line: string) => { | ||||||
|  |           if (!line) { | ||||||
|  |             return; | ||||||
|  |           } | ||||||
|  |           var primarySearchStringIndex = line.indexOf(primaryUrlSearchString); | ||||||
|  |           if (primarySearchStringIndex > -1) { | ||||||
|  |             primaryUrl = line.substring( | ||||||
|  |               primarySearchStringIndex + primaryUrlSearchString.length | ||||||
|  |             ); | ||||||
|  |             return; | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |           var legacySearchStringIndex = line.indexOf(legacyUrlSearchString); | ||||||
|  |           if (legacySearchStringIndex > -1) { | ||||||
|  |             legacyUrl = line.substring( | ||||||
|  |               legacySearchStringIndex + legacyUrlSearchString.length | ||||||
|  |             ); | ||||||
|  |             return; | ||||||
|  |           } | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return [primaryUrl, legacyUrl]; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private version: string; | ||||||
|  |   private cachedToolName: string; | ||||||
|  |   private arch: string; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const DotNetCoreReleasesUrl: string = | ||||||
|  |   'https://raw.githubusercontent.com/dotnet/core/master/release-notes/releases.json'; | ||||||
							
								
								
									
										23
									
								
								src/setup-dotnet.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/setup-dotnet.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | |||||||
|  | import * as core from '@actions/core'; | ||||||
|  | import * as installer from './installer'; | ||||||
|  | 
 | ||||||
|  | async function run() { | ||||||
|  |   try { | ||||||
|  |     //
 | ||||||
|  |     // Version is optional.  If supplied, install / use from the tool cache
 | ||||||
|  |     // If not supplied then task is still used to setup proxy, auth, etc...
 | ||||||
|  |     //
 | ||||||
|  |     const version = core.getInput('version'); | ||||||
|  |     if (version) { | ||||||
|  |       const dotnetInstaller = new installer.DotnetCoreInstaller(version); | ||||||
|  |       await dotnetInstaller.installDotnet(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // TODO: setup proxy from runner proxy config
 | ||||||
|  |     // TODO: problem matchers registered
 | ||||||
|  |   } catch (error) { | ||||||
|  |     core.setFailed(error.message); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | run(); | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user