2 # Copyright (c) .NET Foundation and contributors. All rights reserved.
3 # Licensed under the MIT license. See LICENSE file in the project root for full license information.
8 # Stop script if unbound variable found (use ${var:-} if intentional)
10 # By default cmd1 | cmd2 returns exit code of cmd2 regardless of cmd1 success
11 # This is causing it to fail
14 # Use in the the functions: eval $invocation
15 invocation='say_verbose "Calling: ${yellow:-}${FUNCNAME[0]} ${green:-}$*${normal:-}"'
17 # standard output may be used as a return value in the functions
18 # we need a way to write text on the screen in the functions so that
19 # it won't interfere with the return value.
20 # Exposing stream 3 as a pipe to standard output of the script itself
23 # Setup some colors to use. These need to work in fairly limited shells, like the Ubuntu Docker container where there are only 8 colors.
24 # See if stdout is a terminal
25 if [ -t 1 ] && command -v tput > /dev/null; then
26 # see if it supports colors
27 ncolors=$(tput colors || echo 0)
28 if [ -n "$ncolors" ] && [ $ncolors -ge 8 ]; then
29 bold="$(tput bold || echo)"
30 normal="$(tput sgr0 || echo)"
31 black="$(tput setaf 0 || echo)"
32 red="$(tput setaf 1 || echo)"
33 green="$(tput setaf 2 || echo)"
34 yellow="$(tput setaf 3 || echo)"
35 blue="$(tput setaf 4 || echo)"
36 magenta="$(tput setaf 5 || echo)"
37 cyan="$(tput setaf 6 || echo)"
38 white="$(tput setaf 7 || echo)"
43 printf "%b\n" "${yellow:-}dotnet_install: Warning: $1${normal:-}" >&3
47 printf "%b\n" "${red:-}dotnet_install: Error: $1${normal:-}" >&2
51 # using stream 3 (defined in the beginning) to not interfere with stdout of functions
52 # which may be used as return value
53 printf "%b\n" "${cyan:-}dotnet-install:${normal:-} $1" >&3
57 if [ "$verbose" = true ]; then
62 # This platform list is finite - if the SDK/Runtime has supported Linux distribution-specific assets,
63 # then and only then should the Linux distribution appear in this list.
64 # Adding a Linux distribution to this list does not imply distribution-specific support.
65 get_legacy_os_name_from_platform() {
138 get_legacy_os_name() {
142 if [ "$uname" = "Darwin" ]; then
145 elif [ -n "$runtime_id" ]; then
146 echo $(get_legacy_os_name_from_platform "${runtime_id%-*}" || echo "${runtime_id%-*}")
149 if [ -e /etc/os-release ]; then
151 os=$(get_legacy_os_name_from_platform "$ID${VERSION_ID:+.${VERSION_ID}}" || echo "")
152 if [ -n "$os" ]; then
159 say_verbose "Distribution specific OS name and version could not be detected: UName = $uname"
163 get_linux_platform_name() {
166 if [ -n "$runtime_id" ]; then
167 echo "${runtime_id%-*}"
170 if [ -e /etc/os-release ]; then
172 echo "$ID${VERSION_ID:+.${VERSION_ID}}"
174 elif [ -e /etc/redhat-release ]; then
175 local redhatRelease=$(</etc/redhat-release)
176 if [[ $redhatRelease == "CentOS release 6."* || $redhatRelease == "Red Hat Enterprise Linux "*" release 6."* ]]; then
183 say_verbose "Linux specific platform name and version could not be detected: UName = $uname"
187 is_musl_based_distro() {
188 (ldd --version 2>&1 || true) | grep -q musl
191 get_current_os_name() {
195 if [ "$uname" = "Darwin" ]; then
198 elif [ "$uname" = "FreeBSD" ]; then
201 elif [ "$uname" = "Linux" ]; then
202 local linux_platform_name=""
203 linux_platform_name="$(get_linux_platform_name)" || true
205 if [ "$linux_platform_name" = "rhel.6" ]; then
206 echo $linux_platform_name
208 elif is_musl_based_distro; then
211 elif [ "$linux_platform_name" = "linux-musl" ]; then
220 say_err "OS name could not be detected: UName = $uname"
227 command -v "$1" > /dev/null 2>&1
232 local hasMinimum=false
233 if machine_has "curl"; then
235 elif machine_has "wget"; then
239 if [ "$hasMinimum" = "false" ]; then
240 say_err "curl (recommended) or wget are required to download dotnet. Install missing prerequisite to proceed."
251 echo "$1" | tr '[:upper:]' '[:lower:]'
257 remove_trailing_slash() {
267 remove_beginning_slash() {
277 # child_path - $2 - this parameter can be empty
281 # TODO: Consider making it work with any number of paths. For now:
282 if [ ! -z "${3:-}" ]; then
283 say_err "combine_paths: Function takes two parameters."
287 local root_path="$(remove_trailing_slash "$1")"
288 local child_path="$(remove_beginning_slash "${2:-}")"
289 say_verbose "combine_paths: root_path=$root_path"
290 say_verbose "combine_paths: child_path=$child_path"
291 echo "$root_path/$child_path"
295 get_machine_architecture() {
298 if command -v uname > /dev/null; then
324 # Always default to 'x64'
331 get_normalized_architecture_from_architecture() {
334 local architecture="$(to_lowercase "$1")"
336 if [[ $architecture == \<auto\> ]]; then
337 echo "$(get_machine_architecture)"
341 case "$architecture" in
368 say_err "Architecture \`$architecture\` not supported. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues"
376 get_normalized_architecture_for_specific_sdk_version() {
379 local is_version_support_arm64="$(is_arm64_supported "$1")"
380 local is_channel_support_arm64="$(is_arm64_supported "$2")"
381 local architecture="$3";
382 local osname="$(get_current_os_name)"
384 if [ "$osname" == "osx" ] && [ "$architecture" == "arm64" ] && { [ "$is_version_support_arm64" = false ] || [ "$is_channel_support_arm64" = false ]; }; then
385 #check if rosetta is installed
386 if [ "$(/usr/bin/pgrep oahd >/dev/null 2>&1;echo $?)" -eq 0 ]; then
387 say_verbose "Changing user architecture from '$architecture' to 'x64' because .NET SDKs prior to version 6.0 do not support arm64."
391 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"
401 # version or channel - $1
402 is_arm64_supported() {
403 #any channel or version that starts with the specified versions
405 ( "1"* | "2"* | "3"* | "4"* | "5"*)
415 # user_defined_os - $1
416 get_normalized_os() {
419 local osname="$(to_lowercase "$1")"
420 if [ ! -z "$osname" ]; then
422 osx | freebsd | rhel.6 | linux-musl | linux)
432 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."
437 osname="$(get_current_os_name)" || return 1
445 get_normalized_quality() {
448 local quality="$(to_lowercase "$1")"
449 if [ ! -z "$quality" ]; then
451 daily | signed | validated | preview)
456 #ga quality is available without specifying quality, so normalizing it to empty
460 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."
470 get_normalized_channel() {
473 local channel="$(to_lowercase "$1")"
475 if [[ $channel == current ]]; then
476 say_warning 'Value "Current" is deprecated for -Channel option. Use "STS" instead.'
479 if [[ $channel == release/* ]]; then
480 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.';
483 if [ ! -z "$channel" ]; then
509 get_normalized_product() {
513 local runtime="$(to_lowercase "$1")"
514 if [[ "$runtime" == "dotnet" ]]; then
515 product="dotnet-runtime"
516 elif [[ "$runtime" == "aspnetcore" ]]; then
517 product="aspnetcore-runtime"
518 elif [ -z "$runtime" ]; then
525 # The version text returned from the feeds is a 1-line or 2-line string:
526 # For the SDK and the dotnet runtime (2 lines):
527 # Line 1: # commit_hash
528 # Line 2: # 4-part version
529 # For the aspnetcore runtime (1 line):
530 # Line 1: # 4-part version
533 # version_text - stdin
534 get_version_from_latestversion_file_content() {
537 cat | tail -n 1 | sed 's/\r$//'
543 # relative_path_to_package - $2
544 # specific_version - $3
545 is_dotnet_package_installed() {
548 local install_root="$1"
549 local relative_path_to_package="$2"
550 local specific_version="${3//[$'\t\r\n']}"
552 local dotnet_package_path="$(combine_paths "$(combine_paths "$install_root" "$relative_path_to_package")" "$specific_version")"
553 say_verbose "is_dotnet_package_installed: dotnet_package_path=$dotnet_package_path"
555 if [ -d "$dotnet_package_path" ]; then
563 # downloaded file - $1
564 # remote_file_size - $2
565 validate_remote_local_file_sizes()
569 local downloaded_file="$1"
570 local remote_file_size="$2"
573 if [[ "$OSTYPE" == "linux-gnu"* ]]; then
574 file_size="$(stat -c '%s' "$downloaded_file")"
575 elif [[ "$OSTYPE" == "darwin"* ]]; then
576 # hardcode in order to avoid conflicts with GNU stat
577 file_size="$(/usr/bin/stat -f '%z' "$downloaded_file")"
580 if [ -n "$file_size" ]; then
581 say "Downloaded file size is $file_size bytes."
583 if [ -n "$remote_file_size" ] && [ -n "$file_size" ]; then
584 if [ "$remote_file_size" -ne "$file_size" ]; then
585 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."
587 say "The remote and local file sizes are equal."
592 say "Either downloaded or local package size can not be measured. One of them may be corrupted."
599 # normalized_architecture - $3
600 get_version_from_latestversion_file() {
603 local azure_feed="$1"
605 local normalized_architecture="$3"
607 local version_file_url=null
608 if [[ "$runtime" == "dotnet" ]]; then
609 version_file_url="$azure_feed/Runtime/$channel/latest.version"
610 elif [[ "$runtime" == "aspnetcore" ]]; then
611 version_file_url="$azure_feed/aspnetcore/Runtime/$channel/latest.version"
612 elif [ -z "$runtime" ]; then
613 version_file_url="$azure_feed/Sdk/$channel/latest.version"
615 say_err "Invalid value for \$runtime"
618 say_verbose "get_version_from_latestversion_file: latest url: $version_file_url"
620 download "$version_file_url" || return $?
626 parse_globaljson_file_for_version() {
630 if [ ! -f "$json_file" ]; then
631 say_err "Unable to find \`$json_file\`"
635 sdk_section=$(cat $json_file | tr -d "\r" | awk '/"sdk"/,/}/')
636 if [ -z "$sdk_section" ]; then
637 say_err "Unable to parse the SDK node in \`$json_file\`"
641 sdk_list=$(echo $sdk_section | awk -F"[{}]" '{print $2}')
642 sdk_list=${sdk_list//[\" ]/}
643 sdk_list=${sdk_list//,/$'\n'}
645 local version_info=""
646 while read -r line; do
648 while read -r key value; do
649 if [[ "$key" == "version" ]]; then
654 if [ -z "$version_info" ]; then
655 say_err "Unable to find the SDK:version node in \`$json_file\`"
667 # normalized_architecture - $3
670 get_specific_version_from_version() {
673 local azure_feed="$1"
675 local normalized_architecture="$3"
676 local version="$(to_lowercase "$4")"
679 if [ -z "$json_file" ]; then
680 if [[ "$version" == "latest" ]]; then
682 version_info="$(get_version_from_latestversion_file "$azure_feed" "$channel" "$normalized_architecture" false)" || return 1
683 say_verbose "get_specific_version_from_version: version_info=$version_info"
684 echo "$version_info" | get_version_from_latestversion_file_content
692 version_info="$(parse_globaljson_file_for_version "$json_file")" || return 1
701 # normalized_architecture - $3
702 # specific_version - $4
704 construct_download_link() {
707 local azure_feed="$1"
709 local normalized_architecture="$3"
710 local specific_version="${4//[$'\t\r\n']}"
711 local specific_product_version="$(get_specific_product_version "$1" "$4")"
714 local download_link=null
715 if [[ "$runtime" == "dotnet" ]]; then
716 download_link="$azure_feed/Runtime/$specific_version/dotnet-runtime-$specific_product_version-$osname-$normalized_architecture.tar.gz"
717 elif [[ "$runtime" == "aspnetcore" ]]; then
718 download_link="$azure_feed/aspnetcore/Runtime/$specific_version/aspnetcore-runtime-$specific_product_version-$osname-$normalized_architecture.tar.gz"
719 elif [ -z "$runtime" ]; then
720 download_link="$azure_feed/Sdk/$specific_version/dotnet-sdk-$specific_product_version-$osname-$normalized_architecture.tar.gz"
725 echo "$download_link"
731 # specific_version - $2
732 # download link - $3 (optional)
733 get_specific_product_version() {
734 # If we find a 'productVersion.txt' at the root of any folder, we'll use its contents
735 # to resolve the version of what's in the folder, superseding the specified version.
736 # if 'productVersion.txt' is missing but download link is already available, product version will be taken from download link
739 local azure_feed="$1"
740 local specific_version="${2//[$'\t\r\n']}"
741 local package_download_link=""
742 if [ $# -gt 2 ]; then
743 local package_download_link="$3"
745 local specific_product_version=null
747 # Try to get the version number, using the productVersion.txt file located next to the installer file.
748 local download_links=($(get_specific_product_version_url "$azure_feed" "$specific_version" true "$package_download_link")
749 $(get_specific_product_version_url "$azure_feed" "$specific_version" false "$package_download_link"))
751 for download_link in "${download_links[@]}"
753 say_verbose "Checking for the existence of $download_link"
755 if machine_has "curl"
757 if ! specific_product_version=$(curl -s --fail "${download_link}${feed_credential}" 2>&1); then
760 echo "${specific_product_version//[$'\t\r\n']}"
764 elif machine_has "wget"
766 specific_product_version=$(wget -qO- "${download_link}${feed_credential}" 2>&1)
768 echo "${specific_product_version//[$'\t\r\n']}"
774 # Getting the version number with productVersion.txt has failed. Try parsing the download link for a version number.
775 say_verbose "Failed to get the version using productVersion.txt file. Download link will be parsed instead."
776 specific_product_version="$(get_product_specific_version_from_download_link "$package_download_link" "$specific_version")"
777 echo "${specific_product_version//[$'\t\r\n']}"
783 # specific_version - $2
785 # download link - $4 (optional)
786 get_specific_product_version_url() {
789 local azure_feed="$1"
790 local specific_version="$2"
791 local is_flattened="$3"
792 local package_download_link=""
793 if [ $# -gt 3 ]; then
794 local package_download_link="$4"
797 local pvFileName="productVersion.txt"
798 if [ "$is_flattened" = true ]; then
799 if [ -z "$runtime" ]; then
800 pvFileName="sdk-productVersion.txt"
801 elif [[ "$runtime" == "dotnet" ]]; then
802 pvFileName="runtime-productVersion.txt"
804 pvFileName="$runtime-productVersion.txt"
808 local download_link=null
810 if [ -z "$package_download_link" ]; then
811 if [[ "$runtime" == "dotnet" ]]; then
812 download_link="$azure_feed/Runtime/$specific_version/${pvFileName}"
813 elif [[ "$runtime" == "aspnetcore" ]]; then
814 download_link="$azure_feed/aspnetcore/Runtime/$specific_version/${pvFileName}"
815 elif [ -z "$runtime" ]; then
816 download_link="$azure_feed/Sdk/$specific_version/${pvFileName}"
821 download_link="${package_download_link%/*}/${pvFileName}"
824 say_verbose "Constructed productVersion link: $download_link"
825 echo "$download_link"
831 # specific version - $2
832 get_product_specific_version_from_download_link()
836 local download_link="$1"
837 local specific_version="$2"
838 local specific_product_version=""
840 if [ -z "$download_link" ]; then
841 echo "$specific_version"
846 filename="${download_link##*/}"
848 #product specific version follows the product name
849 #for filename 'dotnet-sdk-3.1.404-linux-x64.tar.gz': the product version is 3.1.404
851 read -ra filename_elems <<< "$filename"
852 count=${#filename_elems[@]}
853 if [[ "$count" -gt 2 ]]; then
854 specific_product_version="${filename_elems[2]}"
856 specific_product_version=$specific_version
859 echo "$specific_product_version"
866 # normalized_architecture - $3
867 # specific_version - $4
868 construct_legacy_download_link() {
871 local azure_feed="$1"
873 local normalized_architecture="$3"
874 local specific_version="${4//[$'\t\r\n']}"
876 local distro_specific_osname
877 distro_specific_osname="$(get_legacy_os_name)" || return 1
879 local legacy_download_link=null
880 if [[ "$runtime" == "dotnet" ]]; then
881 legacy_download_link="$azure_feed/Runtime/$specific_version/dotnet-$distro_specific_osname-$normalized_architecture.$specific_version.tar.gz"
882 elif [ -z "$runtime" ]; then
883 legacy_download_link="$azure_feed/Sdk/$specific_version/dotnet-dev-$distro_specific_osname-$normalized_architecture.$specific_version.tar.gz"
888 echo "$legacy_download_link"
892 get_user_install_path() {
895 if [ ! -z "${DOTNET_INSTALL_DIR:-}" ]; then
896 echo "$DOTNET_INSTALL_DIR"
905 resolve_installation_path() {
909 if [ "$install_dir" = "<auto>" ]; then
910 local user_install_path="$(get_user_install_path)"
911 say_verbose "resolve_installation_path: user_install_path=$user_install_path"
912 echo "$user_install_path"
921 # relative_or_absolute_path - $1
922 get_absolute_path() {
925 local relative_or_absolute_path=$1
926 echo "$(cd "$(dirname "$1")" && pwd -P)/$(basename "$1")"
931 # input_files - stdin
935 copy_files_or_dirs_from_list() {
938 local root_path="$(remove_trailing_slash "$1")"
939 local out_path="$(remove_trailing_slash "$2")"
941 local osname="$(get_current_os_name)"
942 local override_switch=$(
943 if [ "$override" = false ]; then
944 if [ "$osname" = "linux-musl" ]; then
951 cat | uniq | while read -r file_path; do
952 local path="$(remove_beginning_slash "${file_path#$root_path}")"
953 local target="$out_path/$path"
954 if [ "$override" = true ] || (! ([ -d "$target" ] || [ -e "$target" ])); then
955 mkdir -p "$out_path/$(dirname "$path")"
956 if [ -d "$target" ]; then
959 cp -R $override_switch "$root_path/$path" "$target"
966 get_remote_file_size() {
969 if machine_has "curl"; then
970 file_size=$(curl -sI "$zip_uri" | grep -i content-length | awk '{ num = $2 + 0; print num }')
971 elif machine_has "wget"; then
972 file_size=$(wget --spider --server-response -O /dev/null "$zip_uri" 2>&1 | grep -i 'Content-Length:' | awk '{ num = $2 + 0; print num }')
974 say "Neither curl nor wget is available on this system."
978 if [ -n "$file_size" ]; then
979 say "Remote file $zip_uri size is $file_size bytes."
982 say_verbose "Content-Length header was not extracted for $zip_uri."
990 # remote_file_size - $3
991 extract_dotnet_package() {
996 local remote_file_size="$3"
998 local temp_out_path="$(mktemp -d "$temporary_file_template")"
1001 tar -xzf "$zip_path" -C "$temp_out_path" > /dev/null || failed=true
1003 local folders_with_version_regex='^.*/[0-9]+\.[0-9]+[^/]+/'
1004 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
1005 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"
1007 validate_remote_local_file_sizes "$zip_path" "$remote_file_size"
1009 rm -rf "$temp_out_path"
1010 if [ -z ${keep_zip+x} ]; then
1011 rm -f "$zip_path" && say_verbose "Temporary zip file $zip_path was removed"
1014 if [ "$failed" = true ]; then
1015 say_err "Extraction failed"
1023 # disable_feed_credential - $2
1027 local remote_path="$1"
1028 local disable_feed_credential="$2"
1032 if machine_has "curl"; then
1033 get_http_header_curl $remote_path $disable_feed_credential || failed=true
1034 elif machine_has "wget"; then
1035 get_http_header_wget $remote_path $disable_feed_credential || failed=true
1039 if [ "$failed" = true ]; then
1040 say_verbose "Failed to get HTTP header: '$remote_path'."
1048 # disable_feed_credential - $2
1049 get_http_header_curl() {
1051 local remote_path="$1"
1052 local disable_feed_credential="$2"
1054 remote_path_with_credential="$remote_path"
1055 if [ "$disable_feed_credential" = false ]; then
1056 remote_path_with_credential+="$feed_credential"
1059 curl_options="-I -sSL --retry 5 --retry-delay 2 --connect-timeout 15 "
1060 curl $curl_options "$remote_path_with_credential" 2>&1 || return 1
1066 # disable_feed_credential - $2
1067 get_http_header_wget() {
1069 local remote_path="$1"
1070 local disable_feed_credential="$2"
1071 local wget_options="-q -S --spider --tries 5 "
1073 local wget_options_extra=''
1075 # Test for options that aren't supported on all wget implementations.
1076 if [[ $(wget -h 2>&1 | grep -E 'waitretry|connect-timeout') ]]; then
1077 wget_options_extra="--waitretry 2 --connect-timeout 15 "
1079 say "wget extra options are unavailable for this environment"
1082 remote_path_with_credential="$remote_path"
1083 if [ "$disable_feed_credential" = false ]; then
1084 remote_path_with_credential+="$feed_credential"
1087 wget $wget_options $wget_options_extra "$remote_path_with_credential" 2>&1
1094 # [out_path] - $2 - stdout if not provided
1098 local remote_path="$1"
1099 local out_path="${2:-}"
1101 if [[ "$remote_path" != "http"* ]]; then
1102 cp "$remote_path" "$out_path"
1108 while [ $attempts -lt 3 ]; do
1109 attempts=$((attempts+1))
1111 if machine_has "curl"; then
1112 downloadcurl "$remote_path" "$out_path" || failed=true
1113 elif machine_has "wget"; then
1114 downloadwget "$remote_path" "$out_path" || failed=true
1116 say_err "Missing dependency: neither curl nor wget was found."
1120 if [ "$failed" = false ] || [ $attempts -ge 3 ] || { [ ! -z $http_code ] && [ $http_code = "404" ]; }; then
1124 say "Download attempt #$attempts has failed: $http_code $download_error_msg"
1125 say "Attempt #$((attempts+1)) will start in $((attempts*10)) seconds."
1126 sleep $((attempts*10))
1129 if [ "$failed" = true ]; then
1130 say_verbose "Download failed: $remote_path"
1136 # Updates global variables $http_code and $download_error_msg
1140 unset download_error_msg
1141 local remote_path="$1"
1142 local out_path="${2:-}"
1143 # Append feed_credential as late as possible before calling curl to avoid logging feed_credential
1144 # Avoid passing URI with credentials to functions: note, most of them echoing parameters of invocation in verbose output.
1145 local remote_path_with_credential="${remote_path}${feed_credential}"
1146 local curl_options="--retry 20 --retry-delay 2 --connect-timeout 15 -sSL -f --create-dirs "
1147 local curl_exit_code=0;
1148 if [ -z "$out_path" ]; then
1149 curl $curl_options "$remote_path_with_credential" 2>&1
1152 curl $curl_options -o "$out_path" "$remote_path_with_credential" 2>&1
1156 if [ $curl_exit_code -gt 0 ]; then
1157 download_error_msg="Unable to download $remote_path."
1158 # Check for curl timeout codes
1159 if [[ $curl_exit_code == 7 || $curl_exit_code == 28 ]]; then
1160 download_error_msg+=" Failed to reach the server: connection timeout."
1162 local disable_feed_credential=false
1163 local response=$(get_http_header_curl $remote_path $disable_feed_credential)
1164 http_code=$( echo "$response" | awk '/^HTTP/{print $2}' | tail -1 )
1165 if [[ ! -z $http_code && $http_code != 2* ]]; then
1166 download_error_msg+=" Returned HTTP status code: $http_code."
1169 say_verbose "$download_error_msg"
1176 # Updates global variables $http_code and $download_error_msg
1180 unset download_error_msg
1181 local remote_path="$1"
1182 local out_path="${2:-}"
1183 # Append feed_credential as late as possible before calling wget to avoid logging feed_credential
1184 local remote_path_with_credential="${remote_path}${feed_credential}"
1185 local wget_options="--tries 20 "
1187 local wget_options_extra=''
1188 local wget_result=''
1190 # Test for options that aren't supported on all wget implementations.
1191 if [[ $(wget -h 2>&1 | grep -E 'waitretry|connect-timeout') ]]; then
1192 wget_options_extra="--waitretry 2 --connect-timeout 15 "
1194 say "wget extra options are unavailable for this environment"
1197 if [ -z "$out_path" ]; then
1198 wget -q $wget_options $wget_options_extra -O - "$remote_path_with_credential" 2>&1
1201 wget $wget_options $wget_options_extra -O "$out_path" "$remote_path_with_credential" 2>&1
1205 if [[ $wget_result != 0 ]]; then
1206 local disable_feed_credential=false
1207 local response=$(get_http_header_wget $remote_path $disable_feed_credential)
1208 http_code=$( echo "$response" | awk '/^ HTTP/{print $2}' | tail -1 )
1209 download_error_msg="Unable to download $remote_path."
1210 if [[ ! -z $http_code && $http_code != 2* ]]; then
1211 download_error_msg+=" Returned HTTP status code: $http_code."
1212 # wget exit code 4 stands for network-issue
1213 elif [[ $wget_result == 4 ]]; then
1214 download_error_msg+=" Failed to reach the server: connection timeout."
1216 say_verbose "$download_error_msg"
1223 get_download_link_from_aka_ms() {
1226 #quality is not supported for LTS or STS channel
1227 #STS maps to current
1228 if [[ ! -z "$normalized_quality" && ("$normalized_channel" == "LTS" || "$normalized_channel" == "STS") ]]; then
1229 normalized_quality=""
1230 say_warning "Specifying quality for STS or LTS channel is not supported, the quality will be ignored."
1233 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'."
1235 #construct aka.ms link
1236 aka_ms_link="https://aka.ms/dotnet"
1237 if [ "$internal" = true ]; then
1238 aka_ms_link="$aka_ms_link/internal"
1240 aka_ms_link="$aka_ms_link/$normalized_channel"
1241 if [[ ! -z "$normalized_quality" ]]; then
1242 aka_ms_link="$aka_ms_link/$normalized_quality"
1244 aka_ms_link="$aka_ms_link/$normalized_product-$normalized_os-$normalized_architecture.tar.gz"
1245 say_verbose "Constructed aka.ms link: '$aka_ms_link'."
1248 #do not pass credentials as a part of the $aka_ms_link and do not apply credentials in the get_http_header function
1249 #otherwise the redirect link would have credentials as well
1250 #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
1251 disable_feed_credential=true
1252 response="$(get_http_header $aka_ms_link $disable_feed_credential)"
1254 say_verbose "Received response: $response"
1255 # Get results of all the redirects.
1256 http_codes=$( echo "$response" | awk '$1 ~ /^HTTP/ {print $2}' )
1257 # They all need to be 301, otherwise some links are broken (except for the last, which is not a redirect but 200 or 404).
1258 broken_redirects=$( echo "$http_codes" | sed '$d' | grep -v '301' )
1260 # All HTTP codes are 301 (Moved Permanently), the redirect link exists.
1261 if [[ -z "$broken_redirects" ]]; then
1262 aka_ms_download_link=$( echo "$response" | awk '$1 ~ /^Location/{print $2}' | tail -1 | tr -d '\r')
1264 if [[ -z "$aka_ms_download_link" ]]; then
1265 say_verbose "The aka.ms link '$aka_ms_link' is not valid: failed to get redirect location."
1269 say_verbose "The redirect location retrieved: '$aka_ms_download_link'."
1272 say_verbose "The aka.ms link '$aka_ms_link' is not valid: received HTTP code: $(echo "$broken_redirects" | paste -sd "," -)."
1280 "https://dotnetcli.azureedge.net/dotnet"
1281 "https://dotnetbuilds.azureedge.net/public"
1284 if [[ -n "$azure_feed" ]]; then
1285 feeds=("$azure_feed")
1288 if [[ "$no_cdn" == "true" ]]; then
1290 "https://dotnetcli.blob.core.windows.net/dotnet"
1291 "https://dotnetbuilds.blob.core.windows.net/public"
1294 if [[ -n "$uncached_feed" ]]; then
1295 feeds=("$uncached_feed")
1300 # THIS FUNCTION MAY EXIT (if the determined version is already installed).
1301 generate_download_links() {
1304 specific_versions=()
1305 effective_versions=()
1308 # If generate_akams_links returns false, no fallback to old links. Just terminate.
1309 # This function may also 'exit' (if the determined version is already installed).
1310 generate_akams_links || return
1312 # Check other feeds only if we haven't been able to find an aka.ms link.
1313 if [[ "${#download_links[@]}" -lt 1 ]]; then
1314 for feed in ${feeds[@]}
1316 # generate_regular_links may also 'exit' (if the determined version is already installed).
1317 generate_regular_links $feed || return
1321 if [[ "${#download_links[@]}" -eq 0 ]]; then
1322 say_err "Failed to resolve the exact version number."
1326 say_verbose "Generated ${#download_links[@]} links."
1327 for link_index in ${!download_links[@]}
1329 say_verbose "Link $link_index: ${link_types[$link_index]}, ${effective_versions[$link_index]}, ${download_links[$link_index]}"
1333 # THIS FUNCTION MAY EXIT (if the determined version is already installed).
1334 generate_akams_links() {
1335 local valid_aka_ms_link=true;
1337 normalized_version="$(to_lowercase "$version")"
1338 if [[ "$normalized_version" != "latest" ]] && [ -n "$normalized_quality" ]; then
1339 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."
1343 if [[ -n "$json_file" || "$normalized_version" != "latest" ]]; then
1344 # aka.ms links are not needed when exact version is specified via command or json file
1348 get_download_link_from_aka_ms || valid_aka_ms_link=false
1350 if [[ "$valid_aka_ms_link" == true ]]; then
1351 say_verbose "Retrieved primary payload URL from aka.ms link: '$aka_ms_download_link'."
1352 say_verbose "Downloading using legacy url will not be attempted."
1354 download_link=$aka_ms_download_link
1356 #get version from the path
1358 read -ra pathElems <<< "$download_link"
1359 count=${#pathElems[@]}
1360 specific_version="${pathElems[count-2]}"
1362 say_verbose "Version: '$specific_version'."
1364 #Retrieve effective version
1365 effective_version="$(get_specific_product_version "$azure_feed" "$specific_version" "$download_link")"
1367 # Add link info to arrays
1368 download_links+=($download_link)
1369 specific_versions+=($specific_version)
1370 effective_versions+=($effective_version)
1371 link_types+=("aka.ms")
1373 # Check if the SDK version is already installed.
1374 if [[ "$dry_run" != true ]] && is_dotnet_package_installed "$install_root" "$asset_relative_path" "$effective_version"; then
1375 say "$asset_name with version '$effective_version' is already installed."
1382 # if quality is specified - exit with error - there is no fallback approach
1383 if [ ! -z "$normalized_quality" ]; then
1384 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'."
1385 say_err "Refer to: https://aka.ms/dotnet-os-lifecycle for information on .NET Core support."
1388 say_verbose "Falling back to latest.version file approach."
1391 # THIS FUNCTION MAY EXIT (if the determined version is already installed)
1394 generate_regular_links() {
1396 local valid_legacy_download_link=true
1398 specific_version=$(get_specific_version_from_version "$feed" "$channel" "$normalized_architecture" "$version" "$json_file") || specific_version='0'
1400 if [[ "$specific_version" == '0' ]]; then
1401 say_verbose "Failed to resolve the specific version number using feed '$feed'"
1405 effective_version="$(get_specific_product_version "$feed" "$specific_version")"
1406 say_verbose "specific_version=$specific_version"
1408 download_link="$(construct_download_link "$feed" "$channel" "$normalized_architecture" "$specific_version" "$normalized_os")"
1409 say_verbose "Constructed primary named payload URL: $download_link"
1411 # Add link info to arrays
1412 download_links+=($download_link)
1413 specific_versions+=($specific_version)
1414 effective_versions+=($effective_version)
1415 link_types+=("primary")
1417 legacy_download_link="$(construct_legacy_download_link "$feed" "$channel" "$normalized_architecture" "$specific_version")" || valid_legacy_download_link=false
1419 if [ "$valid_legacy_download_link" = true ]; then
1420 say_verbose "Constructed legacy named payload URL: $legacy_download_link"
1422 download_links+=($legacy_download_link)
1423 specific_versions+=($specific_version)
1424 effective_versions+=($effective_version)
1425 link_types+=("legacy")
1427 legacy_download_link=""
1428 say_verbose "Cound not construct a legacy_download_link; omitting..."
1431 # Check if the SDK version is already installed.
1432 if [[ "$dry_run" != true ]] && is_dotnet_package_installed "$install_root" "$asset_relative_path" "$effective_version"; then
1433 say "$asset_name with version '$effective_version' is already installed."
1442 for link_index in "${!download_links[@]}"
1444 say "URL #$link_index - ${link_types[$link_index]}: ${download_links[$link_index]}"
1447 resolved_version=${specific_versions[0]}
1448 repeatable_command="./$script_name --version "\""$resolved_version"\"" --install-dir "\""$install_root"\"" --architecture "\""$normalized_architecture"\"" --os "\""$normalized_os"\"""
1450 if [ ! -z "$normalized_quality" ]; then
1451 repeatable_command+=" --quality "\""$normalized_quality"\"""
1454 if [[ "$runtime" == "dotnet" ]]; then
1455 repeatable_command+=" --runtime "\""dotnet"\"""
1456 elif [[ "$runtime" == "aspnetcore" ]]; then
1457 repeatable_command+=" --runtime "\""aspnetcore"\"""
1460 repeatable_command+="$non_dynamic_parameters"
1462 if [ -n "$feed_credential" ]; then
1463 repeatable_command+=" --feed-credential "\""<feed_credential>"\"""
1466 say "Repeatable invocation: $repeatable_command"
1472 script_name=$(basename "$0")
1473 normalized_architecture="$(get_normalized_architecture_from_architecture "$architecture")"
1474 say_verbose "Normalized architecture: '$normalized_architecture'."
1475 normalized_os="$(get_normalized_os "$user_defined_os")"
1476 say_verbose "Normalized OS: '$normalized_os'."
1477 normalized_quality="$(get_normalized_quality "$quality")"
1478 say_verbose "Normalized quality: '$normalized_quality'."
1479 normalized_channel="$(get_normalized_channel "$channel")"
1480 say_verbose "Normalized channel: '$normalized_channel'."
1481 normalized_product="$(get_normalized_product "$runtime")"
1482 say_verbose "Normalized product: '$normalized_product'."
1483 install_root="$(resolve_installation_path "$install_dir")"
1484 say_verbose "InstallRoot: '$install_root'."
1486 normalized_architecture="$(get_normalized_architecture_for_specific_sdk_version "$version" "$normalized_channel" "$normalized_architecture")"
1488 if [[ "$runtime" == "dotnet" ]]; then
1489 asset_relative_path="shared/Microsoft.NETCore.App"
1490 asset_name=".NET Core Runtime"
1491 elif [[ "$runtime" == "aspnetcore" ]]; then
1492 asset_relative_path="shared/Microsoft.AspNetCore.App"
1493 asset_name="ASP.NET Core Runtime"
1494 elif [ -z "$runtime" ]; then
1495 asset_relative_path="sdk"
1496 asset_name=".NET Core SDK"
1504 local download_failed=false
1505 local download_completed=false
1506 local remote_file_size=0
1508 mkdir -p "$install_root"
1509 zip_path="${zip_path:-$(mktemp "$temporary_file_template")}"
1510 say_verbose "Zip path: $zip_path"
1512 for link_index in "${!download_links[@]}"
1514 download_link="${download_links[$link_index]}"
1515 specific_version="${specific_versions[$link_index]}"
1516 effective_version="${effective_versions[$link_index]}"
1517 link_type="${link_types[$link_index]}"
1519 say "Attempting to download using $link_type link $download_link"
1521 # The download function will set variables $http_code and $download_error_msg in case of failure.
1522 download_failed=false
1523 download "$download_link" "$zip_path" 2>&1 || download_failed=true
1525 if [ "$download_failed" = true ]; then
1528 say "The resource at $link_type link '$download_link' is not available."
1531 say "Failed to download $link_type link '$download_link': $download_error_msg"
1534 rm -f "$zip_path" 2>&1 && say_verbose "Temporary zip file $zip_path was removed"
1536 download_completed=true
1541 if [[ "$download_completed" == false ]]; then
1542 say_err "Could not find \`$asset_name\` with version = $specific_version"
1543 say_err "Refer to: https://aka.ms/dotnet-os-lifecycle for information on .NET Core support"
1547 remote_file_size="$(get_remote_file_size "$download_link")"
1549 say "Extracting zip from $download_link"
1550 extract_dotnet_package "$zip_path" "$install_root" "$remote_file_size" || return 1
1552 # Check if the SDK version is installed; if not, fail the installation.
1553 # if the version contains "RTM" or "servicing"; check if a 'release-type' SDK version is installed.
1554 if [[ $specific_version == *"rtm"* || $specific_version == *"servicing"* ]]; then
1556 read -ra verArr <<< "$specific_version"
1557 release_version="${verArr[0]}"
1559 say_verbose "Checking installation: version = $release_version"
1560 if is_dotnet_package_installed "$install_root" "$asset_relative_path" "$release_version"; then
1561 say "Installed version is $effective_version"
1566 # Check if the standard SDK version is installed.
1567 say_verbose "Checking installation: version = $effective_version"
1568 if is_dotnet_package_installed "$install_root" "$asset_relative_path" "$effective_version"; then
1569 say "Installed version is $effective_version"
1573 # Version verification failed. More likely something is wrong either with the downloaded content or with the verification algorithm.
1574 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."
1575 say_err "\`$asset_name\` with version = $effective_version failed to install with an error."
1581 local_version_file_relative_path="/.version"
1582 bin_folder_relative_path=""
1583 temporary_file_template="${TMPDIR:-/tmp}/dotnet.XXXXXXXXX"
1588 install_dir="<auto>"
1589 architecture="<auto>"
1601 override_non_versioned_files=true
1602 non_dynamic_parameters=""
1609 -c|--channel|-[Cc]hannel)
1613 -v|--version|-[Vv]ersion)
1617 -q|--quality|-[Qq]uality)
1621 --internal|-[Ii]nternal)
1623 non_dynamic_parameters+=" $name"
1625 -i|--install-dir|-[Ii]nstall[Dd]ir)
1629 --arch|--architecture|-[Aa]rch|-[Aa]rchitecture)
1635 user_defined_os="$1"
1637 --shared-runtime|-[Ss]hared[Rr]untime)
1638 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'."
1639 if [ -z "$runtime" ]; then
1643 --runtime|-[Rr]untime)
1646 if [[ "$runtime" != "dotnet" ]] && [[ "$runtime" != "aspnetcore" ]]; then
1647 say_err "Unsupported value for --runtime: '$1'. Valid values are 'dotnet' and 'aspnetcore'."
1648 if [[ "$runtime" == "windowsdesktop" ]]; then
1649 say_err "WindowsDesktop archives are manufactured for Windows platforms only."
1654 --dry-run|-[Dd]ry[Rr]un)
1657 --no-path|-[Nn]o[Pp]ath)
1659 non_dynamic_parameters+=" $name"
1661 --verbose|-[Vv]erbose)
1663 non_dynamic_parameters+=" $name"
1665 --no-cdn|-[Nn]o[Cc]dn)
1667 non_dynamic_parameters+=" $name"
1669 --azure-feed|-[Aa]zure[Ff]eed)
1672 non_dynamic_parameters+=" $name "\""$1"\"""
1674 --uncached-feed|-[Uu]ncached[Ff]eed)
1677 non_dynamic_parameters+=" $name "\""$1"\"""
1679 --feed-credential|-[Ff]eed[Cc]redential)
1681 feed_credential="$1"
1682 #feed_credential should start with "?", for it to be added to the end of the link.
1683 #adding "?" at the beginning of the feed_credential if needed.
1684 [[ -z "$(echo $feed_credential)" ]] || [[ $feed_credential == \?* ]] || feed_credential="?$feed_credential"
1686 --runtime-id|-[Rr]untime[Ii]d)
1689 non_dynamic_parameters+=" $name "\""$1"\"""
1690 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."
1692 --jsonfile|-[Jj][Ss]on[Ff]ile)
1696 --skip-non-versioned-files|-[Ss]kip[Nn]on[Vv]ersioned[Ff]iles)
1697 override_non_versioned_files=false
1698 non_dynamic_parameters+=" $name"
1700 --keep-zip|-[Kk]eep[Zz]ip)
1702 non_dynamic_parameters+=" $name"
1704 --zip-path|-[Zz]ip[Pp]ath)
1708 -?|--?|-h|--help|-[Hh]elp)
1709 script_name="$(basename "$0")"
1710 echo ".NET Tools Installer"
1711 echo "Usage: $script_name [-c|--channel <CHANNEL>] [-v|--version <VERSION>] [-p|--prefix <DESTINATION>]"
1712 echo " $script_name -h|-?|--help"
1714 echo "$script_name is a simple command line interface for obtaining dotnet cli."
1715 echo " Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:"
1716 echo " - The SDK needs to be installed without user interaction and without admin rights."
1717 echo " - The SDK installation doesn't need to persist across multiple CI runs."
1718 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."
1721 echo " -c,--channel <CHANNEL> Download from the channel specified, Defaults to \`$channel\`."
1723 echo " Possible values:"
1724 echo " - STS - the most recent Standard Term Support release"
1725 echo " - LTS - the most recent Long Term Support release"
1726 echo " - 2-part version in a format A.B - represents a specific release"
1727 echo " examples: 2.0; 1.0"
1728 echo " - 3-part version in a format A.B.Cxx - represents a specific SDK release"
1729 echo " examples: 5.0.1xx, 5.0.2xx."
1730 echo " Supported since 5.0 release"
1731 echo " Warning: Value 'Current' is deprecated for the Channel parameter. Use 'STS' instead."
1732 echo " Note: The version parameter overrides the channel parameter when any version other than 'latest' is used."
1733 echo " -v,--version <VERSION> Use specific VERSION, Defaults to \`$version\`."
1735 echo " Possible values:"
1736 echo " - latest - the latest build on specific channel"
1737 echo " - 3-part version in a format A.B.C - represents specific version of build"
1738 echo " examples: 2.0.0-preview2-006120; 1.1.0"
1739 echo " -q,--quality <quality> Download the latest build of specified quality in the channel."
1741 echo " The possible values are: daily, signed, validated, preview, GA."
1742 echo " Works only in combination with channel. Not applicable for STS and LTS channels and will be ignored if those channels are used."
1743 echo " For SDK use channel in A.B.Cxx format. Using quality for SDK together with channel in A.B format is not supported."
1744 echo " Supported since 5.0 release."
1745 echo " Note: The version parameter overrides the channel parameter when any version other than 'latest' is used, and therefore overrides the quality."
1746 echo " --internal,-Internal Download internal builds. Requires providing credentials via --feed-credential parameter."
1747 echo " --feed-credential <FEEDCREDENTIAL> Token to access Azure feed. Used as a query string to append to the Azure feed."
1748 echo " -FeedCredential This parameter typically is not specified."
1749 echo " -i,--install-dir <DIR> Install under specified location (see Install Location below)"
1751 echo " --architecture <ARCHITECTURE> Architecture of dotnet binaries to be installed, Defaults to \`$architecture\`."
1752 echo " --arch,-Architecture,-Arch"
1753 echo " Possible values: x64, arm, arm64, s390x, ppc64le and loongarch64"
1754 echo " --os <system> Specifies operating system to be used when selecting the installer."
1755 echo " Overrides the OS determination approach used by the script. Supported values: osx, linux, linux-musl, freebsd, rhel.6."
1756 echo " In case any other value is provided, the platform will be determined by the script based on machine configuration."
1757 echo " Not supported for legacy links. Use --runtime-id to specify platform for legacy links."
1758 echo " Refer to: https://aka.ms/dotnet-os-lifecycle for more information."
1759 echo " --runtime <RUNTIME> Installs a shared runtime only, without the SDK."
1761 echo " Possible values:"
1762 echo " - dotnet - the Microsoft.NETCore.App shared runtime"
1763 echo " - aspnetcore - the Microsoft.AspNetCore.App shared runtime"
1764 echo " --dry-run,-DryRun Do not perform installation. Display download link."
1765 echo " --no-path, -NoPath Do not set PATH for the current process."
1766 echo " --verbose,-Verbose Display diagnostics information."
1767 echo " --azure-feed,-AzureFeed For internal use only."
1768 echo " Allows using a different storage to download SDK archives from."
1769 echo " This parameter is only used if --no-cdn is false."
1770 echo " --uncached-feed,-UncachedFeed For internal use only."
1771 echo " Allows using a different storage to download SDK archives from."
1772 echo " This parameter is only used if --no-cdn is true."
1773 echo " --skip-non-versioned-files Skips non-versioned files if they already exist, such as the dotnet executable."
1774 echo " -SkipNonVersionedFiles"
1775 echo " --no-cdn,-NoCdn Disable downloading from the Azure CDN, and use the uncached feed directly."
1776 echo " --jsonfile <JSONFILE> Determines the SDK version from a user specified global.json file."
1777 echo " Note: global.json must have a value for 'SDK:Version'"
1778 echo " --keep-zip,-KeepZip If set, downloaded file is kept."
1779 echo " --zip-path, -ZipPath If set, downloaded file is stored at the specified path."
1780 echo " -?,--?,-h,--help,-Help Shows this help message"
1782 echo "Install Location:"
1783 echo " Location is chosen in following order:"
1784 echo " - --install-dir option"
1785 echo " - Environmental variable DOTNET_INSTALL_DIR"
1786 echo " - $HOME/.dotnet"
1790 say_err "Unknown argument \`$name\`"
1798 say_verbose "Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:"
1799 say_verbose "- The SDK needs to be installed without user interaction and without admin rights."
1800 say_verbose "- The SDK installation doesn't need to persist across multiple CI runs."
1801 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"
1803 if [ "$internal" = true ] && [ -z "$(echo $feed_credential)" ]; then
1804 message="Provide credentials via --feed-credential parameter."
1805 if [ "$dry_run" = true ]; then
1806 say_warning "$message"
1815 # generate_regular_links call below will 'exit' if the determined version is already installed.
1816 generate_download_links
1818 if [[ "$dry_run" = true ]]; then
1825 bin_path="$(get_absolute_path "$(combine_paths "$install_root" "$bin_folder_relative_path")")"
1826 if [ "$no_path" = false ]; then
1827 say "Adding to current process PATH: \`$bin_path\`. Note: This change will be visible only when sourcing script."
1828 export PATH="$bin_path":"$PATH"
1830 say "Binaries of dotnet can be found in $bin_path"
1833 say "Note that the script does not resolve dependencies during installation."
1834 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."
1835 say "Installation finished successfully."