dotnet-installer/scripts/obtain/dotnet-install.sh
Omair Majid 46a1d6f41e Add --runtime-id flag to dotnet-install.sh
This lets us specify the runtime id of the desired .NET Core SDK on the
command line. This makes it easier to get the SDK for the desired
runtime without having to modify the install script for new runtimes
unsupported by the current version of this script.
2017-03-14 17:52:57 -04:00

718 lines
20 KiB
Bash
Executable file

# 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.
#
# Note: This script should be compatible with the dash shell used in Ubuntu. So avoid bashisms! See https://wiki.ubuntu.com/DashAsBinSh for more info
# 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 [ "$linux_portable" = true ]; then
echo "linux"
return 0
elif [ "$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_err "OS name could not be detected: $ID.$VERSION_ID"
return 1
}
machine_has() {
eval $invocation
hash "$1" > /dev/null 2>&1
return $?
}
check_min_reqs() {
if ! machine_has "curl"; then
say_err "curl is required to download dotnet. Install curl 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 libcurl)" ] && say_err "Unable to locate libcurl. Install libcurl 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
# azure_channel - $2
# normalized_architecture - $3
get_latest_version_info() {
eval $invocation
local azure_feed=$1
local azure_channel=$2
local normalized_architecture=$3
local osname
osname=$(get_current_os_name) || return 1
local version_file_url=null
if [ "$shared_runtime" = true ]; then
version_file_url="$uncached_feed/$azure_channel/dnvm/latest.sharedfx.$osname.$normalized_architecture.version"
else
version_file_url="$uncached_feed/Sdk/$azure_channel/latest.version"
fi
say_verbose "get_latest_version_info: latest url: $version_file_url"
download $version_file_url
return $?
}
# args:
# channel - $1
get_azure_channel_from_channel() {
eval $invocation
local channel=$(to_lowercase $1)
case $channel in
future|dev)
echo "dev"
return 0
;;
production)
say_err "Production channel does not exist yet"
return 1
esac
echo $channel
return 0
}
# args:
# azure_feed - $1
# azure_channel - $2
# normalized_architecture - $3
# version - $4
get_specific_version_from_version() {
eval $invocation
local azure_feed=$1
local azure_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 $azure_channel $normalized_architecture)" || return 1
say_verbose "get_specific_version_from_version: version_info=$version_info"
echo "$version_info" | get_version_from_version_info
return 0
;;
lkg)
say_err "``--version LKG`` not supported yet."
return 1
;;
*)
echo $version
return 0
;;
esac
}
# args:
# azure_feed - $1
# azure_channel - $2
# normalized_architecture - $3
# specific_version - $4
construct_download_link() {
eval $invocation
local azure_feed=$1
local azure_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/$azure_channel/Binaries/$specific_version/dotnet-$osname-$normalized_architecture.$specific_version.tar.gz"
else
download_link="$azure_feed/Sdk/$specific_version/dotnet-dev-$osname-$normalized_architecture.$specific_version.tar.gz"
fi
echo "$download_link"
return 0
}
get_user_share_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_share_path=$(get_user_share_path)
say_verbose "resolve_installation_path: share_path=$user_share_path"
echo "$user_share_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 [ -z "$out_path" ]; then
curl --fail -s $remote_path || failed=true
else
curl --fail -s -o $out_path $remote_path || failed=true
fi
if [ "$failed" = true ]; then
say_err "Download failed"
return 1
fi
}
calculate_vars() {
eval $invocation
azure_channel=$(get_azure_channel_from_channel "$channel")
say_verbose "azure_channel=$azure_channel"
normalized_architecture=$(get_normalized_architecture_from_architecture "$architecture")
say_verbose "normalized_architecture=$normalized_architecture"
specific_version=$(get_specific_version_from_version $azure_feed $azure_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 $azure_channel $normalized_architecture $specific_version)
say_verbose "download_link=$download_link"
install_root=$(resolve_installation_path $install_dir)
say_verbose "install_root=$install_root"
}
install_dotnet() {
eval $invocation
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 $download_link"
download "$download_link" $zip_path
say_verbose "Downloaded file exists and readable? $(if [ -r $zip_path ]; then echo "yes"; else echo "no"; fi)"
say "Extracting zip"
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="rel-1.0.0"
version="Latest"
install_dir="<auto>"
architecture="<auto>"
debug_symbols=false
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
linux_portable=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
;;
--debug-symbols|-[Dd]ebug[Ss]ymbols)
debug_symbols=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"
;;
--linux-portable|-[Ll]inux[Pp]ortable)
linux_portable=true
;;
--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 (default: $channel)."
echo " -Channel"
echo " -v,--version <VERSION> Use specific version, ``latest`` or ``lkg``. Defaults to ``latest``."
echo " -Version"
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 " --debug-symbols,-DebugSymbols Specifies if symbols should be included in the installation."
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"
echo " --linux-portable Installs the Linux portable .NET Tools instead of a distro-specific version."
echo " -LinuxPortable"
echo " --runtime-id Installs the .NET Tools for the given platform (such as linux-x64)."
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 " - /usr/local/share/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"
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."