mirror of
				https://github.com/actions/setup-dotnet.git
				synced 2025-10-31 15:43:46 +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 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'); | ||||
| @ -10,12 +11,117 @@ process.env['RUNNER_TOOLSDIRECTORY'] = toolDir; | ||||
| process.env['RUNNER_TEMPDIRECTORY'] = tempDir; | ||||
| import * as installer from '../src/installer'; | ||||
| 
 | ||||
| const IS_WINDOWS = process.platform === 'win32'; | ||||
| 
 | ||||
| describe('installer tests', () => { | ||||
|   beforeAll(() => {}); | ||||
|   beforeAll(async () => { | ||||
|     await io.rmRF(toolDir); | ||||
|     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