Add installer

This commit is contained in:
Danny McCormick 2019-06-19 16:25:47 -04:00
parent 32b4c69676
commit d10bcaa370
8 changed files with 2271 additions and 0 deletions

192
externals/get-os-distro.sh vendored Normal file
View 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
View 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"

503
externals/install-dotnet.ps1 vendored Normal file
View File

@ -0,0 +1,503 @@
#
# 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
.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>, x64 and x86
.PARAMETER SharedRuntime
Default: false
Installs just the shared runtime bits, not the entire SDK
.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 to change URL for the Azure feed used by this installer.
.PARAMETER UncachedFeed
This parameter typically is not changed by the user.
It allows to change URL for the Uncached feed used by this installer.
.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.
#>
[cmdletbinding()]
param(
[string]$Channel="LTS",
[string]$Version="Latest",
[string]$InstallDir="<auto>",
[string]$Architecture="<auto>",
[switch]$SharedRuntime,
[switch]$DryRun,
[switch]$NoPath,
[string]$AzureFeed="https://dotnetcli.azureedge.net/dotnet",
[string]$UncachedFeed="https://dotnetcli.blob.core.windows.net/dotnet",
[string]$ProxyAddress,
[switch]$ProxyUseDefaultCredentials
)
Set-StrictMode -Version Latest
$ErrorActionPreference="Stop"
$ProgressPreference="SilentlyContinue"
$BinFolderRelativePath=""
# example path with regex: shared/1.0.0-beta-12345/somepath
$VersionRegEx="/\d+\.\d+[^/]+/"
$OverrideNonVersionedFiles=$true
function Say($str) {
Write-Output "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, IA64, x86
return $ENV:PROCESSOR_ARCHITECTURE
}
# TODO: Architecture and CLIArchitecture should be unified
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" }
default { throw "Architecture not supported. If you think this is a bug, please report it at https://github.com/dotnet/cli/issues" }
}
}
function Get-Version-Info-From-Version-Text([string]$VersionText) {
Say-Invocation $MyInvocation
$Data = @($VersionText.Split([char[]]@(), [StringSplitOptions]::RemoveEmptyEntries));
$VersionInfo = @{}
$VersionInfo.CommitHash = $Data[0].Trim()
$VersionInfo.Version = $Data[1].Trim()
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)
{
# Despite no proxy being explicitly specified, we may still be behind a default proxy
$DefaultProxy = [System.Net.WebRequest]::DefaultWebProxy;
if($DefaultProxy -and (-not $DefaultProxy.IsBypassed($Uri))){
$ProxyAddress = $DefaultProxy.GetProxy($Uri).OriginalString
$ProxyUseDefaultCredentials = $true
}
}
if($ProxyAddress){
$HttpClientHandler = New-Object System.Net.Http.HttpClientHandler
$HttpClientHandler.Proxy = New-Object System.Net.WebProxy -Property @{Address=$ProxyAddress;UseDefaultCredentials=$ProxyUseDefaultCredentials}
$HttpClient = New-Object System.Net.Http.HttpClient -ArgumentList $HttpClientHandler
}
else {
$HttpClient = New-Object System.Net.Http.HttpClient
}
# Default timeout for HttpClient is 100s. For a 50 MB download this assumes 500 KB/s average, any less will time out
# 10 minutes allows it to work over much slower connections.
$HttpClient.Timeout = New-TimeSpan -Minutes 10
$Response = $HttpClient.GetAsync($Uri).Result
if (($Response -eq $null) -or (-not ($Response.IsSuccessStatusCode)))
{
$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 ($SharedRuntime) {
$VersionFileUrl = "$UncachedFeed/Runtime/$Channel/latest.version"
}
else {
if ($Coherent) {
$VersionFileUrl = "$UncachedFeed/Sdk/$Channel/latest.coherent.version"
}
else {
$VersionFileUrl = "$UncachedFeed/Sdk/$Channel/latest.version"
}
}
$Response = GetHTTPResponse -Uri $VersionFileUrl
$StringContent = $Response.Content.ReadAsStringAsync().Result
switch ($Response.Content.Headers.ContentType) {
{ ($_ -eq "application/octet-stream") } { $VersionText = [Text.Encoding]::UTF8.GetString($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]$Channel, [string]$SpecificVersion, [string]$CLIArchitecture) {
Say-Invocation $MyInvocation
if ($SharedRuntime) {
$PayloadURL = "$AzureFeed/Runtime/$SpecificVersion/dotnet-runtime-$SpecificVersion-win-$CLIArchitecture.zip"
}
else {
$PayloadURL = "$AzureFeed/Sdk/$SpecificVersion/dotnet-sdk-$SpecificVersion-win-$CLIArchitecture.zip"
}
Say-Verbose "Constructed primary payload URL: $PayloadURL"
return $PayloadURL
}
function Get-LegacyDownload-Link([string]$AzureFeed, [string]$Channel, [string]$SpecificVersion, [string]$CLIArchitecture) {
Say-Invocation $MyInvocation
if ($SharedRuntime) {
$PayloadURL = "$AzureFeed/Runtime/$SpecificVersion/dotnet-win-$CLIArchitecture.$SpecificVersion.zip"
}
else {
$PayloadURL = "$AzureFeed/Sdk/$SpecificVersion/dotnet-dev-win-$CLIArchitecture.$SpecificVersion.zip"
}
Say-Verbose "Constructed legacy 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([Uri]$Uri, [string]$OutPath) {
$Stream = $null
try {
$Response = GetHTTPResponse -Uri $Uri
$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) {
Say "Adding to current process PATH: `"$BinPath`". Note: This change will not be visible if PowerShell was run as a child process."
$env:path = "$BinPath;" + $env:path
}
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 -Channel $Channel -SpecificVersion $SpecificVersion -CLIArchitecture $CLIArchitecture
$LegacyDownloadLink = Get-LegacyDownload-Link -AzureFeed $AzureFeed -Channel $Channel -SpecificVersion $SpecificVersion -CLIArchitecture $CLIArchitecture
if ($DryRun) {
Say "Payload URLs:"
Say "Primary - $DownloadLink"
Say "Legacy - $LegacyDownloadLink"
Say "Repeatable invocation: .\$($MyInvocation.MyCommand) -Version $SpecificVersion -Channel $Channel -Architecture $CLIArchitecture -InstallDir $InstallDir"
exit 0
}
$InstallRoot = Resolve-Installation-Path $InstallDir
Say-Verbose "InstallRoot: $InstallRoot"
$IsSdkInstalled = Is-Dotnet-Package-Installed -InstallRoot $InstallRoot -RelativePathToPackage "sdk" -SpecificVersion $SpecificVersion
Say-Verbose ".NET SDK installed? $IsSdkInstalled"
if ($IsSdkInstalled) {
Say ".NET SDK version $SpecificVersion is already installed."
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);
Write-Output "${installDrive}:";
$free = Get-CimInstance -Class win32_logicaldisk | where Deviceid -eq "${installDrive}:"
if ($free.Freespace / 1MB -le 100 ) {
Say "There is not enough disk space on drive ${installDrive}:"
exit 0
}
$ZipPath = [System.IO.Path]::GetTempFileName()
Say-Verbose "Zip path: $ZipPath"
Say "Downloading link: $DownloadLink"
try {
DownloadFile -Uri $DownloadLink -OutPath $ZipPath
}
catch {
Say "Cannot download: $DownloadLink"
$DownloadLink = $LegacyDownloadLink
$ZipPath = [System.IO.Path]::GetTempFileName()
Say-Verbose "Legacy zip path: $ZipPath"
Say "Downloading legacy link: $DownloadLink"
DownloadFile -Uri $DownloadLink -OutPath $ZipPath
}
Say "Extracting zip from $DownloadLink"
Extract-Dotnet-Package -ZipPath $ZipPath -OutPath $InstallRoot
Remove-Item $ZipPath
Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot -BinFolderRelativePath $BinFolderRelativePath
Say "Installation finished"
exit 0

317
lib/installer.js Normal file
View 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
View 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();

815
src/install-dotnet.sh Normal file
View File

@ -0,0 +1,815 @@
#!/usr/bin/env bash
# Copyright (c) .NET Foundation and contributors. All rights reserved.
# Licensed under the MIT license. See LICENSE file in the project root for full license information.
#
# Stop script on NZEC
set -e
# Stop script if unbound variable found (use ${var:-} if intentional)
set -u
# By default cmd1 | cmd2 returns exit code of cmd2 regardless of cmd1 success
# This is causing it to fail
set -o pipefail
# Use in the the functions: eval $invocation
invocation='say_verbose "Calling: ${yellow:-}${FUNCNAME[0]} ${green:-}$*${normal:-}"'
# standard output may be used as a return value in the functions
# we need a way to write text on the screen in the functions so that
# it won't interfere with the return value.
# Exposing stream 3 as a pipe to standard output of the script itself
exec 3>&1
# Setup some colors to use. These need to work in fairly limited shells, like the Ubuntu Docker container where there are only 8 colors.
# See if stdout is a terminal
if [ -t 1 ]; then
# see if it supports colors
ncolors=$(tput colors)
if [ -n "$ncolors" ] && [ $ncolors -ge 8 ]; then
bold="$(tput bold || echo)"
normal="$(tput sgr0 || echo)"
black="$(tput setaf 0 || echo)"
red="$(tput setaf 1 || echo)"
green="$(tput setaf 2 || echo)"
yellow="$(tput setaf 3 || echo)"
blue="$(tput setaf 4 || echo)"
magenta="$(tput setaf 5 || echo)"
cyan="$(tput setaf 6 || echo)"
white="$(tput setaf 7 || echo)"
fi
fi
say_err() {
printf "%b\n" "${red:-}dotnet_install: Error: $1${normal:-}" >&2
}
say() {
# using stream 3 (defined in the beginning) to not interfere with stdout of functions
# which may be used as return value
printf "%b\n" "${cyan:-}dotnet-install:${normal:-} $1" >&3
}
say_verbose() {
if [ "$verbose" = true ]; then
say "$1"
fi
}
get_os_download_name_from_platform() {
eval $invocation
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.24")
echo "fedora.24"
return 0
;;
"opensuse.13.2")
echo "opensuse.13.2"
return 0
;;
"opensuse.42.1")
echo "opensuse.42.1"
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
;;
"alpine.3.4.3")
echo "alpine"
return 0
;;
esac
return 1
}
get_current_os_name() {
eval $invocation
local uname=$(uname)
if [ "$uname" = "Darwin" ]; then
echo "osx"
return 0
else
if [ "$uname" = "Linux" ]; then
echo "linux"
return 0
fi
fi
say_err "OS name could not be detected: $ID.$VERSION_ID"
return 1
}
get_distro_specific_os_name() {
eval $invocation
local uname=$(uname)
if [ "$uname" = "Darwin" ]; then
echo "osx"
return 0
elif [ -n "$runtime_id" ]; then
echo $(get_os_download_name_from_platform "${runtime_id%-*}" || echo "${runtime_id%-*}")
return 0
else
if [ -e /etc/os-release ]; then
. /etc/os-release
os=$(get_os_download_name_from_platform "$ID.$VERSION_ID" || echo "")
if [ -n "$os" ]; then
echo "$os"
return 0
fi
fi
fi
say_verbose "Distribution specific OS name and version could not be detected: $ID.$VERSION_ID"
return 1
}
machine_has() {
eval $invocation
hash "$1" > /dev/null 2>&1
return $?
}
check_min_reqs() {
local hasMinimum=false
if machine_has "curl"; then
hasMinimum=true
elif machine_has "wget"; then
hasMinimum=true
fi
if [ "$hasMinimum" = "false" ]; then
say_err "curl (recommended) or wget are required to download dotnet. Install missing prerequisite to proceed."
return 1
fi
return 0
}
check_pre_reqs() {
eval $invocation
local failing=false;
if [ "${DOTNET_INSTALL_SKIP_PREREQS:-}" = "1" ]; then
return 0
fi
if [ "$(uname)" = "Linux" ]; then
if ! [ -x "$(command -v ldconfig)" ]; then
echo "ldconfig is not in PATH, trying /sbin/ldconfig."
LDCONFIG_COMMAND="/sbin/ldconfig"
else
LDCONFIG_COMMAND="ldconfig"
fi
[ -z "$($LDCONFIG_COMMAND -p | grep libunwind)" ] && say_err "Unable to locate libunwind. Install libunwind to continue" && failing=true
[ -z "$($LDCONFIG_COMMAND -p | grep libssl)" ] && say_err "Unable to locate libssl. Install libssl to continue" && failing=true
[ -z "$($LDCONFIG_COMMAND -p | grep libicu)" ] && say_err "Unable to locate libicu. Install libicu to continue" && failing=true
fi
if [ "$failing" = true ]; then
return 1
fi
return 0
}
# args:
# input - $1
to_lowercase() {
#eval $invocation
echo "$1" | tr '[:upper:]' '[:lower:]'
return 0
}
# args:
# input - $1
remove_trailing_slash() {
#eval $invocation
local input=${1:-}
echo "${input%/}"
return 0
}
# args:
# input - $1
remove_beginning_slash() {
#eval $invocation
local input=${1:-}
echo "${input#/}"
return 0
}
# args:
# root_path - $1
# child_path - $2 - this parameter can be empty
combine_paths() {
eval $invocation
# TODO: Consider making it work with any number of paths. For now:
if [ ! -z "${3:-}" ]; then
say_err "combine_paths: Function takes two parameters."
return 1
fi
local root_path=$(remove_trailing_slash $1)
local child_path=$(remove_beginning_slash ${2:-})
say_verbose "combine_paths: root_path=$root_path"
say_verbose "combine_paths: child_path=$child_path"
echo "$root_path/$child_path"
return 0
}
get_machine_architecture() {
eval $invocation
# Currently the only one supported
echo "x64"
return 0
}
# args:
# architecture - $1
get_normalized_architecture_from_architecture() {
eval $invocation
local architecture=$(to_lowercase $1)
case $architecture in
\<auto\>)
echo "$(get_normalized_architecture_from_architecture $(get_machine_architecture))"
return 0
;;
amd64|x64)
echo "x64"
return 0
;;
x86)
say_err "Architecture \`x86\` currently not supported"
return 1
;;
esac
say_err "Architecture \`$architecture\` not supported. If you think this is a bug, please report it at https://github.com/dotnet/cli/issues"
return 1
}
# version_info is a conceptual two line string representing commit hash and 4-part version
# format:
# Line 1: # commit_hash
# Line 2: # 4-part version
# args:
# version_text - stdin
get_version_from_version_info() {
eval $invocation
cat | tail -n 1
return 0
}
# args:
# version_text - stdin
get_commit_hash_from_version_info() {
eval $invocation
cat | head -n 1
return 0
}
# args:
# install_root - $1
# relative_path_to_package - $2
# specific_version - $3
is_dotnet_package_installed() {
eval $invocation
local install_root=$1
local relative_path_to_package=$2
local specific_version=${3//[$'\t\r\n']}
local dotnet_package_path=$(combine_paths $(combine_paths $install_root $relative_path_to_package) $specific_version)
say_verbose "is_dotnet_package_installed: dotnet_package_path=$dotnet_package_path"
if [ -d "$dotnet_package_path" ]; then
return 0
else
return 1
fi
}
# args:
# azure_feed - $1
# channel - $2
# normalized_architecture - $3
get_latest_version_info() {
eval $invocation
local azure_feed=$1
local channel=$2
local normalized_architecture=$3
local coherent=$4
local version_file_url=null
if [ "$shared_runtime" = true ]; then
version_file_url="$uncached_feed/Runtime/$channel/latest.version"
else
if [ "$coherent" = true ]; then
version_file_url="$uncached_feed/Sdk/$channel/latest.coherent.version"
else
version_file_url="$uncached_feed/Sdk/$channel/latest.version"
fi
fi
say_verbose "get_latest_version_info: latest url: $version_file_url"
download $version_file_url
return $?
}
# args:
# azure_feed - $1
# channel - $2
# normalized_architecture - $3
# version - $4
get_specific_version_from_version() {
eval $invocation
local azure_feed=$1
local channel=$2
local normalized_architecture=$3
local version=$(to_lowercase $4)
case $version in
latest)
local version_info
version_info="$(get_latest_version_info $azure_feed $channel $normalized_architecture false)" || return 1
say_verbose "get_specific_version_from_version: version_info=$version_info"
echo "$version_info" | get_version_from_version_info
return 0
;;
coherent)
local version_info
version_info="$(get_latest_version_info $azure_feed $channel $normalized_architecture true)" || return 1
say_verbose "get_specific_version_from_version: version_info=$version_info"
echo "$version_info" | get_version_from_version_info
return 0
;;
*)
echo $version
return 0
;;
esac
}
# args:
# azure_feed - $1
# channel - $2
# normalized_architecture - $3
# specific_version - $4
construct_download_link() {
eval $invocation
local azure_feed=$1
local channel=$2
local normalized_architecture=$3
local specific_version=${4//[$'\t\r\n']}
local osname
osname=$(get_current_os_name) || return 1
local download_link=null
if [ "$shared_runtime" = true ]; then
download_link="$azure_feed/Runtime/$specific_version/dotnet-runtime-$specific_version-$osname-$normalized_architecture.tar.gz"
else
download_link="$azure_feed/Sdk/$specific_version/dotnet-sdk-$specific_version-$osname-$normalized_architecture.tar.gz"
fi
echo "$download_link"
return 0
}
# args:
# azure_feed - $1
# channel - $2
# normalized_architecture - $3
# specific_version - $4
construct_legacy_download_link() {
eval $invocation
local azure_feed=$1
local channel=$2
local normalized_architecture=$3
local specific_version=${4//[$'\t\r\n']}
local distro_specific_osname
distro_specific_osname=$(get_distro_specific_os_name) || return 1
local legacy_download_link=null
if [ "$shared_runtime" = true ]; then
legacy_download_link="$azure_feed/Runtime/$specific_version/dotnet-$distro_specific_osname-$normalized_architecture.$specific_version.tar.gz"
else
legacy_download_link="$azure_feed/Sdk/$specific_version/dotnet-dev-$distro_specific_osname-$normalized_architecture.$specific_version.tar.gz"
fi
echo "$legacy_download_link"
return 0
}
get_user_install_path() {
eval $invocation
if [ ! -z "${DOTNET_INSTALL_DIR:-}" ]; then
echo $DOTNET_INSTALL_DIR
else
echo "$HOME/.dotnet"
fi
return 0
}
# args:
# install_dir - $1
resolve_installation_path() {
eval $invocation
local install_dir=$1
if [ "$install_dir" = "<auto>" ]; then
local user_install_path=$(get_user_install_path)
say_verbose "resolve_installation_path: user_install_path=$user_install_path"
echo "$user_install_path"
return 0
fi
echo "$install_dir"
return 0
}
# args:
# install_root - $1
get_installed_version_info() {
eval $invocation
local install_root=$1
local version_file=$(combine_paths "$install_root" "$local_version_file_relative_path")
say_verbose "Local version file: $version_file"
if [ ! -z "$version_file" ] | [ -r "$version_file" ]; then
local version_info="$(cat $version_file)"
echo "$version_info"
return 0
fi
say_verbose "Local version file not found."
return 0
}
# args:
# relative_or_absolute_path - $1
get_absolute_path() {
eval $invocation
local relative_or_absolute_path=$1
echo $(cd $(dirname "$1") && pwd -P)/$(basename "$1")
return 0
}
# args:
# input_files - stdin
# root_path - $1
# out_path - $2
# override - $3
copy_files_or_dirs_from_list() {
eval $invocation
local root_path=$(remove_trailing_slash $1)
local out_path=$(remove_trailing_slash $2)
local override=$3
local override_switch=$(if [ "$override" = false ]; then printf -- "-n"; fi)
cat | uniq | while read -r file_path; do
local path=$(remove_beginning_slash ${file_path#$root_path})
local target=$out_path/$path
if [ "$override" = true ] || (! ([ -d "$target" ] || [ -e "$target" ])); then
mkdir -p $out_path/$(dirname $path)
cp -R $override_switch $root_path/$path $target
fi
done
}
# args:
# zip_path - $1
# out_path - $2
extract_dotnet_package() {
eval $invocation
local zip_path=$1
local out_path=$2
local temp_out_path=$(mktemp -d $temporary_file_template)
local failed=false
tar -xzf "$zip_path" -C "$temp_out_path" > /dev/null || failed=true
local folders_with_version_regex='^.*/[0-9]+\.[0-9]+[^/]+/'
find $temp_out_path -type f | grep -Eo $folders_with_version_regex | copy_files_or_dirs_from_list $temp_out_path $out_path false
find $temp_out_path -type f | grep -Ev $folders_with_version_regex | copy_files_or_dirs_from_list $temp_out_path $out_path true
rm -rf $temp_out_path
if [ "$failed" = true ]; then
say_err "Extraction failed"
return 1
fi
}
# args:
# remote_path - $1
# [out_path] - $2 - stdout if not provided
download() {
eval $invocation
local remote_path=$1
local out_path=${2:-}
local failed=false
if machine_has "curl"; then
downloadcurl $remote_path $out_path || failed=true
elif machine_has "wget"; then
downloadwget $remote_path $out_path || failed=true
else
failed=true
fi
if [ "$failed" = true ]; then
say_verbose "Download failed: $remote_path"
return 1
fi
return 0
}
downloadcurl() {
eval $invocation
local remote_path=$1
local out_path=${2:-}
local failed=false
if [ -z "$out_path" ]; then
curl --retry 10 -sSL -f --create-dirs $remote_path || failed=true
else
curl --retry 10 -sSL -f --create-dirs -o $out_path $remote_path || failed=true
fi
if [ "$failed" = true ]; then
say_verbose "Curl download failed"
return 1
fi
return 0
}
downloadwget() {
eval $invocation
local remote_path=$1
local out_path=${2:-}
local failed=false
if [ -z "$out_path" ]; then
wget -q --tries 10 $remote_path || failed=true
else
wget -v --tries 10 -O $out_path $remote_path || failed=true
fi
if [ "$failed" = true ]; then
say_verbose "Wget download failed"
return 1
fi
return 0
}
calculate_vars() {
eval $invocation
valid_legacy_download_link=true
normalized_architecture=$(get_normalized_architecture_from_architecture "$architecture")
say_verbose "normalized_architecture=$normalized_architecture"
specific_version=$(get_specific_version_from_version $azure_feed $channel $normalized_architecture $version)
say_verbose "specific_version=$specific_version"
if [ -z "$specific_version" ]; then
say_err "Could not get version information."
return 1
fi
download_link=$(construct_download_link $azure_feed $channel $normalized_architecture $specific_version)
say_verbose "download_link=$download_link"
legacy_download_link=$(construct_legacy_download_link $azure_feed $channel $normalized_architecture $specific_version) || valid_legacy_download_link=false
if [ "$valid_legacy_download_link" = true ]; then
say_verbose "legacy_download_link=$legacy_download_link"
else
say_verbose "Cound not construct a legacy_download_link; omitting..."
fi
install_root=$(resolve_installation_path $install_dir)
say_verbose "install_root=$install_root"
}
install_dotnet() {
eval $invocation
local download_failed=false
if is_dotnet_package_installed $install_root "sdk" $specific_version; then
say ".NET SDK version $specific_version is already installed."
return 0
fi
mkdir -p $install_root
zip_path=$(mktemp $temporary_file_template)
say_verbose "Zip path: $zip_path"
say "Downloading link: $download_link"
download "$download_link" $zip_path || download_failed=true
# if the download fails, download the legacy_download_link
if [ "$download_failed" = true ] && [ "$valid_legacy_download_link" = true ]; then
say "Cannot download: $download_link"
download_link=$legacy_download_link
zip_path=$(mktemp $temporary_file_template)
say_verbose "Legacy zip path: $zip_path"
say "Downloading legacy link: $download_link"
download "$download_link" $zip_path
fi
say "Extracting zip from $download_link"
extract_dotnet_package $zip_path $install_root
return 0
}
local_version_file_relative_path="/.version"
bin_folder_relative_path=""
temporary_file_template="${TMPDIR:-/tmp}/dotnet.XXXXXXXXX"
channel="LTS"
version="Latest"
install_dir="<auto>"
architecture="<auto>"
dry_run=false
no_path=false
azure_feed="https://dotnetcli.azureedge.net/dotnet"
uncached_feed="https://dotnetcli.blob.core.windows.net/dotnet"
verbose=false
shared_runtime=false
runtime_id=""
while [ $# -ne 0 ]
do
name=$1
case $name in
-c|--channel|-[Cc]hannel)
shift
channel=$1
;;
-v|--version|-[Vv]ersion)
shift
version="$1"
;;
-i|--install-dir|-[Ii]nstall[Dd]ir)
shift
install_dir="$1"
;;
--arch|--architecture|-[Aa]rch|-[Aa]rchitecture)
shift
architecture="$1"
;;
--shared-runtime|-[Ss]hared[Rr]untime)
shared_runtime=true
;;
--dry-run|-[Dd]ry[Rr]un)
dry_run=true
;;
--no-path|-[Nn]o[Pp]ath)
no_path=true
;;
--verbose|-[Vv]erbose)
verbose=true
;;
--azure-feed|-[Aa]zure[Ff]eed)
shift
azure_feed="$1"
;;
--uncached-feed|-[Uu]ncached[Ff]eed)
shift
uncached_feed="$1"
;;
--runtime-id|-[Rr]untime[Ii]d)
shift
runtime_id="$1"
;;
-?|--?|-h|--help|-[Hh]elp)
script_name="$(basename $0)"
echo ".NET Tools Installer"
echo "Usage: $script_name [-c|--channel <CHANNEL>] [-v|--version <VERSION>] [-p|--prefix <DESTINATION>]"
echo " $script_name -h|-?|--help"
echo ""
echo "$script_name is a simple command line interface for obtaining dotnet cli."
echo ""
echo "Options:"
echo " -c,--channel <CHANNEL> Download from the CHANNEL specified, Defaults to \`$channel\`."
echo " -Channel"
echo " Possible values:"
echo " - Current - most current release"
echo " - LTS - most current supported release"
echo " - 2-part version in a format A.B - represents a specific release"
echo " examples: 2.0; 1.0"
echo " - Branch name"
echo " examples: release/2.0.0; Master"
echo " -v,--version <VERSION> Use specific VERSION, Defaults to \`$version\`."
echo " -Version"
echo " Possible values:"
echo " - latest - most latest build on specific channel"
echo " - coherent - most latest coherent build on specific channel"
echo " coherent applies only to SDK downloads"
echo " - 3-part version in a format A.B.C - represents specific version of build"
echo " examples: 2.0.0-preview2-006120; 1.1.0"
echo " -i,--install-dir <DIR> Install under specified location (see Install Location below)"
echo " -InstallDir"
echo " --architecture <ARCHITECTURE> Architecture of .NET Tools. Currently only x64 is supported."
echo " --arch,-Architecture,-Arch"
echo " --shared-runtime Installs just the shared runtime bits, not the entire SDK."
echo " -SharedRuntime"
echo " --dry-run,-DryRun Do not perform installation. Display download link."
echo " --no-path, -NoPath Do not set PATH for the current process."
echo " --verbose,-Verbose Display diagnostics information."
echo " --azure-feed,-AzureFeed Azure feed location. Defaults to $azure_feed, This parameter typically is not changed by the user."
echo " --uncached-feed,-UncachedFeed Uncached feed location. This parameter typically is not changed by the user."
echo " --runtime-id Installs the .NET Tools for the given platform (use linux-x64 for portable linux)."
echo " -RuntimeId"
echo " -?,--?,-h,--help,-Help Shows this help message"
echo ""
echo "Install Location:"
echo " Location is chosen in following order:"
echo " - --install-dir option"
echo " - Environmental variable DOTNET_INSTALL_DIR"
echo " - $HOME/.dotnet"
exit 0
;;
*)
say_err "Unknown argument \`$name\`"
exit 1
;;
esac
shift
done
check_min_reqs
calculate_vars
if [ "$dry_run" = true ]; then
say "Payload URL: $download_link"
if [ "$valid_legacy_download_link" = true ]; then
say "Legacy payload URL: $legacy_download_link"
fi
say "Repeatable invocation: ./$(basename $0) --version $specific_version --channel $channel --install-dir $install_dir"
exit 0
fi
check_pre_reqs
install_dotnet
bin_path=$(get_absolute_path $(combine_paths $install_root $bin_folder_relative_path))
if [ "$no_path" = false ]; then
say "Adding to current process PATH: \`$bin_path\`. Note: This change will be visible only when sourcing script."
export PATH=$bin_path:$PATH
else
say "Binaries of dotnet can be found in $bin_path"
fi
say "Installation finished successfully."

363
src/installer.ts Normal file
View File

@ -0,0 +1,363 @@
// 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 httpInterfaces = require('typed-rest-client/Interfaces');
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);
try {
let globalToolPath: string = '';
if (IS_WINDOWS) {
globalToolPath = path.join(
process.env.USERPROFILE || '',
'.dotnet\\tools'
);
} else {
globalToolPath = path.join(process.env.HOME || '', '.dotnet/tools');
}
await io.mkdirP(globalToolPath);
core.addPath(globalToolPath);
} catch (error) {
//nop
}
// Set DOTNET_ROOT for dotnet core Apphost to find runtime since it is installed to a non well-known location.
core.exportVariable('DOTNET_ROOT', toolPath);
}
private getLocalTool(): string {
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}`;
}
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();
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}`;
}
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
View 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();