#!/bin/sh # Copyright (c) The mukti Contributors # SPDX-License-Identifier: MIT OR Apache-2.0 # Installs a pre-built cargo-nextest binary. # # Generated by mukti-bin generate-install-scripts. # # * This is written to use POSIX sh only, without `local` since illumos /bin/sh # is ksh93 which doesn't support locals. # * The entire script is a single brace group so that truncated scripts # are invalid. { # zsh only word-splits unquoted expansions when emulating sh. The main install # code relies on this. if [ -n "${ZSH_VERSION:-}" ]; then emulate sh fi set -eu PROJECT='cargo-nextest' BIN_NAME='cargo-nextest' DEFAULT_BASE_URL='https://get.nexte.st' DOCS_URL='https://nexte.st/docs/installation/pre-built-binaries/' POST_INSTALL_MIN_MAJOR=0 POST_INSTALL_MIN_MINOR=9 POST_INSTALL_MIN_PATCH=152 MANIFEST_HEADER='mukti-install-manifest' MANIFEST_FORMAT='1' MANIFEST_FILE_NAME='install-manifest.txt' ARCHIVE_FORMAT=tar.gz info() { printf 'info: %s\n' "$*" >&2 } warn() { printf 'warning: %s\n' "$*" >&2 } die() { printf 'error: %s\n' "$1" >&2 shift for die_line in "$@"; do printf ' %s\n' "$die_line" >&2 done exit 1 } usage() { cat < Version to install: "latest", a series like "0.9", or an exact version like "0.9.100" [env: NEXTEST_INSTALL_VERSION] [default: latest] --install-dir Directory to install $BIN_NAME into [env: NEXTEST_INSTALL_DIR] [default: \$CARGO_HOME/bin, or ~/.cargo/bin] --target Target triple to install instead of the detected one [env: NEXTEST_INSTALL_TARGET] --base-url URL to fetch install manifests from [env: NEXTEST_INSTALL_BASE_URL] [default: $DEFAULT_BASE_URL] -h, --help Print help For more information, see $DOCS_URL EOF } set_option() { if [ -z "$2" ]; then die "option $1 requires a non-empty value" "run with --help for usage" fi case "$1" in --version) opt_version=$2 ;; --install-dir) opt_install_dir=$2 ;; --target) opt_target=$2 ;; --base-url) opt_base_url=$2 ;; esac } parse_args() { opt_version=${NEXTEST_INSTALL_VERSION:-latest} opt_install_dir=${NEXTEST_INSTALL_DIR:-} opt_target=${NEXTEST_INSTALL_TARGET:-} opt_base_url=${NEXTEST_INSTALL_BASE_URL:-$DEFAULT_BASE_URL} while [ $# -gt 0 ]; do case "$1" in --version | --install-dir | --target | --base-url) if [ $# -lt 2 ]; then die "option $1 requires a value" "run with --help for usage" fi set_option "$1" "$2" shift 2 ;; --version=* | --install-dir=* | --target=* | --base-url=*) set_option "${1%%=*}" "${1#*=}" shift ;; -h | --help) usage exit 0 ;; *) die "unexpected argument: $1" "run with --help for usage" ;; esac done # The version becomes a URL path segment, so restrict its characters. case "$opt_version" in *[!0-9A-Za-z.+-]*) die "invalid version: $opt_version" \ "expected \"latest\", a series like \"0.9\", or a version like \"0.9.100\"" ;; esac case "$opt_target" in *[!0-9A-Za-z._-]*) die "invalid target: $opt_target" "expected a target triple like x86_64-unknown-linux-gnu" ;; esac case "$opt_base_url" in # The base URL gets a path appended, which a query string or fragment # would swallow. *[?#]*) die "invalid base URL: $opt_base_url" "the base URL must not contain \"?\" or \"#\"" ;; # https only! The manifest carries the checksum, so it must not arrive # unprotected. https://*) ;; *) die "invalid base URL: $opt_base_url" "the base URL must start with \"https://\"" ;; esac # Strip every trailing slash, matching install.ps1's TrimEnd('/'). while :; do case "$opt_base_url" in */) opt_base_url=${opt_base_url%/} ;; *) break ;; esac done } has_cmd() { command -v "$1" >/dev/null 2>&1 } need_cmd() { if ! has_cmd "$1"; then die "required command not found: $1" "install $1 and run this script again" fi } unsupported_platform() { die "no $PROJECT build is known for this platform ($1)" \ "see $DOCS_URL for supported platforms and other ways to install" \ "to force a specific build, pass --target " } # Sets glibc_major and glibc_minor to the host's glibc, or both to empty if # glibc was not detected. detect_glibc() { glibc_major= glibc_minor= dg_out=$(getconf GNU_LIBC_VERSION 2>/dev/null) || return 0 case "$dg_out" in "glibc "*.*) dg_version=${dg_out#glibc } ;; *) return 0 ;; esac # A patch component such as 2.39.1 is dropped rather than rejected. dg_major=${dg_version%%.*} dg_rest=${dg_version#*.} dg_minor=${dg_rest%%.*} is_glibc_version "$dg_major.$dg_minor" || return 0 glibc_major=$dg_major glibc_minor=$dg_minor } # Returns 0 if $1 is a valid glibc version in the form MAJOR.MINOR. is_glibc_version() { case "$1" in *.*.* | .* | *.) return 1 ;; *.*) ;; *) return 1 ;; esac case "${1%%.*}" in '' | *[!0-9]*) return 1 ;; esac case "${1#*.}" in '' | *[!0-9]*) return 1 ;; esac return 0 } # Returns 0 if the detected glibc is at least $1 (a MAJOR.MINOR that was # validated by is_glibc_version). glibc_at_least() { ga_major=${1%%.*} ga_minor=${1#*.} [ "$glibc_major" -gt "$ga_major" ] || { [ "$glibc_major" -eq "$ga_major" ] && [ "$glibc_minor" -ge "$ga_minor" ]; } } # Sets candidates to the targets that can run on this host, best first. detect_candidates() { host_note= glibc_major= glibc_minor= if [ -n "$opt_target" ]; then candidates=$opt_target return 0 fi dc_os=$(uname -s) || die "could not determine the operating system: uname -s failed" dc_arch=$(uname -m) || die "could not determine the architecture: uname -m failed" dc_platform="OS: $dc_os, architecture: $dc_arch" # Map uname's names to Rust's; anything else is passed through and matched # against the manifest as is. case "$dc_arch" in x86_64 | amd64) dc_cpu=x86_64 ;; aarch64 | arm64) dc_cpu=aarch64 ;; riscv64) dc_cpu=riscv64gc ;; i386 | i486 | i586 | i686) dc_cpu=i686 ;; ppc64le) dc_cpu=powerpc64le ;; *) dc_cpu=$dc_arch ;; esac case "$dc_os" in Linux) if [ "$(uname -o 2>/dev/null || true)" = Android ]; then unsupported_platform "$dc_platform, Android" fi detect_glibc # A 64-bit kernel can run a 32-bit userland. In that case, uname # would report the 64-bit architecture, but the 64-bit glibc is not # present. dc_long_bit=$(getconf LONG_BIT 2>/dev/null || true) case "$dc_arch" in *64*) dc_kernel_64=yes ;; *) dc_kernel_64=no ;; esac if [ "$dc_long_bit" = 32 ] && [ "$dc_kernel_64" = yes ]; then candidates=$dc_cpu-unknown-linux-musl host_note="64-bit kernel ($dc_arch) with a 32-bit userland: 64-bit glibc builds cannot run, so only statically linked musl builds were considered" elif [ -n "$glibc_major" ]; then candidates="$dc_cpu-unknown-linux-gnu $dc_cpu-unknown-linux-musl" else candidates=$dc_cpu-unknown-linux-musl host_note="glibc was not detected, so only statically linked musl builds were considered" fi ;; Darwin) candidates="$dc_cpu-apple-darwin universal-apple-darwin" ;; FreeBSD) candidates=$dc_cpu-unknown-freebsd ;; NetBSD) candidates=$dc_cpu-unknown-netbsd ;; OpenBSD) candidates=$dc_cpu-unknown-openbsd ;; DragonFly) candidates=$dc_cpu-unknown-dragonfly ;; SunOS) if [ "$(uname -o 2>/dev/null || true)" != illumos ]; then unsupported_platform "$dc_platform" fi # illumos reports i86pc for both 32-bit and 64-bit x86 -- isainfo # gives the native instruction set. if [ "$dc_arch" = i86pc ]; then case "$(isainfo -n 2>/dev/null || true)" in amd64) dc_cpu=x86_64 ;; *) unsupported_platform "$dc_platform" ;; esac fi candidates=$dc_cpu-unknown-illumos ;; MINGW* | MSYS* | CYGWIN*) # uname -m reflects the shell's own architecture, so ask Windows for # the native one. # # These are undocumented hooks for tests, standing in for what the # registry lookup below returns and for the process's own two # variables. dc_win_arch=${__MUKTI_TEST_MACHINE_ARCHITECTURE:-} if [ -n "$dc_win_arch" ]; then dc_process_arch=${__MUKTI_TEST_PROCESSOR_ARCHITECTURE:-} dc_process_arch_w6432=${__MUKTI_TEST_PROCESSOR_ARCHITEW6432:-} else dc_process_arch=${PROCESSOR_ARCHITECTURE:-} dc_process_arch_w6432=${PROCESSOR_ARCHITEW6432:-} # The shell's architecture can be emulated, e.g. an x64 or x86 Git # Bash on ARM64 Windows. We want to ensure that we download the ARM64 # binary in such cases -- the machine-level value in the registry # provides that. Cygwin and MSYS2 make the registry available as # files under `/proc/registry`. dc_registry='/proc/registry/HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Session Manager/Environment/PROCESSOR_ARCHITECTURE' if [ -r "$dc_registry" ]; then # Strip trailing NUL, CR, and LF bytes (/proc/registry # terminates string values with NULs). dc_win_arch=$(tr -d '\000\r\n' <"$dc_registry" 2>/dev/null || true) fi fi case "$dc_win_arch" in AMD64 | ARM64 | x86) ;; *) # If the machine-level value isn't available, fall back to # PROCESSOR_ARCHITEW6432 or PROCESSOR_ARCHITECTURE. dc_win_arch=${dc_process_arch_w6432:-$dc_process_arch} ;; esac if [ -n "$dc_process_arch" ] && [ "$dc_win_arch" != "$dc_process_arch" ]; then host_note="the shell is an $dc_process_arch process on an $dc_win_arch machine; preferring $dc_win_arch builds" fi # Here, later candidates run under Windows' built-in emulation. case "$dc_win_arch" in AMD64) candidates="x86_64-pc-windows-msvc i686-pc-windows-msvc" ;; ARM64) candidates="aarch64-pc-windows-msvc x86_64-pc-windows-msvc i686-pc-windows-msvc" ;; x86) candidates=i686-pc-windows-msvc ;; *) unsupported_platform "$dc_platform, PROCESSOR_ARCHITECTURE: ${dc_win_arch:-unset}" ;; esac ;; *) unsupported_platform "$dc_platform" ;; esac } # Downloads $1 to $2 with curl, and sets http_result to ok, not-found, or # failed. http_get() { hg_url=$1 hg_dest=$2 http_result=failed # curl retries timeouts and 408, 429, 500, 502, 503, and 504 responses, but # not other 5xx responses or dropped connections. # # We set both proto and proto-redir to https to ensure that we fail on an # https to http redirect. # # __MUKTI_TEST_STALL_SECONDS is an undocumented hook so that tests can time # out a stalled download quickly. hg_stall_seconds=${__MUKTI_TEST_STALL_SECONDS:-60} case "$hg_stall_seconds" in '' | *[!0-9]*) hg_stall_seconds=60 ;; esac set -- --proto '=https' --proto-redir '=https' -fsSL --retry 3 --connect-timeout 30 --speed-limit 1 --speed-time "$hg_stall_seconds" -o "$hg_dest" -w '%{http_code}' if hg_code=$(curl "$@" "$hg_url" 2>"$hg_dest.log"); then http_result=ok elif [ "$hg_code" = 404 ]; then http_result=not-found else cat "$hg_dest.log" >&2 || : fi } is_sha256() { if [ "${#1}" -ne 64 ]; then return 1 fi case "$1" in *[!0-9a-f]*) return 1 ;; esac return 0 } lowercase_hex() { printf '%s' "$1" | tr 'A-F' 'a-f' } sha256_failed() { die "could not compute a SHA-256 checksum for $2: $1 failed" "nothing was installed" } # Sets sha256_result to the lowercase SHA-256 of the file. sha256_file() { if has_cmd sha256sum; then sf_out=$(sha256sum <"$1") || sha256_failed sha256sum "$1" sf_hash=${sf_out%% *} elif has_cmd shasum; then sf_out=$(shasum -a 256 <"$1") || sha256_failed shasum "$1" sf_hash=${sf_out%% *} elif has_cmd sha256; then sf_hash=$(sha256 -q <"$1") || sha256_failed sha256 "$1" elif has_cmd digest; then sf_hash=$(digest -a sha256 <"$1") || sha256_failed digest "$1" elif has_cmd openssl; then sf_out=$(openssl dgst -sha256 <"$1") || sha256_failed openssl "$1" sf_hash=${sf_out##* } else die "no SHA-256 tool was found, so the download cannot be verified" \ "tried: sha256sum, shasum, sha256, digest, openssl" \ "install one of them and run this script again" fi sha256_result=$(lowercase_hex "$sf_hash") || sha256_failed tr "$1" if ! is_sha256 "$sha256_result"; then die "could not compute a SHA-256 checksum for $1" "unexpected output from the checksum tool: $sf_hash" fi } # Parses the manifest at $1 (fetched from $2). Sets m_version and its parts, # and target, m_sha256, and m_url for the best candidate defined by the # manifest. read_manifest() { m_version= m_sha256= m_url= target= rm_header= rm_format= rm_project= rm_status= rm_targets= rm_best_rank=0 rm_bad_line= rm_glibc_note= # Tolerate CRLF line endings and a missing final newline. tr -d '\r' <"$1" >"$1.lf" || die "failed to read the install manifest downloaded from $2" { read -r rm_header rm_format _ || : while read -r rm_key rm_a rm_b rm_c rm_d rm_e rm_extra || [ -n "$rm_key" ]; do case "$rm_key" in project) rm_project=$rm_a ;; version) m_version=$rm_a ;; status) rm_status=$rm_a ;; archive) # linux-gnu lines carry one extra field, # min-glibc=MAJOR.MINOR. Other lines carry none. rm_floor= rm_line_ok=yes if [ -z "$rm_d" ] || [ -n "$rm_extra" ]; then rm_line_ok=no else case "$rm_a" in *-linux-gnu*) case "$rm_e" in min-glibc=*) rm_floor=${rm_e#min-glibc=} is_glibc_version "$rm_floor" || rm_line_ok=no ;; *) rm_line_ok=no ;; esac ;; *) [ -z "$rm_e" ] || rm_line_ok=no ;; esac fi if [ "$rm_line_ok" = no ]; then # We must fail on malformed lines. if [ -z "$rm_bad_line" ]; then rm_bad_line="archive${rm_a:+ $rm_a}${rm_b:+ $rm_b}${rm_c:+ $rm_c}${rm_d:+ $rm_d}${rm_e:+ $rm_e}${rm_extra:+ $rm_extra}" fi elif [ "$rm_b" = "$ARCHIVE_FORMAT" ]; then rm_targets="$rm_targets $rm_a" rm_rank=0 for rm_candidate in $candidates; do rm_rank=$((rm_rank + 1)) if [ "$rm_candidate" != "$rm_a" ]; then continue fi # glibc_major is empty when --target is given, so an # explicit target bypasses the glibc floor check. if [ -n "$rm_floor" ] && [ -n "$glibc_major" ] && ! glibc_at_least "$rm_floor"; then rm_glibc_note="glibc $glibc_major.$glibc_minor is older than the $rm_floor that the $rm_a build needs" elif [ "$rm_best_rank" -eq 0 ] || [ "$rm_rank" -lt "$rm_best_rank" ]; then rm_best_rank=$rm_rank target=$rm_a m_sha256=$rm_c m_url=$rm_d fi break done fi ;; esac done } <"$1.lf" if [ "$rm_header" != "$MANIFEST_HEADER" ]; then die "the response from $2 is not an install manifest" \ "check the base URL, and that nothing on the network is rewriting the response" fi if [ "$rm_format" != "$MANIFEST_FORMAT" ]; then die "the install manifest at $2 uses unsupported format $rm_format (expected format $MANIFEST_FORMAT)" \ "download the current install script from $DEFAULT_BASE_URL/install.sh and try again" fi if [ "$rm_project" != "$PROJECT" ]; then die "the install manifest at $2 is for project \"$rm_project\", not \"$PROJECT\"" \ "check the base URL" fi if [ -n "$rm_bad_line" ]; then die "the install manifest at $2 has a malformed archive line: \"$rm_bad_line\"" \ "the manifest may be incomplete; try again, and if this keeps happening, see $DOCS_URL" fi # The version must be MAJOR.MINOR.PATCH with an optional prerelease and # build suffix. rm_core=${m_version%%[-+]*} m_major=${rm_core%%.*} rm_rest=${rm_core#*.} m_minor=${rm_rest%%.*} m_patch=${rm_rest#*.} case "$rm_rest" in *.*) ;; *) m_patch= ;; esac rm_version_ok=yes case "$m_version" in '' | *[!0-9A-Za-z.+-]*) rm_version_ok=no ;; esac for rm_part in "$m_major" "$m_minor" "$m_patch"; do case "$rm_part" in '' | *[!0-9]*) rm_version_ok=no ;; esac done if [ "$rm_version_ok" = no ]; then die "the install manifest at $2 has a missing or malformed version: \"$m_version\"" \ "expected MAJOR.MINOR.PATCH, with an optional prerelease or build suffix" \ "this is a problem with the release; see $DOCS_URL for other ways to install" fi case "${m_version%%+*}" in *-*) m_prerelease=yes ;; *) m_prerelease=no ;; esac if ! manifest_matches_request "$opt_version"; then die "the install manifest at $2 is for $PROJECT $m_version, which version \"$opt_version\" cannot resolve to" \ "the server may be serving a stale or misplaced manifest; try again, and if this keeps happening, see $DOCS_URL" fi case "$rm_status" in active) ;; yanked) warn "$PROJECT $m_version has been yanked; installing it anyway since it was requested" ;; *) die "the install manifest at $2 has an unknown release status: \"$rm_status\"" \ "download the current install script from $DEFAULT_BASE_URL/install.sh and try again" ;; esac if [ -z "$target" ]; then die "$PROJECT $m_version has no $ARCHIVE_FORMAT archive for this platform" \ "looked for: $candidates" \ ${host_note:+"$host_note"} \ ${rm_glibc_note:+"$rm_glibc_note"} \ "targets available for this version:${rm_targets:- (none)}" \ "see $DOCS_URL for supported platforms and other ways to install" fi if [ -n "$rm_glibc_note" ]; then host_note="${host_note:+$host_note; }$rm_glibc_note, so the $target build was chosen instead" fi if ! is_sha256 "$m_sha256"; then die "the install manifest at $2 has a malformed SHA-256 checksum for target $target" fi case "$m_url" in https://*) ;; *) die "the install manifest at $2 has an archive URL for target $target that is not an https URL: \"$m_url\"" ;; esac } # Returns 0 if the manifest's version is one that the requested $1 can # potentially resolve to. # # "latest" and version series like "0.9" are allowed to match any stable # version. manifest_matches_request() { if [ "$1" = "$m_version" ]; then return 0 fi case "$1" in latest) [ "$m_prerelease" = no ] ;; '' | *[!0-9.]* | .* | *. | *..* | *.*.*) return 1 ;; 0.*) [ "$m_prerelease" = no ] && [ "$m_major" = 0 ] && [ "$m_minor" = "${1#0.}" ] ;; *.*) return 1 ;; *) [ "$m_prerelease" = no ] && [ "$m_major" = "$1" ] ;; esac } # Sets post_install_supported to yes if the version is at or above the # post-install minimum. (A prerelease of the minimum counts as older.) check_post_install_min() { post_install_supported=no if [ "$m_major" -gt "$POST_INSTALL_MIN_MAJOR" ]; then post_install_supported=yes elif [ "$m_major" -eq "$POST_INSTALL_MIN_MAJOR" ]; then if [ "$m_minor" -gt "$POST_INSTALL_MIN_MINOR" ]; then post_install_supported=yes elif [ "$m_minor" -eq "$POST_INSTALL_MIN_MINOR" ]; then if [ "$m_patch" -gt "$POST_INSTALL_MIN_PATCH" ]; then post_install_supported=yes elif [ "$m_patch" -eq "$POST_INSTALL_MIN_PATCH" ] && [ "$m_prerelease" = no ]; then post_install_supported=yes fi fi fi } cleanup() { if [ -n "$tmp_dir" ]; then rm -rf "$tmp_dir" || warn "could not remove the temporary directory $tmp_dir; it is safe to delete" fi } main() { parse_args "$@" # From here on, the positional parameters are the arguments for the # post-install command -- empty means there is no post-install step. set -- 'nextest' 'self' 'setup' '--source' 'install-script' need_cmd uname need_cmd curl need_cmd mktemp need_cmd tr need_cmd tar need_cmd gunzip detect_candidates if [ -n "$opt_install_dir" ]; then install_dir=$opt_install_dir elif [ -n "${CARGO_HOME:-}" ]; then install_dir=$CARGO_HOME/bin elif [ -n "${HOME:-}" ]; then install_dir=$HOME/.cargo/bin else die "could not determine where to install: neither CARGO_HOME nor HOME is set" \ "pass --install-dir " fi mkdir -p -- "$install_dir" || die "could not create the install directory $install_dir" "pass --install-dir to install somewhere else" # With CDPATH set, cd to a relative path can go somewhere else and print the # result. install_dir_abs=$(CDPATH='' cd -- "$install_dir" && pwd) || die "could not enter the install directory $install_dir" "pass --install-dir to install somewhere else" install_dir=$install_dir_abs # The temp dir lives in the install dir so that the final mv is a # same-filesystem rename. tmp_dir= trap cleanup EXIT # dash does not run the EXIT trap when a signal kills it, so turn each # supported signal into an exit. trap 'exit 129' HUP trap 'exit 130' INT trap 'exit 143' TERM tmp_dir=$(mktemp -d "$install_dir/.$PROJECT-install.XXXXXX") || die "could not create a temporary directory in $install_dir" "check that the directory is writable" manifest_url=$opt_base_url/$opt_version/$MANIFEST_FILE_NAME http_get "$manifest_url" "$tmp_dir/manifest" case "$http_result" in ok) ;; not-found) die "$PROJECT version \"$opt_version\" was not found (no manifest at $manifest_url)" \ "expected \"latest\", a series like \"0.9\", or a released version like \"0.9.100\"" ;; *) die "failed to download the install manifest from $manifest_url" "check the network connection, then try again" ;; esac read_manifest "$tmp_dir/manifest" "$manifest_url" check_post_install_min case "$target" in *-windows-*) bin_name=$BIN_NAME.exe ;; *) bin_name=$BIN_NAME ;; esac if [ -n "$host_note" ]; then info "$host_note" fi info "installing $PROJECT $m_version ($target) to $install_dir" info "downloading $m_url" http_get "$m_url" "$tmp_dir/archive.$ARCHIVE_FORMAT" case "$http_result" in ok) ;; not-found) die "the archive for $target was not found at $m_url" \ "the release may have been removed; see $DOCS_URL for other ways to install" ;; *) die "failed to download $m_url" "check the network connection, then try again" ;; esac sha256_file "$tmp_dir/archive.$ARCHIVE_FORMAT" if [ "$sha256_result" != "$m_sha256" ]; then die "checksum mismatch for $m_url" \ "expected SHA-256: $m_sha256" \ " actual SHA-256: $sha256_result" \ "the download may be incomplete or corrupted; nothing was installed" \ "try again, and if this keeps happening, see $DOCS_URL" fi # POSIX sh cannot see a gunzip failure in a pipeline. gunzip -c "$tmp_dir/archive.$ARCHIVE_FORMAT" >"$tmp_dir/archive.tar" || die "failed to decompress the downloaded archive" "nothing was installed" mkdir "$tmp_dir/extract" || die "could not create a directory in $tmp_dir to extract the archive into" # The o flag keeps tar from restoring the archive's owner when run as root. (CDPATH='' cd -- "$tmp_dir/extract" && tar xof ../archive.tar "$bin_name") || die "failed to extract $bin_name from the downloaded archive" "nothing was installed" if [ ! -f "$tmp_dir/extract/$bin_name" ]; then die "the downloaded archive did not contain $bin_name; nothing was installed" \ "this is a problem with the release; see $DOCS_URL for other ways to install" fi chmod +x "$tmp_dir/extract/$bin_name" || die "could not make $bin_name executable" "nothing was installed" dest=$install_dir/$bin_name if [ -d "$dest" ]; then die "cannot install to $dest: it is a directory" fi mv -f "$tmp_dir/extract/$bin_name" "$dest" || die "failed to move the new binary to $dest" \ "if $BIN_NAME is currently running, stop it and run this script again" if [ $# -gt 0 ] && [ "$post_install_supported" = yes ]; then info "running post-install step: $BIN_NAME $*" # Turn -e off rather than testing the command in an if. # # Experienced shell script authors might know that within the context of # an `if`, a `set -e` is suspended (so that exit codes can be tested). # But in bash 3.2 (the sh on macOS), the -e is only suspended in the # parent shell. The child forked to run $dest retains the -e. If the # exec then fails (for example, an x86_64 build on Apple Silicon without # Rosetta), the child exits with 1 rather than 126, so the check below # would misreport it as a failed post-install step. # # As an alternative, we use `set +e` explicitly to turn it off for the # child too. set +e "$dest" "$@" post_install_status=$? set -e case "$post_install_status" in 0) ;; # 126 and 127 mean that the shell couldn't execute the file at all. 126 | 127) die "$PROJECT $m_version ($target) was installed to $dest, but it cannot run on this system (exit code $post_install_status)" \ "the $target build may not match this system; to install a different build, pass --target " \ "see $DOCS_URL for supported platforms and other ways to install" ;; *) die "$PROJECT $m_version was installed to $dest, but the post-install step ($BIN_NAME $*) failed with exit code $post_install_status" \ "the binary is in place; run \"$dest $*\" to retry the step" ;; esac fi info "installed $PROJECT $m_version to $dest" case ":${PATH:-}:" in *":$install_dir:"*) ;; *) info "$install_dir is not in your PATH; add it to run $BIN_NAME" ;; esac } main "$@" }