mirror of
https://github.com/actions/setup-dotnet.git
synced 2024-11-23 12:01:07 +07:00
6bd8b7f777
* Bump braces from 3.0.2 to 3.0.3 Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3. - [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3) --- updated-dependencies: - dependency-name: braces dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> * Bump undici from 5.28.3 to 5.28.4 * update checkout from v3 to v4 * Update test proxy --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: HarithaVattikuti <73516759+HarithaVattikuti@users.noreply.github.com>
1861 lines
62 KiB
Bash
Executable File
1861 lines
62 KiB
Bash
Executable File
#!/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 ] && command -v tput > /dev/null; then
|
|
# see if it supports colors
|
|
ncolors=$(tput colors || echo 0)
|
|
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_warning() {
|
|
printf "%b\n" "${yellow:-}dotnet_install: Warning: $1${normal:-}" >&3
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
# This platform list is finite - if the SDK/Runtime has supported Linux distribution-specific assets,
|
|
# then and only then should the Linux distribution appear in this list.
|
|
# Adding a Linux distribution to this list does not imply distribution-specific support.
|
|
get_legacy_os_name_from_platform() {
|
|
eval $invocation
|
|
|
|
platform="$1"
|
|
case "$platform" in
|
|
"centos.7")
|
|
echo "centos"
|
|
return 0
|
|
;;
|
|
"debian.8")
|
|
echo "debian"
|
|
return 0
|
|
;;
|
|
"debian.9")
|
|
echo "debian.9"
|
|
return 0
|
|
;;
|
|
"fedora.23")
|
|
echo "fedora.23"
|
|
return 0
|
|
;;
|
|
"fedora.24")
|
|
echo "fedora.24"
|
|
return 0
|
|
;;
|
|
"fedora.27")
|
|
echo "fedora.27"
|
|
return 0
|
|
;;
|
|
"fedora.28")
|
|
echo "fedora.28"
|
|
return 0
|
|
;;
|
|
"opensuse.13.2")
|
|
echo "opensuse.13.2"
|
|
return 0
|
|
;;
|
|
"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_legacy_os_name() {
|
|
eval $invocation
|
|
|
|
local uname=$(uname)
|
|
if [ "$uname" = "Darwin" ]; then
|
|
echo "osx"
|
|
return 0
|
|
elif [ -n "$runtime_id" ]; then
|
|
echo $(get_legacy_os_name_from_platform "${runtime_id%-*}" || echo "${runtime_id%-*}")
|
|
return 0
|
|
else
|
|
if [ -e /etc/os-release ]; then
|
|
. /etc/os-release
|
|
os=$(get_legacy_os_name_from_platform "$ID${VERSION_ID:+.${VERSION_ID}}" || echo "")
|
|
if [ -n "$os" ]; then
|
|
echo "$os"
|
|
return 0
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
say_verbose "Distribution specific OS name and version could not be detected: UName = $uname"
|
|
return 1
|
|
}
|
|
|
|
get_linux_platform_name() {
|
|
eval $invocation
|
|
|
|
if [ -n "$runtime_id" ]; then
|
|
echo "${runtime_id%-*}"
|
|
return 0
|
|
else
|
|
if [ -e /etc/os-release ]; then
|
|
. /etc/os-release
|
|
echo "$ID${VERSION_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 "*" release 6."* ]]; then
|
|
echo "rhel.6"
|
|
return 0
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
say_verbose "Linux specific platform name and version could not be detected: UName = $uname"
|
|
return 1
|
|
}
|
|
|
|
is_musl_based_distro() {
|
|
(ldd --version 2>&1 || true) | grep -q musl
|
|
}
|
|
|
|
get_current_os_name() {
|
|
eval $invocation
|
|
|
|
local uname=$(uname)
|
|
if [ "$uname" = "Darwin" ]; then
|
|
echo "osx"
|
|
return 0
|
|
elif [ "$uname" = "FreeBSD" ]; then
|
|
echo "freebsd"
|
|
return 0
|
|
elif [ "$uname" = "Linux" ]; then
|
|
local linux_platform_name=""
|
|
linux_platform_name="$(get_linux_platform_name)" || true
|
|
|
|
if [ "$linux_platform_name" = "rhel.6" ]; then
|
|
echo $linux_platform_name
|
|
return 0
|
|
elif is_musl_based_distro; then
|
|
echo "linux-musl"
|
|
return 0
|
|
elif [ "$linux_platform_name" = "linux-musl" ]; 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
|
|
}
|
|
|
|
machine_has() {
|
|
eval $invocation
|
|
|
|
command -v "$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
|
|
}
|
|
|
|
# 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
|
|
|
|
if command -v uname > /dev/null; then
|
|
CPUName=$(uname -m)
|
|
case $CPUName in
|
|
armv1*|armv2*|armv3*|armv4*|armv5*|armv6*)
|
|
echo "armv6-or-below"
|
|
return 0
|
|
;;
|
|
armv*l)
|
|
echo "arm"
|
|
return 0
|
|
;;
|
|
aarch64|arm64)
|
|
if [ "$(getconf LONG_BIT)" -lt 64 ]; then
|
|
# This is 32-bit OS running on 64-bit CPU (for example Raspberry Pi OS)
|
|
echo "arm"
|
|
return 0
|
|
fi
|
|
echo "arm64"
|
|
return 0
|
|
;;
|
|
s390x)
|
|
echo "s390x"
|
|
return 0
|
|
;;
|
|
ppc64le)
|
|
echo "ppc64le"
|
|
return 0
|
|
;;
|
|
loongarch64)
|
|
echo "loongarch64"
|
|
return 0
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
# Always default to 'x64'
|
|
echo "x64"
|
|
return 0
|
|
}
|
|
|
|
# args:
|
|
# architecture - $1
|
|
get_normalized_architecture_from_architecture() {
|
|
eval $invocation
|
|
|
|
local architecture="$(to_lowercase "$1")"
|
|
|
|
if [[ $architecture == \<auto\> ]]; then
|
|
machine_architecture="$(get_machine_architecture)"
|
|
if [[ "$machine_architecture" == "armv6-or-below" ]]; then
|
|
say_err "Architecture \`$machine_architecture\` not supported. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues"
|
|
return 1
|
|
fi
|
|
|
|
echo $machine_architecture
|
|
return 0
|
|
fi
|
|
|
|
case "$architecture" in
|
|
amd64|x64)
|
|
echo "x64"
|
|
return 0
|
|
;;
|
|
arm)
|
|
echo "arm"
|
|
return 0
|
|
;;
|
|
arm64)
|
|
echo "arm64"
|
|
return 0
|
|
;;
|
|
s390x)
|
|
echo "s390x"
|
|
return 0
|
|
;;
|
|
ppc64le)
|
|
echo "ppc64le"
|
|
return 0
|
|
;;
|
|
loongarch64)
|
|
echo "loongarch64"
|
|
return 0
|
|
;;
|
|
esac
|
|
|
|
say_err "Architecture \`$architecture\` not supported. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues"
|
|
return 1
|
|
}
|
|
|
|
# args:
|
|
# version - $1
|
|
# channel - $2
|
|
# architecture - $3
|
|
get_normalized_architecture_for_specific_sdk_version() {
|
|
eval $invocation
|
|
|
|
local is_version_support_arm64="$(is_arm64_supported "$1")"
|
|
local is_channel_support_arm64="$(is_arm64_supported "$2")"
|
|
local architecture="$3";
|
|
local osname="$(get_current_os_name)"
|
|
|
|
if [ "$osname" == "osx" ] && [ "$architecture" == "arm64" ] && { [ "$is_version_support_arm64" = false ] || [ "$is_channel_support_arm64" = false ]; }; then
|
|
#check if rosetta is installed
|
|
if [ "$(/usr/bin/pgrep oahd >/dev/null 2>&1;echo $?)" -eq 0 ]; then
|
|
say_verbose "Changing user architecture from '$architecture' to 'x64' because .NET SDKs prior to version 6.0 do not support arm64."
|
|
echo "x64"
|
|
return 0;
|
|
else
|
|
say_err "Architecture \`$architecture\` is not supported for .NET SDK version \`$version\`. Please install Rosetta to allow emulation of the \`$architecture\` .NET SDK on this platform"
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
echo "$architecture"
|
|
return 0
|
|
}
|
|
|
|
# args:
|
|
# version or channel - $1
|
|
is_arm64_supported() {
|
|
#any channel or version that starts with the specified versions
|
|
case "$1" in
|
|
( "1"* | "2"* | "3"* | "4"* | "5"*)
|
|
echo false
|
|
return 0
|
|
esac
|
|
|
|
echo true
|
|
return 0
|
|
}
|
|
|
|
# args:
|
|
# user_defined_os - $1
|
|
get_normalized_os() {
|
|
eval $invocation
|
|
|
|
local osname="$(to_lowercase "$1")"
|
|
if [ ! -z "$osname" ]; then
|
|
case "$osname" in
|
|
osx | freebsd | rhel.6 | linux-musl | linux)
|
|
echo "$osname"
|
|
return 0
|
|
;;
|
|
macos)
|
|
osname='osx'
|
|
echo "$osname"
|
|
return 0
|
|
;;
|
|
*)
|
|
say_err "'$user_defined_os' is not a supported value for --os option, supported values are: osx, macos, linux, linux-musl, freebsd, rhel.6. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues."
|
|
return 1
|
|
;;
|
|
esac
|
|
else
|
|
osname="$(get_current_os_name)" || return 1
|
|
fi
|
|
echo "$osname"
|
|
return 0
|
|
}
|
|
|
|
# args:
|
|
# quality - $1
|
|
get_normalized_quality() {
|
|
eval $invocation
|
|
|
|
local quality="$(to_lowercase "$1")"
|
|
if [ ! -z "$quality" ]; then
|
|
case "$quality" in
|
|
daily | signed | validated | preview)
|
|
echo "$quality"
|
|
return 0
|
|
;;
|
|
ga)
|
|
#ga quality is available without specifying quality, so normalizing it to empty
|
|
return 0
|
|
;;
|
|
*)
|
|
say_err "'$quality' is not a supported value for --quality option. Supported values are: daily, signed, validated, preview, ga. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues."
|
|
return 1
|
|
;;
|
|
esac
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# args:
|
|
# channel - $1
|
|
get_normalized_channel() {
|
|
eval $invocation
|
|
|
|
local channel="$(to_lowercase "$1")"
|
|
|
|
if [[ $channel == current ]]; then
|
|
say_warning 'Value "Current" is deprecated for -Channel option. Use "STS" instead.'
|
|
fi
|
|
|
|
if [[ $channel == release/* ]]; then
|
|
say_warning 'Using branch name with -Channel option is no longer supported with newer releases. Use -Quality option with a channel in X.Y format instead.';
|
|
fi
|
|
|
|
if [ ! -z "$channel" ]; then
|
|
case "$channel" in
|
|
lts)
|
|
echo "LTS"
|
|
return 0
|
|
;;
|
|
sts)
|
|
echo "STS"
|
|
return 0
|
|
;;
|
|
current)
|
|
echo "STS"
|
|
return 0
|
|
;;
|
|
*)
|
|
echo "$channel"
|
|
return 0
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# args:
|
|
# runtime - $1
|
|
get_normalized_product() {
|
|
eval $invocation
|
|
|
|
local product=""
|
|
local runtime="$(to_lowercase "$1")"
|
|
if [[ "$runtime" == "dotnet" ]]; then
|
|
product="dotnet-runtime"
|
|
elif [[ "$runtime" == "aspnetcore" ]]; then
|
|
product="aspnetcore-runtime"
|
|
elif [ -z "$runtime" ]; then
|
|
product="dotnet-sdk"
|
|
fi
|
|
echo "$product"
|
|
return 0
|
|
}
|
|
|
|
# The version text returned from the feeds is a 1-line or 2-line string:
|
|
# For the SDK and the dotnet runtime (2 lines):
|
|
# Line 1: # commit_hash
|
|
# Line 2: # 4-part version
|
|
# For the aspnetcore runtime (1 line):
|
|
# Line 1: # 4-part version
|
|
|
|
# args:
|
|
# version_text - stdin
|
|
get_version_from_latestversion_file_content() {
|
|
eval $invocation
|
|
|
|
cat | tail -n 1 | sed 's/\r$//'
|
|
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:
|
|
# downloaded file - $1
|
|
# remote_file_size - $2
|
|
validate_remote_local_file_sizes()
|
|
{
|
|
eval $invocation
|
|
|
|
local downloaded_file="$1"
|
|
local remote_file_size="$2"
|
|
local file_size=''
|
|
|
|
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
|
file_size="$(stat -c '%s' "$downloaded_file")"
|
|
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
|
# hardcode in order to avoid conflicts with GNU stat
|
|
file_size="$(/usr/bin/stat -f '%z' "$downloaded_file")"
|
|
fi
|
|
|
|
if [ -n "$file_size" ]; then
|
|
say "Downloaded file size is $file_size bytes."
|
|
|
|
if [ -n "$remote_file_size" ] && [ -n "$file_size" ]; then
|
|
if [ "$remote_file_size" -ne "$file_size" ]; then
|
|
say "The remote and local file sizes are not equal. The remote file size is $remote_file_size bytes and the local size is $file_size bytes. The local package may be corrupted."
|
|
else
|
|
say "The remote and local file sizes are equal."
|
|
fi
|
|
fi
|
|
|
|
else
|
|
say "Either downloaded or local package size can not be measured. One of them may be corrupted."
|
|
fi
|
|
}
|
|
|
|
# args:
|
|
# azure_feed - $1
|
|
# channel - $2
|
|
# normalized_architecture - $3
|
|
get_version_from_latestversion_file() {
|
|
eval $invocation
|
|
|
|
local azure_feed="$1"
|
|
local channel="$2"
|
|
local normalized_architecture="$3"
|
|
|
|
local version_file_url=null
|
|
if [[ "$runtime" == "dotnet" ]]; then
|
|
version_file_url="$azure_feed/Runtime/$channel/latest.version"
|
|
elif [[ "$runtime" == "aspnetcore" ]]; then
|
|
version_file_url="$azure_feed/aspnetcore/Runtime/$channel/latest.version"
|
|
elif [ -z "$runtime" ]; then
|
|
version_file_url="$azure_feed/Sdk/$channel/latest.version"
|
|
else
|
|
say_err "Invalid value for \$runtime"
|
|
return 1
|
|
fi
|
|
say_verbose "get_version_from_latestversion_file: latest url: $version_file_url"
|
|
|
|
download "$version_file_url" || return $?
|
|
return 0
|
|
}
|
|
|
|
# args:
|
|
# json_file - $1
|
|
parse_globaljson_file_for_version() {
|
|
eval $invocation
|
|
|
|
local json_file="$1"
|
|
if [ ! -f "$json_file" ]; then
|
|
say_err "Unable to find \`$json_file\`"
|
|
return 1
|
|
fi
|
|
|
|
sdk_section=$(cat $json_file | tr -d "\r" | awk '/"sdk"/,/}/')
|
|
if [ -z "$sdk_section" ]; then
|
|
say_err "Unable to parse the SDK node in \`$json_file\`"
|
|
return 1
|
|
fi
|
|
|
|
sdk_list=$(echo $sdk_section | awk -F"[{}]" '{print $2}')
|
|
sdk_list=${sdk_list//[\" ]/}
|
|
sdk_list=${sdk_list//,/$'\n'}
|
|
|
|
local version_info=""
|
|
while read -r line; do
|
|
IFS=:
|
|
while read -r key value; do
|
|
if [[ "$key" == "version" ]]; then
|
|
version_info=$value
|
|
fi
|
|
done <<< "$line"
|
|
done <<< "$sdk_list"
|
|
if [ -z "$version_info" ]; then
|
|
say_err "Unable to find the SDK:version node in \`$json_file\`"
|
|
return 1
|
|
fi
|
|
|
|
unset IFS;
|
|
echo "$version_info"
|
|
return 0
|
|
}
|
|
|
|
# args:
|
|
# azure_feed - $1
|
|
# channel - $2
|
|
# normalized_architecture - $3
|
|
# version - $4
|
|
# json_file - $5
|
|
get_specific_version_from_version() {
|
|
eval $invocation
|
|
|
|
local azure_feed="$1"
|
|
local channel="$2"
|
|
local normalized_architecture="$3"
|
|
local version="$(to_lowercase "$4")"
|
|
local json_file="$5"
|
|
|
|
if [ -z "$json_file" ]; then
|
|
if [[ "$version" == "latest" ]]; then
|
|
local version_info
|
|
version_info="$(get_version_from_latestversion_file "$azure_feed" "$channel" "$normalized_architecture" false)" || return 1
|
|
say_verbose "get_specific_version_from_version: version_info=$version_info"
|
|
echo "$version_info" | get_version_from_latestversion_file_content
|
|
return 0
|
|
else
|
|
echo "$version"
|
|
return 0
|
|
fi
|
|
else
|
|
local version_info
|
|
version_info="$(parse_globaljson_file_for_version "$json_file")" || return 1
|
|
echo "$version_info"
|
|
return 0
|
|
fi
|
|
}
|
|
|
|
# args:
|
|
# azure_feed - $1
|
|
# channel - $2
|
|
# normalized_architecture - $3
|
|
# specific_version - $4
|
|
# normalized_os - $5
|
|
construct_download_link() {
|
|
eval $invocation
|
|
|
|
local azure_feed="$1"
|
|
local channel="$2"
|
|
local normalized_architecture="$3"
|
|
local specific_version="${4//[$'\t\r\n']}"
|
|
local specific_product_version="$(get_specific_product_version "$1" "$4")"
|
|
local osname="$5"
|
|
|
|
local download_link=null
|
|
if [[ "$runtime" == "dotnet" ]]; then
|
|
download_link="$azure_feed/Runtime/$specific_version/dotnet-runtime-$specific_product_version-$osname-$normalized_architecture.tar.gz"
|
|
elif [[ "$runtime" == "aspnetcore" ]]; then
|
|
download_link="$azure_feed/aspnetcore/Runtime/$specific_version/aspnetcore-runtime-$specific_product_version-$osname-$normalized_architecture.tar.gz"
|
|
elif [ -z "$runtime" ]; then
|
|
download_link="$azure_feed/Sdk/$specific_version/dotnet-sdk-$specific_product_version-$osname-$normalized_architecture.tar.gz"
|
|
else
|
|
return 1
|
|
fi
|
|
|
|
echo "$download_link"
|
|
return 0
|
|
}
|
|
|
|
# args:
|
|
# azure_feed - $1
|
|
# specific_version - $2
|
|
# download link - $3 (optional)
|
|
get_specific_product_version() {
|
|
# If we find a 'productVersion.txt' at the root of any folder, we'll use its contents
|
|
# to resolve the version of what's in the folder, superseding the specified version.
|
|
# if 'productVersion.txt' is missing but download link is already available, product version will be taken from download link
|
|
eval $invocation
|
|
|
|
local azure_feed="$1"
|
|
local specific_version="${2//[$'\t\r\n']}"
|
|
local package_download_link=""
|
|
if [ $# -gt 2 ]; then
|
|
local package_download_link="$3"
|
|
fi
|
|
local specific_product_version=null
|
|
|
|
# Try to get the version number, using the productVersion.txt file located next to the installer file.
|
|
local download_links=($(get_specific_product_version_url "$azure_feed" "$specific_version" true "$package_download_link")
|
|
$(get_specific_product_version_url "$azure_feed" "$specific_version" false "$package_download_link"))
|
|
|
|
for download_link in "${download_links[@]}"
|
|
do
|
|
say_verbose "Checking for the existence of $download_link"
|
|
|
|
if machine_has "curl"
|
|
then
|
|
if ! specific_product_version=$(curl -s --fail "${download_link}${feed_credential}" 2>&1); then
|
|
continue
|
|
else
|
|
echo "${specific_product_version//[$'\t\r\n']}"
|
|
return 0
|
|
fi
|
|
|
|
elif machine_has "wget"
|
|
then
|
|
specific_product_version=$(wget -qO- "${download_link}${feed_credential}" 2>&1)
|
|
if [ $? = 0 ]; then
|
|
echo "${specific_product_version//[$'\t\r\n']}"
|
|
return 0
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# Getting the version number with productVersion.txt has failed. Try parsing the download link for a version number.
|
|
say_verbose "Failed to get the version using productVersion.txt file. Download link will be parsed instead."
|
|
specific_product_version="$(get_product_specific_version_from_download_link "$package_download_link" "$specific_version")"
|
|
echo "${specific_product_version//[$'\t\r\n']}"
|
|
return 0
|
|
}
|
|
|
|
# args:
|
|
# azure_feed - $1
|
|
# specific_version - $2
|
|
# is_flattened - $3
|
|
# download link - $4 (optional)
|
|
get_specific_product_version_url() {
|
|
eval $invocation
|
|
|
|
local azure_feed="$1"
|
|
local specific_version="$2"
|
|
local is_flattened="$3"
|
|
local package_download_link=""
|
|
if [ $# -gt 3 ]; then
|
|
local package_download_link="$4"
|
|
fi
|
|
|
|
local pvFileName="productVersion.txt"
|
|
if [ "$is_flattened" = true ]; then
|
|
if [ -z "$runtime" ]; then
|
|
pvFileName="sdk-productVersion.txt"
|
|
elif [[ "$runtime" == "dotnet" ]]; then
|
|
pvFileName="runtime-productVersion.txt"
|
|
else
|
|
pvFileName="$runtime-productVersion.txt"
|
|
fi
|
|
fi
|
|
|
|
local download_link=null
|
|
|
|
if [ -z "$package_download_link" ]; then
|
|
if [[ "$runtime" == "dotnet" ]]; then
|
|
download_link="$azure_feed/Runtime/$specific_version/${pvFileName}"
|
|
elif [[ "$runtime" == "aspnetcore" ]]; then
|
|
download_link="$azure_feed/aspnetcore/Runtime/$specific_version/${pvFileName}"
|
|
elif [ -z "$runtime" ]; then
|
|
download_link="$azure_feed/Sdk/$specific_version/${pvFileName}"
|
|
else
|
|
return 1
|
|
fi
|
|
else
|
|
download_link="${package_download_link%/*}/${pvFileName}"
|
|
fi
|
|
|
|
say_verbose "Constructed productVersion link: $download_link"
|
|
echo "$download_link"
|
|
return 0
|
|
}
|
|
|
|
# args:
|
|
# download link - $1
|
|
# specific version - $2
|
|
get_product_specific_version_from_download_link()
|
|
{
|
|
eval $invocation
|
|
|
|
local download_link="$1"
|
|
local specific_version="$2"
|
|
local specific_product_version=""
|
|
|
|
if [ -z "$download_link" ]; then
|
|
echo "$specific_version"
|
|
return 0
|
|
fi
|
|
|
|
#get filename
|
|
filename="${download_link##*/}"
|
|
|
|
#product specific version follows the product name
|
|
#for filename 'dotnet-sdk-3.1.404-linux-x64.tar.gz': the product version is 3.1.404
|
|
IFS='-'
|
|
read -ra filename_elems <<< "$filename"
|
|
count=${#filename_elems[@]}
|
|
if [[ "$count" -gt 2 ]]; then
|
|
specific_product_version="${filename_elems[2]}"
|
|
else
|
|
specific_product_version=$specific_version
|
|
fi
|
|
unset IFS;
|
|
echo "$specific_product_version"
|
|
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_legacy_os_name)" || return 1
|
|
|
|
local legacy_download_link=null
|
|
if [[ "$runtime" == "dotnet" ]]; then
|
|
legacy_download_link="$azure_feed/Runtime/$specific_version/dotnet-$distro_specific_osname-$normalized_architecture.$specific_version.tar.gz"
|
|
elif [ -z "$runtime" ]; then
|
|
legacy_download_link="$azure_feed/Sdk/$specific_version/dotnet-dev-$distro_specific_osname-$normalized_architecture.$specific_version.tar.gz"
|
|
else
|
|
return 1
|
|
fi
|
|
|
|
echo "$legacy_download_link"
|
|
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:
|
|
# 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 osname="$(get_current_os_name)"
|
|
local override_switch=$(
|
|
if [ "$override" = false ]; then
|
|
if [ "$osname" = "linux-musl" ]; then
|
|
printf -- "-u";
|
|
else
|
|
printf -- "-n";
|
|
fi
|
|
fi)
|
|
|
|
cat | uniq | while read -r file_path; do
|
|
local path="$(remove_beginning_slash "${file_path#$root_path}")"
|
|
local target="$out_path/$path"
|
|
if [ "$override" = true ] || (! ([ -d "$target" ] || [ -e "$target" ])); then
|
|
mkdir -p "$out_path/$(dirname "$path")"
|
|
if [ -d "$target" ]; then
|
|
rm -rf "$target"
|
|
fi
|
|
cp -R $override_switch "$root_path/$path" "$target"
|
|
fi
|
|
done
|
|
}
|
|
|
|
# args:
|
|
# zip_uri - $1
|
|
get_remote_file_size() {
|
|
local zip_uri="$1"
|
|
|
|
if machine_has "curl"; then
|
|
file_size=$(curl -sI "$zip_uri" | grep -i content-length | awk '{ num = $2 + 0; print num }')
|
|
elif machine_has "wget"; then
|
|
file_size=$(wget --spider --server-response -O /dev/null "$zip_uri" 2>&1 | grep -i 'Content-Length:' | awk '{ num = $2 + 0; print num }')
|
|
else
|
|
say "Neither curl nor wget is available on this system."
|
|
return
|
|
fi
|
|
|
|
if [ -n "$file_size" ]; then
|
|
say "Remote file $zip_uri size is $file_size bytes."
|
|
echo "$file_size"
|
|
else
|
|
say_verbose "Content-Length header was not extracted for $zip_uri."
|
|
echo ""
|
|
fi
|
|
}
|
|
|
|
# args:
|
|
# zip_path - $1
|
|
# out_path - $2
|
|
# remote_file_size - $3
|
|
extract_dotnet_package() {
|
|
eval $invocation
|
|
|
|
local zip_path="$1"
|
|
local out_path="$2"
|
|
local remote_file_size="$3"
|
|
|
|
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" | sort | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" false
|
|
find "$temp_out_path" -type f | grep -Ev "$folders_with_version_regex" | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" "$override_non_versioned_files"
|
|
|
|
validate_remote_local_file_sizes "$zip_path" "$remote_file_size"
|
|
|
|
rm -rf "$temp_out_path"
|
|
if [ -z ${keep_zip+x} ]; then
|
|
rm -f "$zip_path" && say_verbose "Temporary archive file $zip_path was removed"
|
|
fi
|
|
|
|
if [ "$failed" = true ]; then
|
|
say_err "Extraction failed"
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# args:
|
|
# remote_path - $1
|
|
# disable_feed_credential - $2
|
|
get_http_header()
|
|
{
|
|
eval $invocation
|
|
local remote_path="$1"
|
|
local disable_feed_credential="$2"
|
|
|
|
local failed=false
|
|
local response
|
|
if machine_has "curl"; then
|
|
get_http_header_curl $remote_path $disable_feed_credential || failed=true
|
|
elif machine_has "wget"; then
|
|
get_http_header_wget $remote_path $disable_feed_credential || failed=true
|
|
else
|
|
failed=true
|
|
fi
|
|
if [ "$failed" = true ]; then
|
|
say_verbose "Failed to get HTTP header: '$remote_path'."
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# args:
|
|
# remote_path - $1
|
|
# disable_feed_credential - $2
|
|
get_http_header_curl() {
|
|
eval $invocation
|
|
local remote_path="$1"
|
|
local disable_feed_credential="$2"
|
|
|
|
remote_path_with_credential="$remote_path"
|
|
if [ "$disable_feed_credential" = false ]; then
|
|
remote_path_with_credential+="$feed_credential"
|
|
fi
|
|
|
|
curl_options="-I -sSL --retry 5 --retry-delay 2 --connect-timeout 15 "
|
|
curl $curl_options "$remote_path_with_credential" 2>&1 || return 1
|
|
return 0
|
|
}
|
|
|
|
# args:
|
|
# remote_path - $1
|
|
# disable_feed_credential - $2
|
|
get_http_header_wget() {
|
|
eval $invocation
|
|
local remote_path="$1"
|
|
local disable_feed_credential="$2"
|
|
local wget_options="-q -S --spider --tries 5 "
|
|
|
|
local wget_options_extra=''
|
|
|
|
# Test for options that aren't supported on all wget implementations.
|
|
if [[ $(wget -h 2>&1 | grep -E 'waitretry|connect-timeout') ]]; then
|
|
wget_options_extra="--waitretry 2 --connect-timeout 15 "
|
|
else
|
|
say "wget extra options are unavailable for this environment"
|
|
fi
|
|
|
|
remote_path_with_credential="$remote_path"
|
|
if [ "$disable_feed_credential" = false ]; then
|
|
remote_path_with_credential+="$feed_credential"
|
|
fi
|
|
|
|
wget $wget_options $wget_options_extra "$remote_path_with_credential" 2>&1
|
|
|
|
return $?
|
|
}
|
|
|
|
# args:
|
|
# remote_path - $1
|
|
# [out_path] - $2 - stdout if not provided
|
|
download() {
|
|
eval $invocation
|
|
|
|
local remote_path="$1"
|
|
local out_path="${2:-}"
|
|
|
|
if [[ "$remote_path" != "http"* ]]; then
|
|
cp "$remote_path" "$out_path"
|
|
return $?
|
|
fi
|
|
|
|
local failed=false
|
|
local attempts=0
|
|
while [ $attempts -lt 3 ]; do
|
|
attempts=$((attempts+1))
|
|
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
|
|
say_err "Missing dependency: neither curl nor wget was found."
|
|
exit 1
|
|
fi
|
|
|
|
if [ "$failed" = false ] || [ $attempts -ge 3 ] || { [ ! -z $http_code ] && [ $http_code = "404" ]; }; then
|
|
break
|
|
fi
|
|
|
|
say "Download attempt #$attempts has failed: $http_code $download_error_msg"
|
|
say "Attempt #$((attempts+1)) will start in $((attempts*10)) seconds."
|
|
sleep $((attempts*10))
|
|
done
|
|
|
|
if [ "$failed" = true ]; then
|
|
say_verbose "Download failed: $remote_path"
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# Updates global variables $http_code and $download_error_msg
|
|
downloadcurl() {
|
|
eval $invocation
|
|
unset http_code
|
|
unset download_error_msg
|
|
local remote_path="$1"
|
|
local out_path="${2:-}"
|
|
# Append feed_credential as late as possible before calling curl to avoid logging feed_credential
|
|
# Avoid passing URI with credentials to functions: note, most of them echoing parameters of invocation in verbose output.
|
|
local remote_path_with_credential="${remote_path}${feed_credential}"
|
|
local curl_options="--retry 20 --retry-delay 2 --connect-timeout 15 -sSL -f --create-dirs "
|
|
local curl_exit_code=0;
|
|
if [ -z "$out_path" ]; then
|
|
curl $curl_options "$remote_path_with_credential" 2>&1
|
|
curl_exit_code=$?
|
|
else
|
|
curl $curl_options -o "$out_path" "$remote_path_with_credential" 2>&1
|
|
curl_exit_code=$?
|
|
fi
|
|
|
|
if [ $curl_exit_code -gt 0 ]; then
|
|
download_error_msg="Unable to download $remote_path."
|
|
# Check for curl timeout codes
|
|
if [[ $curl_exit_code == 7 || $curl_exit_code == 28 ]]; then
|
|
download_error_msg+=" Failed to reach the server: connection timeout."
|
|
else
|
|
local disable_feed_credential=false
|
|
local response=$(get_http_header_curl $remote_path $disable_feed_credential)
|
|
http_code=$( echo "$response" | awk '/^HTTP/{print $2}' | tail -1 )
|
|
if [[ ! -z $http_code && $http_code != 2* ]]; then
|
|
download_error_msg+=" Returned HTTP status code: $http_code."
|
|
fi
|
|
fi
|
|
say_verbose "$download_error_msg"
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
|
|
# Updates global variables $http_code and $download_error_msg
|
|
downloadwget() {
|
|
eval $invocation
|
|
unset http_code
|
|
unset download_error_msg
|
|
local remote_path="$1"
|
|
local out_path="${2:-}"
|
|
# Append feed_credential as late as possible before calling wget to avoid logging feed_credential
|
|
local remote_path_with_credential="${remote_path}${feed_credential}"
|
|
local wget_options="--tries 20 "
|
|
|
|
local wget_options_extra=''
|
|
local wget_result=''
|
|
|
|
# Test for options that aren't supported on all wget implementations.
|
|
if [[ $(wget -h 2>&1 | grep -E 'waitretry|connect-timeout') ]]; then
|
|
wget_options_extra="--waitretry 2 --connect-timeout 15 "
|
|
else
|
|
say "wget extra options are unavailable for this environment"
|
|
fi
|
|
|
|
if [ -z "$out_path" ]; then
|
|
wget -q $wget_options $wget_options_extra -O - "$remote_path_with_credential" 2>&1
|
|
wget_result=$?
|
|
else
|
|
wget $wget_options $wget_options_extra -O "$out_path" "$remote_path_with_credential" 2>&1
|
|
wget_result=$?
|
|
fi
|
|
|
|
if [[ $wget_result != 0 ]]; then
|
|
local disable_feed_credential=false
|
|
local response=$(get_http_header_wget $remote_path $disable_feed_credential)
|
|
http_code=$( echo "$response" | awk '/^ HTTP/{print $2}' | tail -1 )
|
|
download_error_msg="Unable to download $remote_path."
|
|
if [[ ! -z $http_code && $http_code != 2* ]]; then
|
|
download_error_msg+=" Returned HTTP status code: $http_code."
|
|
# wget exit code 4 stands for network-issue
|
|
elif [[ $wget_result == 4 ]]; then
|
|
download_error_msg+=" Failed to reach the server: connection timeout."
|
|
fi
|
|
say_verbose "$download_error_msg"
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
get_download_link_from_aka_ms() {
|
|
eval $invocation
|
|
|
|
#quality is not supported for LTS or STS channel
|
|
#STS maps to current
|
|
if [[ ! -z "$normalized_quality" && ("$normalized_channel" == "LTS" || "$normalized_channel" == "STS") ]]; then
|
|
normalized_quality=""
|
|
say_warning "Specifying quality for STS or LTS channel is not supported, the quality will be ignored."
|
|
fi
|
|
|
|
say_verbose "Retrieving primary payload URL from aka.ms for channel: '$normalized_channel', quality: '$normalized_quality', product: '$normalized_product', os: '$normalized_os', architecture: '$normalized_architecture'."
|
|
|
|
#construct aka.ms link
|
|
aka_ms_link="https://aka.ms/dotnet"
|
|
if [ "$internal" = true ]; then
|
|
aka_ms_link="$aka_ms_link/internal"
|
|
fi
|
|
aka_ms_link="$aka_ms_link/$normalized_channel"
|
|
if [[ ! -z "$normalized_quality" ]]; then
|
|
aka_ms_link="$aka_ms_link/$normalized_quality"
|
|
fi
|
|
aka_ms_link="$aka_ms_link/$normalized_product-$normalized_os-$normalized_architecture.tar.gz"
|
|
say_verbose "Constructed aka.ms link: '$aka_ms_link'."
|
|
|
|
#get HTTP response
|
|
#do not pass credentials as a part of the $aka_ms_link and do not apply credentials in the get_http_header function
|
|
#otherwise the redirect link would have credentials as well
|
|
#it would result in applying credentials twice to the resulting link and thus breaking it, and in echoing credentials to the output as a part of redirect link
|
|
disable_feed_credential=true
|
|
response="$(get_http_header $aka_ms_link $disable_feed_credential)"
|
|
|
|
say_verbose "Received response: $response"
|
|
# Get results of all the redirects.
|
|
http_codes=$( echo "$response" | awk '$1 ~ /^HTTP/ {print $2}' )
|
|
# They all need to be 301, otherwise some links are broken (except for the last, which is not a redirect but 200 or 404).
|
|
broken_redirects=$( echo "$http_codes" | sed '$d' | grep -v '301' )
|
|
# The response may end without final code 2xx/4xx/5xx somehow, e.g. network restrictions on www.bing.com causes redirecting to bing.com fails with connection refused.
|
|
# In this case it should not exclude the last.
|
|
last_http_code=$( echo "$http_codes" | tail -n 1 )
|
|
if ! [[ $last_http_code =~ ^(2|4|5)[0-9][0-9]$ ]]; then
|
|
broken_redirects=$( echo "$http_codes" | grep -v '301' )
|
|
fi
|
|
|
|
# All HTTP codes are 301 (Moved Permanently), the redirect link exists.
|
|
if [[ -z "$broken_redirects" ]]; then
|
|
aka_ms_download_link=$( echo "$response" | awk '$1 ~ /^Location/{print $2}' | tail -1 | tr -d '\r')
|
|
|
|
if [[ -z "$aka_ms_download_link" ]]; then
|
|
say_verbose "The aka.ms link '$aka_ms_link' is not valid: failed to get redirect location."
|
|
return 1
|
|
fi
|
|
|
|
say_verbose "The redirect location retrieved: '$aka_ms_download_link'."
|
|
return 0
|
|
else
|
|
say_verbose "The aka.ms link '$aka_ms_link' is not valid: received HTTP code: $(echo "$broken_redirects" | paste -sd "," -)."
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
get_feeds_to_use()
|
|
{
|
|
feeds=(
|
|
"https://dotnetcli.azureedge.net/dotnet"
|
|
"https://dotnetbuilds.azureedge.net/public"
|
|
)
|
|
|
|
if [[ -n "$azure_feed" ]]; then
|
|
feeds=("$azure_feed")
|
|
fi
|
|
|
|
if [[ "$no_cdn" == "true" ]]; then
|
|
feeds=(
|
|
"https://dotnetcli.blob.core.windows.net/dotnet"
|
|
"https://dotnetbuilds.blob.core.windows.net/public"
|
|
)
|
|
|
|
if [[ -n "$uncached_feed" ]]; then
|
|
feeds=("$uncached_feed")
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# THIS FUNCTION MAY EXIT (if the determined version is already installed).
|
|
generate_download_links() {
|
|
|
|
download_links=()
|
|
specific_versions=()
|
|
effective_versions=()
|
|
link_types=()
|
|
|
|
# If generate_akams_links returns false, no fallback to old links. Just terminate.
|
|
# This function may also 'exit' (if the determined version is already installed).
|
|
generate_akams_links || return
|
|
|
|
# Check other feeds only if we haven't been able to find an aka.ms link.
|
|
if [[ "${#download_links[@]}" -lt 1 ]]; then
|
|
for feed in ${feeds[@]}
|
|
do
|
|
# generate_regular_links may also 'exit' (if the determined version is already installed).
|
|
generate_regular_links $feed || return
|
|
done
|
|
fi
|
|
|
|
if [[ "${#download_links[@]}" -eq 0 ]]; then
|
|
say_err "Failed to resolve the exact version number."
|
|
return 1
|
|
fi
|
|
|
|
say_verbose "Generated ${#download_links[@]} links."
|
|
for link_index in ${!download_links[@]}
|
|
do
|
|
say_verbose "Link $link_index: ${link_types[$link_index]}, ${effective_versions[$link_index]}, ${download_links[$link_index]}"
|
|
done
|
|
}
|
|
|
|
# THIS FUNCTION MAY EXIT (if the determined version is already installed).
|
|
generate_akams_links() {
|
|
local valid_aka_ms_link=true;
|
|
|
|
normalized_version="$(to_lowercase "$version")"
|
|
if [[ "$normalized_version" != "latest" ]] && [ -n "$normalized_quality" ]; then
|
|
say_err "Quality and Version options are not allowed to be specified simultaneously. See https://learn.microsoft.com/dotnet/core/tools/dotnet-install-script#options for details."
|
|
return 1
|
|
fi
|
|
|
|
if [[ -n "$json_file" || "$normalized_version" != "latest" ]]; then
|
|
# aka.ms links are not needed when exact version is specified via command or json file
|
|
return
|
|
fi
|
|
|
|
get_download_link_from_aka_ms || valid_aka_ms_link=false
|
|
|
|
if [[ "$valid_aka_ms_link" == true ]]; then
|
|
say_verbose "Retrieved primary payload URL from aka.ms link: '$aka_ms_download_link'."
|
|
say_verbose "Downloading using legacy url will not be attempted."
|
|
|
|
download_link=$aka_ms_download_link
|
|
|
|
#get version from the path
|
|
IFS='/'
|
|
read -ra pathElems <<< "$download_link"
|
|
count=${#pathElems[@]}
|
|
specific_version="${pathElems[count-2]}"
|
|
unset IFS;
|
|
say_verbose "Version: '$specific_version'."
|
|
|
|
#Retrieve effective version
|
|
effective_version="$(get_specific_product_version "$azure_feed" "$specific_version" "$download_link")"
|
|
|
|
# Add link info to arrays
|
|
download_links+=($download_link)
|
|
specific_versions+=($specific_version)
|
|
effective_versions+=($effective_version)
|
|
link_types+=("aka.ms")
|
|
|
|
# Check if the SDK version is already installed.
|
|
if [[ "$dry_run" != true ]] && is_dotnet_package_installed "$install_root" "$asset_relative_path" "$effective_version"; then
|
|
say "$asset_name with version '$effective_version' is already installed."
|
|
exit 0
|
|
fi
|
|
|
|
return 0
|
|
fi
|
|
|
|
# if quality is specified - exit with error - there is no fallback approach
|
|
if [ ! -z "$normalized_quality" ]; then
|
|
say_err "Failed to locate the latest version in the channel '$normalized_channel' with '$normalized_quality' quality for '$normalized_product', os: '$normalized_os', architecture: '$normalized_architecture'."
|
|
say_err "Refer to: https://aka.ms/dotnet-os-lifecycle for information on .NET Core support."
|
|
return 1
|
|
fi
|
|
say_verbose "Falling back to latest.version file approach."
|
|
}
|
|
|
|
# THIS FUNCTION MAY EXIT (if the determined version is already installed)
|
|
# args:
|
|
# feed - $1
|
|
generate_regular_links() {
|
|
local feed="$1"
|
|
local valid_legacy_download_link=true
|
|
|
|
specific_version=$(get_specific_version_from_version "$feed" "$channel" "$normalized_architecture" "$version" "$json_file") || specific_version='0'
|
|
|
|
if [[ "$specific_version" == '0' ]]; then
|
|
say_verbose "Failed to resolve the specific version number using feed '$feed'"
|
|
return
|
|
fi
|
|
|
|
effective_version="$(get_specific_product_version "$feed" "$specific_version")"
|
|
say_verbose "specific_version=$specific_version"
|
|
|
|
download_link="$(construct_download_link "$feed" "$channel" "$normalized_architecture" "$specific_version" "$normalized_os")"
|
|
say_verbose "Constructed primary named payload URL: $download_link"
|
|
|
|
# Add link info to arrays
|
|
download_links+=($download_link)
|
|
specific_versions+=($specific_version)
|
|
effective_versions+=($effective_version)
|
|
link_types+=("primary")
|
|
|
|
legacy_download_link="$(construct_legacy_download_link "$feed" "$channel" "$normalized_architecture" "$specific_version")" || valid_legacy_download_link=false
|
|
|
|
if [ "$valid_legacy_download_link" = true ]; then
|
|
say_verbose "Constructed legacy named payload URL: $legacy_download_link"
|
|
|
|
download_links+=($legacy_download_link)
|
|
specific_versions+=($specific_version)
|
|
effective_versions+=($effective_version)
|
|
link_types+=("legacy")
|
|
else
|
|
legacy_download_link=""
|
|
say_verbose "Cound not construct a legacy_download_link; omitting..."
|
|
fi
|
|
|
|
# Check if the SDK version is already installed.
|
|
if [[ "$dry_run" != true ]] && is_dotnet_package_installed "$install_root" "$asset_relative_path" "$effective_version"; then
|
|
say "$asset_name with version '$effective_version' is already installed."
|
|
exit 0
|
|
fi
|
|
}
|
|
|
|
print_dry_run() {
|
|
|
|
say "Payload URLs:"
|
|
|
|
for link_index in "${!download_links[@]}"
|
|
do
|
|
say "URL #$link_index - ${link_types[$link_index]}: ${download_links[$link_index]}"
|
|
done
|
|
|
|
resolved_version=${specific_versions[0]}
|
|
repeatable_command="./$script_name --version "\""$resolved_version"\"" --install-dir "\""$install_root"\"" --architecture "\""$normalized_architecture"\"" --os "\""$normalized_os"\"""
|
|
|
|
if [ ! -z "$normalized_quality" ]; then
|
|
repeatable_command+=" --quality "\""$normalized_quality"\"""
|
|
fi
|
|
|
|
if [[ "$runtime" == "dotnet" ]]; then
|
|
repeatable_command+=" --runtime "\""dotnet"\"""
|
|
elif [[ "$runtime" == "aspnetcore" ]]; then
|
|
repeatable_command+=" --runtime "\""aspnetcore"\"""
|
|
fi
|
|
|
|
repeatable_command+="$non_dynamic_parameters"
|
|
|
|
if [ -n "$feed_credential" ]; then
|
|
repeatable_command+=" --feed-credential "\""<feed_credential>"\"""
|
|
fi
|
|
|
|
say "Repeatable invocation: $repeatable_command"
|
|
}
|
|
|
|
calculate_vars() {
|
|
eval $invocation
|
|
|
|
script_name=$(basename "$0")
|
|
normalized_architecture="$(get_normalized_architecture_from_architecture "$architecture")"
|
|
say_verbose "Normalized architecture: '$normalized_architecture'."
|
|
normalized_os="$(get_normalized_os "$user_defined_os")"
|
|
say_verbose "Normalized OS: '$normalized_os'."
|
|
normalized_quality="$(get_normalized_quality "$quality")"
|
|
say_verbose "Normalized quality: '$normalized_quality'."
|
|
normalized_channel="$(get_normalized_channel "$channel")"
|
|
say_verbose "Normalized channel: '$normalized_channel'."
|
|
normalized_product="$(get_normalized_product "$runtime")"
|
|
say_verbose "Normalized product: '$normalized_product'."
|
|
install_root="$(resolve_installation_path "$install_dir")"
|
|
say_verbose "InstallRoot: '$install_root'."
|
|
|
|
normalized_architecture="$(get_normalized_architecture_for_specific_sdk_version "$version" "$normalized_channel" "$normalized_architecture")"
|
|
|
|
if [[ "$runtime" == "dotnet" ]]; then
|
|
asset_relative_path="shared/Microsoft.NETCore.App"
|
|
asset_name=".NET Core Runtime"
|
|
elif [[ "$runtime" == "aspnetcore" ]]; then
|
|
asset_relative_path="shared/Microsoft.AspNetCore.App"
|
|
asset_name="ASP.NET Core Runtime"
|
|
elif [ -z "$runtime" ]; then
|
|
asset_relative_path="sdk"
|
|
asset_name=".NET Core SDK"
|
|
fi
|
|
|
|
get_feeds_to_use
|
|
}
|
|
|
|
install_dotnet() {
|
|
eval $invocation
|
|
local download_failed=false
|
|
local download_completed=false
|
|
local remote_file_size=0
|
|
|
|
mkdir -p "$install_root"
|
|
zip_path="${zip_path:-$(mktemp "$temporary_file_template")}"
|
|
say_verbose "Archive path: $zip_path"
|
|
|
|
for link_index in "${!download_links[@]}"
|
|
do
|
|
download_link="${download_links[$link_index]}"
|
|
specific_version="${specific_versions[$link_index]}"
|
|
effective_version="${effective_versions[$link_index]}"
|
|
link_type="${link_types[$link_index]}"
|
|
|
|
say "Attempting to download using $link_type link $download_link"
|
|
|
|
# The download function will set variables $http_code and $download_error_msg in case of failure.
|
|
download_failed=false
|
|
download "$download_link" "$zip_path" 2>&1 || download_failed=true
|
|
|
|
if [ "$download_failed" = true ]; then
|
|
case $http_code in
|
|
404)
|
|
say "The resource at $link_type link '$download_link' is not available."
|
|
;;
|
|
*)
|
|
say "Failed to download $link_type link '$download_link': $download_error_msg"
|
|
;;
|
|
esac
|
|
rm -f "$zip_path" 2>&1 && say_verbose "Temporary archive file $zip_path was removed"
|
|
else
|
|
download_completed=true
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [[ "$download_completed" == false ]]; then
|
|
say_err "Could not find \`$asset_name\` with version = $specific_version"
|
|
say_err "Refer to: https://aka.ms/dotnet-os-lifecycle for information on .NET Core support"
|
|
return 1
|
|
fi
|
|
|
|
remote_file_size="$(get_remote_file_size "$download_link")"
|
|
|
|
say "Extracting archive from $download_link"
|
|
extract_dotnet_package "$zip_path" "$install_root" "$remote_file_size" || return 1
|
|
|
|
# Check if the SDK version is installed; if not, fail the installation.
|
|
# if the version contains "RTM" or "servicing"; check if a 'release-type' SDK version is installed.
|
|
if [[ $specific_version == *"rtm"* || $specific_version == *"servicing"* ]]; then
|
|
IFS='-'
|
|
read -ra verArr <<< "$specific_version"
|
|
release_version="${verArr[0]}"
|
|
unset IFS;
|
|
say_verbose "Checking installation: version = $release_version"
|
|
if is_dotnet_package_installed "$install_root" "$asset_relative_path" "$release_version"; then
|
|
say "Installed version is $effective_version"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
# Check if the standard SDK version is installed.
|
|
say_verbose "Checking installation: version = $effective_version"
|
|
if is_dotnet_package_installed "$install_root" "$asset_relative_path" "$effective_version"; then
|
|
say "Installed version is $effective_version"
|
|
return 0
|
|
fi
|
|
|
|
# Version verification failed. More likely something is wrong either with the downloaded content or with the verification algorithm.
|
|
say_err "Failed to verify the version of installed \`$asset_name\`.\nInstallation source: $download_link.\nInstallation location: $install_root.\nReport the bug at https://github.com/dotnet/install-scripts/issues."
|
|
say_err "\`$asset_name\` with version = $effective_version failed to install with an error."
|
|
return 1
|
|
}
|
|
|
|
args=("$@")
|
|
|
|
local_version_file_relative_path="/.version"
|
|
bin_folder_relative_path=""
|
|
temporary_file_template="${TMPDIR:-/tmp}/dotnet.XXXXXXXXX"
|
|
|
|
channel="LTS"
|
|
version="Latest"
|
|
json_file=""
|
|
install_dir="<auto>"
|
|
architecture="<auto>"
|
|
dry_run=false
|
|
no_path=false
|
|
no_cdn=false
|
|
azure_feed=""
|
|
uncached_feed=""
|
|
feed_credential=""
|
|
verbose=false
|
|
runtime=""
|
|
runtime_id=""
|
|
quality=""
|
|
internal=false
|
|
override_non_versioned_files=true
|
|
non_dynamic_parameters=""
|
|
user_defined_os=""
|
|
|
|
while [ $# -ne 0 ]
|
|
do
|
|
name="$1"
|
|
case "$name" in
|
|
-c|--channel|-[Cc]hannel)
|
|
shift
|
|
channel="$1"
|
|
;;
|
|
-v|--version|-[Vv]ersion)
|
|
shift
|
|
version="$1"
|
|
;;
|
|
-q|--quality|-[Qq]uality)
|
|
shift
|
|
quality="$1"
|
|
;;
|
|
--internal|-[Ii]nternal)
|
|
internal=true
|
|
non_dynamic_parameters+=" $name"
|
|
;;
|
|
-i|--install-dir|-[Ii]nstall[Dd]ir)
|
|
shift
|
|
install_dir="$1"
|
|
;;
|
|
--arch|--architecture|-[Aa]rch|-[Aa]rchitecture)
|
|
shift
|
|
architecture="$1"
|
|
;;
|
|
--os|-[Oo][SS])
|
|
shift
|
|
user_defined_os="$1"
|
|
;;
|
|
--shared-runtime|-[Ss]hared[Rr]untime)
|
|
say_warning "The --shared-runtime flag is obsolete and may be removed in a future version of this script. The recommended usage is to specify '--runtime dotnet'."
|
|
if [ -z "$runtime" ]; then
|
|
runtime="dotnet"
|
|
fi
|
|
;;
|
|
--runtime|-[Rr]untime)
|
|
shift
|
|
runtime="$1"
|
|
if [[ "$runtime" != "dotnet" ]] && [[ "$runtime" != "aspnetcore" ]]; then
|
|
say_err "Unsupported value for --runtime: '$1'. Valid values are 'dotnet' and 'aspnetcore'."
|
|
if [[ "$runtime" == "windowsdesktop" ]]; then
|
|
say_err "WindowsDesktop archives are manufactured for Windows platforms only."
|
|
fi
|
|
exit 1
|
|
fi
|
|
;;
|
|
--dry-run|-[Dd]ry[Rr]un)
|
|
dry_run=true
|
|
;;
|
|
--no-path|-[Nn]o[Pp]ath)
|
|
no_path=true
|
|
non_dynamic_parameters+=" $name"
|
|
;;
|
|
--verbose|-[Vv]erbose)
|
|
verbose=true
|
|
non_dynamic_parameters+=" $name"
|
|
;;
|
|
--no-cdn|-[Nn]o[Cc]dn)
|
|
no_cdn=true
|
|
non_dynamic_parameters+=" $name"
|
|
;;
|
|
--azure-feed|-[Aa]zure[Ff]eed)
|
|
shift
|
|
azure_feed="$1"
|
|
non_dynamic_parameters+=" $name "\""$1"\"""
|
|
;;
|
|
--uncached-feed|-[Uu]ncached[Ff]eed)
|
|
shift
|
|
uncached_feed="$1"
|
|
non_dynamic_parameters+=" $name "\""$1"\"""
|
|
;;
|
|
--feed-credential|-[Ff]eed[Cc]redential)
|
|
shift
|
|
feed_credential="$1"
|
|
#feed_credential should start with "?", for it to be added to the end of the link.
|
|
#adding "?" at the beginning of the feed_credential if needed.
|
|
[[ -z "$(echo $feed_credential)" ]] || [[ $feed_credential == \?* ]] || feed_credential="?$feed_credential"
|
|
;;
|
|
--runtime-id|-[Rr]untime[Ii]d)
|
|
shift
|
|
runtime_id="$1"
|
|
non_dynamic_parameters+=" $name "\""$1"\"""
|
|
say_warning "Use of --runtime-id is obsolete and should be limited to the versions below 2.1. To override architecture, use --architecture option instead. To override OS, use --os option instead."
|
|
;;
|
|
--jsonfile|-[Jj][Ss]on[Ff]ile)
|
|
shift
|
|
json_file="$1"
|
|
;;
|
|
--skip-non-versioned-files|-[Ss]kip[Nn]on[Vv]ersioned[Ff]iles)
|
|
override_non_versioned_files=false
|
|
non_dynamic_parameters+=" $name"
|
|
;;
|
|
--keep-zip|-[Kk]eep[Zz]ip)
|
|
keep_zip=true
|
|
non_dynamic_parameters+=" $name"
|
|
;;
|
|
--zip-path|-[Zz]ip[Pp]ath)
|
|
shift
|
|
zip_path="$1"
|
|
;;
|
|
-?|--?|-h|--help|-[Hh]elp)
|
|
script_name="$(basename "$0")"
|
|
echo ".NET Tools Installer"
|
|
echo "Usage:"
|
|
echo " # Install a .NET SDK of a given Quality from a given Channel"
|
|
echo " $script_name [-c|--channel <CHANNEL>] [-q|--quality <QUALITY>]"
|
|
echo " # Install a .NET SDK of a specific public version"
|
|
echo " $script_name [-v|--version <VERSION>]"
|
|
echo " $script_name -h|-?|--help"
|
|
echo ""
|
|
echo "$script_name is a simple command line interface for obtaining dotnet cli."
|
|
echo " Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:"
|
|
echo " - The SDK needs to be installed without user interaction and without admin rights."
|
|
echo " - The SDK installation doesn't need to persist across multiple CI runs."
|
|
echo " To set up a development environment or to run apps, use installers rather than this script. Visit https://dotnet.microsoft.com/download to get the installer."
|
|
echo ""
|
|
echo "Options:"
|
|
echo " -c,--channel <CHANNEL> Download from the channel specified, Defaults to \`$channel\`."
|
|
echo " -Channel"
|
|
echo " Possible values:"
|
|
echo " - STS - the most recent Standard Term Support release"
|
|
echo " - LTS - the most recent Long Term Support release"
|
|
echo " - 2-part version in a format A.B - represents a specific release"
|
|
echo " examples: 2.0; 1.0"
|
|
echo " - 3-part version in a format A.B.Cxx - represents a specific SDK release"
|
|
echo " examples: 5.0.1xx, 5.0.2xx."
|
|
echo " Supported since 5.0 release"
|
|
echo " Warning: Value 'Current' is deprecated for the Channel parameter. Use 'STS' instead."
|
|
echo " Note: The version parameter overrides the channel parameter when any version other than 'latest' is used."
|
|
echo " -v,--version <VERSION> Use specific VERSION, Defaults to \`$version\`."
|
|
echo " -Version"
|
|
echo " Possible values:"
|
|
echo " - latest - the latest build on specific channel"
|
|
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 " -q,--quality <quality> Download the latest build of specified quality in the channel."
|
|
echo " -Quality"
|
|
echo " The possible values are: daily, signed, validated, preview, GA."
|
|
echo " Works only in combination with channel. Not applicable for STS and LTS channels and will be ignored if those channels are used."
|
|
echo " For SDK use channel in A.B.Cxx format. Using quality for SDK together with channel in A.B format is not supported."
|
|
echo " Supported since 5.0 release."
|
|
echo " Note: The version parameter overrides the channel parameter when any version other than 'latest' is used, and therefore overrides the quality."
|
|
echo " --internal,-Internal Download internal builds. Requires providing credentials via --feed-credential parameter."
|
|
echo " --feed-credential <FEEDCREDENTIAL> Token to access Azure feed. Used as a query string to append to the Azure feed."
|
|
echo " -FeedCredential This parameter typically is not specified."
|
|
echo " -i,--install-dir <DIR> Install under specified location (see Install Location below)"
|
|
echo " -InstallDir"
|
|
echo " --architecture <ARCHITECTURE> Architecture of dotnet binaries to be installed, Defaults to \`$architecture\`."
|
|
echo " --arch,-Architecture,-Arch"
|
|
echo " Possible values: x64, arm, arm64, s390x, ppc64le and loongarch64"
|
|
echo " --os <system> Specifies operating system to be used when selecting the installer."
|
|
echo " Overrides the OS determination approach used by the script. Supported values: osx, linux, linux-musl, freebsd, rhel.6."
|
|
echo " In case any other value is provided, the platform will be determined by the script based on machine configuration."
|
|
echo " Not supported for legacy links. Use --runtime-id to specify platform for legacy links."
|
|
echo " Refer to: https://aka.ms/dotnet-os-lifecycle for more information."
|
|
echo " --runtime <RUNTIME> Installs a shared runtime only, without the SDK."
|
|
echo " -Runtime"
|
|
echo " Possible values:"
|
|
echo " - dotnet - the Microsoft.NETCore.App shared runtime"
|
|
echo " - aspnetcore - the Microsoft.AspNetCore.App shared runtime"
|
|
echo " --dry-run,-DryRun Do not perform installation. Display download link."
|
|
echo " --no-path, -NoPath Do not set PATH for the current process."
|
|
echo " --verbose,-Verbose Display diagnostics information."
|
|
echo " --azure-feed,-AzureFeed For internal use only."
|
|
echo " Allows using a different storage to download SDK archives from."
|
|
echo " This parameter is only used if --no-cdn is false."
|
|
echo " --uncached-feed,-UncachedFeed For internal use only."
|
|
echo " Allows using a different storage to download SDK archives from."
|
|
echo " This parameter is only used if --no-cdn is true."
|
|
echo " --skip-non-versioned-files Skips non-versioned files if they already exist, such as the dotnet executable."
|
|
echo " -SkipNonVersionedFiles"
|
|
echo " --no-cdn,-NoCdn Disable downloading from the Azure CDN, and use the uncached feed directly."
|
|
echo " --jsonfile <JSONFILE> Determines the SDK version from a user specified global.json file."
|
|
echo " Note: global.json must have a value for 'SDK:Version'"
|
|
echo " --keep-zip,-KeepZip If set, downloaded file is kept."
|
|
echo " --zip-path, -ZipPath If set, downloaded file is stored at the specified path."
|
|
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
|
|
|
|
say_verbose "Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:"
|
|
say_verbose "- The SDK needs to be installed without user interaction and without admin rights."
|
|
say_verbose "- The SDK installation doesn't need to persist across multiple CI runs."
|
|
say_verbose "To set up a development environment or to run apps, use installers rather than this script. Visit https://dotnet.microsoft.com/download to get the installer.\n"
|
|
|
|
if [ "$internal" = true ] && [ -z "$(echo $feed_credential)" ]; then
|
|
message="Provide credentials via --feed-credential parameter."
|
|
if [ "$dry_run" = true ]; then
|
|
say_warning "$message"
|
|
else
|
|
say_err "$message"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
check_min_reqs
|
|
calculate_vars
|
|
# generate_regular_links call below will 'exit' if the determined version is already installed.
|
|
generate_download_links
|
|
|
|
if [[ "$dry_run" = true ]]; then
|
|
print_dry_run
|
|
exit 0
|
|
fi
|
|
|
|
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 "Note that the script does not resolve dependencies during installation."
|
|
say "To check the list of dependencies, go to https://learn.microsoft.com/dotnet/core/install, select your operating system and check the \"Dependencies\" section."
|
|
say "Installation finished successfully."
|