From ef14df5ba26861869c260d122286c6ab03aa8869 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Mon, 16 Dec 2024 16:02:48 -0800 Subject: [PATCH] feat: Add install script for website --- aider/website/install.sh | 1825 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 1825 insertions(+) create mode 100644 aider/website/install.sh diff --git a/aider/website/install.sh b/aider/website/install.sh new file mode 100644 index 00000000000..98f10952af7 --- /dev/null +++ b/aider/website/install.sh @@ -0,0 +1,1825 @@ +#!/bin/sh +# shellcheck shell=dash +# +# Licensed under the MIT license +# , at your +# option. This file may not be copied, modified, or distributed +# except according to those terms. + +if [ "$KSH_VERSION" = 'Version JM 93t+ 2010-03-05' ]; then + # The version of ksh93 that ships with many illumos systems does not + # support the "local" extension. Print a message rather than fail in + # subtle ways later on: + echo 'this installer does not work with this ksh93 version; please try bash!' >&2 + exit 1 +fi + +set -u + +APP_NAME="uv" +APP_VERSION="0.5.9" +# Look for GitHub Enterprise-style base URL first +if [ -n "${UV_INSTALLER_GHE_BASE_URL:-}" ]; then + INSTALLER_BASE_URL="$UV_INSTALLER_GHE_BASE_URL" +else + INSTALLER_BASE_URL="${UV_INSTALLER_GITHUB_BASE_URL:-https://github.com}" +fi +if [ -n "${INSTALLER_DOWNLOAD_URL:-}" ]; then + ARTIFACT_DOWNLOAD_URL="$INSTALLER_DOWNLOAD_URL" +else + ARTIFACT_DOWNLOAD_URL="${INSTALLER_BASE_URL}/astral-sh/uv/releases/download/0.5.9" +fi +PRINT_VERBOSE=${INSTALLER_PRINT_VERBOSE:-0} +PRINT_QUIET=${INSTALLER_PRINT_QUIET:-0} +if [ -n "${UV_NO_MODIFY_PATH:-}" ]; then + NO_MODIFY_PATH="$UV_NO_MODIFY_PATH" +else + NO_MODIFY_PATH=${INSTALLER_NO_MODIFY_PATH:-0} +fi +if [ "${UV_DISABLE_UPDATE:-0}" = "1" ]; then + INSTALL_UPDATER=0 +else + INSTALL_UPDATER=1 +fi +UNMANAGED_INSTALL="${UV_UNMANAGED_INSTALL:-}" +if [ -n "${UNMANAGED_INSTALL}" ]; then + NO_MODIFY_PATH=1 + INSTALL_UPDATER=0 +fi + +read -r RECEIPT <&2 + say_verbose " from $_url" 1>&2 + say_verbose " to $_file" 1>&2 + + ensure mkdir -p "$_dir" + + if ! downloader "$_url" "$_file"; then + say "failed to download $_url" + say "this may be a standard network error, but it may also indicate" + say "that $APP_NAME's release process is not working. When in doubt" + say "please feel free to open an issue!" + exit 1 + fi + + if [ -n "${_checksum_style:-}" ]; then + verify_checksum "$_file" "$_checksum_style" "$_checksum_value" + else + say "no checksums to verify" + fi + + # ...and then the updater, if it exists + if [ -n "$_updater_name" ] && [ "$INSTALL_UPDATER" = "1" ]; then + local _updater_url="$ARTIFACT_DOWNLOAD_URL/$_updater_name" + # This renames the artifact while doing the download, removing the + # target triple and leaving just the appname-update format + local _updater_file="$_dir/$APP_NAME-update" + + if ! downloader "$_updater_url" "$_updater_file"; then + say "failed to download $_updater_url" + say "this may be a standard network error, but it may also indicate" + say "that $APP_NAME's release process is not working. When in doubt" + say "please feel free to open an issue!" + exit 1 + fi + + # Add the updater to the list of binaries to install + _bins="$_bins $APP_NAME-update" + fi + + # unpack the archive + case "$_zip_ext" in + ".zip") + ensure unzip -q "$_file" -d "$_dir" + ;; + + ".tar."*) + ensure tar xf "$_file" --strip-components 1 -C "$_dir" + ;; + *) + err "unknown archive format: $_zip_ext" + ;; + esac + + install "$_dir" "$_bins" "$_libs" "$_staticlibs" "$_arch" "$@" + local _retval=$? + if [ "$_retval" != 0 ]; then + return "$_retval" + fi + + ignore rm -rf "$_dir" + + # Install the install receipt + if [ "$INSTALL_UPDATER" = "1" ]; then + if ! mkdir -p "$RECEIPT_HOME"; then + err "unable to create receipt directory at $RECEIPT_HOME" + else + echo "$RECEIPT" > "$RECEIPT_HOME/$APP_NAME-receipt.json" + # shellcheck disable=SC2320 + local _retval=$? + fi + else + local _retval=0 + fi + + return "$_retval" +} + +# Replaces $HOME with the variable name for display to the user, +# only if $HOME is defined. +replace_home() { + local _str="$1" + + if [ -n "${HOME:-}" ]; then + echo "$_str" | sed "s,$HOME,\$HOME," + else + echo "$_str" + fi +} + +json_binary_aliases() { + local _arch="$1" + + case "$_arch" in + "aarch64-apple-darwin") + echo '{}' + ;; + "aarch64-unknown-linux-gnu") + echo '{}' + ;; + "aarch64-unknown-linux-musl-dynamic") + echo '{}' + ;; + "aarch64-unknown-linux-musl-static") + echo '{}' + ;; + "arm-unknown-linux-gnueabihf") + echo '{}' + ;; + "arm-unknown-linux-musl-dynamiceabihf") + echo '{}' + ;; + "arm-unknown-linux-musl-staticeabihf") + echo '{}' + ;; + "armv7-unknown-linux-gnueabihf") + echo '{}' + ;; + "armv7-unknown-linux-musl-dynamiceabihf") + echo '{}' + ;; + "armv7-unknown-linux-musl-staticeabihf") + echo '{}' + ;; + "i686-pc-windows-gnu") + echo '{}' + ;; + "i686-unknown-linux-gnu") + echo '{}' + ;; + "i686-unknown-linux-musl-dynamic") + echo '{}' + ;; + "i686-unknown-linux-musl-static") + echo '{}' + ;; + "powerpc64-unknown-linux-gnu") + echo '{}' + ;; + "powerpc64le-unknown-linux-gnu") + echo '{}' + ;; + "s390x-unknown-linux-gnu") + echo '{}' + ;; + "x86_64-apple-darwin") + echo '{}' + ;; + "x86_64-pc-windows-gnu") + echo '{}' + ;; + "x86_64-unknown-linux-gnu") + echo '{}' + ;; + "x86_64-unknown-linux-musl-dynamic") + echo '{}' + ;; + "x86_64-unknown-linux-musl-static") + echo '{}' + ;; + *) + echo '{}' + ;; + esac +} + +aliases_for_binary() { + local _bin="$1" + local _arch="$2" + + case "$_arch" in + "aarch64-apple-darwin") + case "$_bin" in + *) + echo "" + ;; + esac + ;; + "aarch64-unknown-linux-gnu") + case "$_bin" in + *) + echo "" + ;; + esac + ;; + "aarch64-unknown-linux-musl-dynamic") + case "$_bin" in + *) + echo "" + ;; + esac + ;; + "aarch64-unknown-linux-musl-static") + case "$_bin" in + *) + echo "" + ;; + esac + ;; + "arm-unknown-linux-gnueabihf") + case "$_bin" in + *) + echo "" + ;; + esac + ;; + "arm-unknown-linux-musl-dynamiceabihf") + case "$_bin" in + *) + echo "" + ;; + esac + ;; + "arm-unknown-linux-musl-staticeabihf") + case "$_bin" in + *) + echo "" + ;; + esac + ;; + "armv7-unknown-linux-gnueabihf") + case "$_bin" in + *) + echo "" + ;; + esac + ;; + "armv7-unknown-linux-musl-dynamiceabihf") + case "$_bin" in + *) + echo "" + ;; + esac + ;; + "armv7-unknown-linux-musl-staticeabihf") + case "$_bin" in + *) + echo "" + ;; + esac + ;; + "i686-pc-windows-gnu") + case "$_bin" in + *) + echo "" + ;; + esac + ;; + "i686-unknown-linux-gnu") + case "$_bin" in + *) + echo "" + ;; + esac + ;; + "i686-unknown-linux-musl-dynamic") + case "$_bin" in + *) + echo "" + ;; + esac + ;; + "i686-unknown-linux-musl-static") + case "$_bin" in + *) + echo "" + ;; + esac + ;; + "powerpc64-unknown-linux-gnu") + case "$_bin" in + *) + echo "" + ;; + esac + ;; + "powerpc64le-unknown-linux-gnu") + case "$_bin" in + *) + echo "" + ;; + esac + ;; + "s390x-unknown-linux-gnu") + case "$_bin" in + *) + echo "" + ;; + esac + ;; + "x86_64-apple-darwin") + case "$_bin" in + *) + echo "" + ;; + esac + ;; + "x86_64-pc-windows-gnu") + case "$_bin" in + *) + echo "" + ;; + esac + ;; + "x86_64-unknown-linux-gnu") + case "$_bin" in + *) + echo "" + ;; + esac + ;; + "x86_64-unknown-linux-musl-dynamic") + case "$_bin" in + *) + echo "" + ;; + esac + ;; + "x86_64-unknown-linux-musl-static") + case "$_bin" in + *) + echo "" + ;; + esac + ;; + *) + echo "" + ;; + esac +} + +select_archive_for_arch() { + local _true_arch="$1" + local _archive + + # try each archive, checking runtime conditions like libc versions + # accepting the first one that matches, as it's the best match + case "$_true_arch" in + "aarch64-apple-darwin") + _archive="uv-aarch64-apple-darwin.tar.gz" + if [ -n "$_archive" ]; then + echo "$_archive" + return 0 + fi + _archive="uv-x86_64-apple-darwin.tar.gz" + if [ -n "$_archive" ]; then + echo "$_archive" + return 0 + fi + ;; + "aarch64-pc-windows-msvc") + _archive="uv-x86_64-pc-windows-msvc.zip" + if [ -n "$_archive" ]; then + echo "$_archive" + return 0 + fi + _archive="uv-i686-pc-windows-msvc.zip" + if [ -n "$_archive" ]; then + echo "$_archive" + return 0 + fi + ;; + "aarch64-unknown-linux-gnu") + _archive="uv-aarch64-unknown-linux-gnu.tar.gz" + if ! check_glibc "2" "31"; then + _archive="" + fi + if [ -n "$_archive" ]; then + echo "$_archive" + return 0 + fi + _archive="uv-aarch64-unknown-linux-musl.tar.gz" + if [ -n "$_archive" ]; then + echo "$_archive" + return 0 + fi + ;; + "aarch64-unknown-linux-musl-dynamic") + _archive="uv-aarch64-unknown-linux-musl.tar.gz" + if [ -n "$_archive" ]; then + echo "$_archive" + return 0 + fi + ;; + "aarch64-unknown-linux-musl-static") + _archive="uv-aarch64-unknown-linux-musl.tar.gz" + if [ -n "$_archive" ]; then + echo "$_archive" + return 0 + fi + ;; + "arm-unknown-linux-gnueabihf") + _archive="uv-arm-unknown-linux-musleabihf.tar.gz" + if [ -n "$_archive" ]; then + echo "$_archive" + return 0 + fi + ;; + "arm-unknown-linux-musl-dynamiceabihf") + _archive="uv-arm-unknown-linux-musleabihf.tar.gz" + if [ -n "$_archive" ]; then + echo "$_archive" + return 0 + fi + ;; + "arm-unknown-linux-musl-staticeabihf") + _archive="uv-arm-unknown-linux-musleabihf.tar.gz" + if [ -n "$_archive" ]; then + echo "$_archive" + return 0 + fi + ;; + "armv7-unknown-linux-gnueabihf") + _archive="uv-armv7-unknown-linux-gnueabihf.tar.gz" + if ! check_glibc "2" "31"; then + _archive="" + fi + if [ -n "$_archive" ]; then + echo "$_archive" + return 0 + fi + _archive="uv-armv7-unknown-linux-musleabihf.tar.gz" + if [ -n "$_archive" ]; then + echo "$_archive" + return 0 + fi + ;; + "armv7-unknown-linux-musl-dynamiceabihf") + _archive="uv-armv7-unknown-linux-musleabihf.tar.gz" + if [ -n "$_archive" ]; then + echo "$_archive" + return 0 + fi + ;; + "armv7-unknown-linux-musl-staticeabihf") + _archive="uv-armv7-unknown-linux-musleabihf.tar.gz" + if [ -n "$_archive" ]; then + echo "$_archive" + return 0 + fi + ;; + "i686-pc-windows-gnu") + _archive="uv-i686-pc-windows-msvc.zip" + if [ -n "$_archive" ]; then + echo "$_archive" + return 0 + fi + ;; + "i686-pc-windows-msvc") + _archive="uv-i686-pc-windows-msvc.zip" + if [ -n "$_archive" ]; then + echo "$_archive" + return 0 + fi + ;; + "i686-unknown-linux-gnu") + _archive="uv-i686-unknown-linux-gnu.tar.gz" + if ! check_glibc "2" "31"; then + _archive="" + fi + if [ -n "$_archive" ]; then + echo "$_archive" + return 0 + fi + _archive="uv-i686-unknown-linux-musl.tar.gz" + if [ -n "$_archive" ]; then + echo "$_archive" + return 0 + fi + ;; + "i686-unknown-linux-musl-dynamic") + _archive="uv-i686-unknown-linux-musl.tar.gz" + if [ -n "$_archive" ]; then + echo "$_archive" + return 0 + fi + ;; + "i686-unknown-linux-musl-static") + _archive="uv-i686-unknown-linux-musl.tar.gz" + if [ -n "$_archive" ]; then + echo "$_archive" + return 0 + fi + ;; + "powerpc64-unknown-linux-gnu") + _archive="uv-powerpc64-unknown-linux-gnu.tar.gz" + if ! check_glibc "2" "31"; then + _archive="" + fi + if [ -n "$_archive" ]; then + echo "$_archive" + return 0 + fi + ;; + "powerpc64le-unknown-linux-gnu") + _archive="uv-powerpc64le-unknown-linux-gnu.tar.gz" + if ! check_glibc "2" "31"; then + _archive="" + fi + if [ -n "$_archive" ]; then + echo "$_archive" + return 0 + fi + ;; + "s390x-unknown-linux-gnu") + _archive="uv-s390x-unknown-linux-gnu.tar.gz" + if ! check_glibc "2" "31"; then + _archive="" + fi + if [ -n "$_archive" ]; then + echo "$_archive" + return 0 + fi + ;; + "x86_64-apple-darwin") + _archive="uv-x86_64-apple-darwin.tar.gz" + if [ -n "$_archive" ]; then + echo "$_archive" + return 0 + fi + ;; + "x86_64-pc-windows-gnu") + _archive="uv-x86_64-pc-windows-msvc.zip" + if [ -n "$_archive" ]; then + echo "$_archive" + return 0 + fi + ;; + "x86_64-pc-windows-msvc") + _archive="uv-x86_64-pc-windows-msvc.zip" + if [ -n "$_archive" ]; then + echo "$_archive" + return 0 + fi + _archive="uv-i686-pc-windows-msvc.zip" + if [ -n "$_archive" ]; then + echo "$_archive" + return 0 + fi + ;; + "x86_64-unknown-linux-gnu") + _archive="uv-x86_64-unknown-linux-gnu.tar.gz" + if ! check_glibc "2" "31"; then + _archive="" + fi + if [ -n "$_archive" ]; then + echo "$_archive" + return 0 + fi + _archive="uv-x86_64-unknown-linux-musl.tar.gz" + if [ -n "$_archive" ]; then + echo "$_archive" + return 0 + fi + ;; + "x86_64-unknown-linux-musl-dynamic") + _archive="uv-x86_64-unknown-linux-musl.tar.gz" + if [ -n "$_archive" ]; then + echo "$_archive" + return 0 + fi + ;; + "x86_64-unknown-linux-musl-static") + _archive="uv-x86_64-unknown-linux-musl.tar.gz" + if [ -n "$_archive" ]; then + echo "$_archive" + return 0 + fi + ;; + *) + err "there isn't a download for your platform $_true_arch" + ;; + esac + err "no compatible downloads were found for your platform $_true_arch" +} + +check_glibc() { + local _min_glibc_major="$1" + local _min_glibc_series="$2" + + # Parsing version out from line 1 like: + # ldd (Ubuntu GLIBC 2.35-0ubuntu3.1) 2.35 + _local_glibc="$(ldd --version | awk -F' ' '{ if (FNR<=1) print $NF }')" + + if [ "$(echo "${_local_glibc}" | awk -F. '{ print $1 }')" = "$_min_glibc_major" ] && [ "$(echo "${_local_glibc}" | awk -F. '{ print $2 }')" -ge "$_min_glibc_series" ]; then + return 0 + else + say "System glibc version (\`${_local_glibc}') is too old; checking alternatives" >&2 + return 1 + fi +} + +# See discussion of late-bound vs early-bound for why we use single-quotes with env vars +# shellcheck disable=SC2016 +install() { + # This code needs to both compute certain paths for itself to write to, and + # also write them to shell/rc files so that they can look them up to e.g. + # add them to PATH. This requires an active distinction between paths + # and expressions that can compute them. + # + # The distinction lies in when we want env-vars to be evaluated. For instance + # if we determine that we want to install to $HOME/.myapp, which do we add + # to e.g. $HOME/.profile: + # + # * early-bound: export PATH="/home/myuser/.myapp:$PATH" + # * late-bound: export PATH="$HOME/.myapp:$PATH" + # + # In this case most people would prefer the late-bound version, but in other + # cases the early-bound version might be a better idea. In particular when using + # other env-vars than $HOME, they are more likely to be only set temporarily + # for the duration of this install script, so it's more advisable to erase their + # existence with early-bounding. + # + # This distinction is handled by "double-quotes" (early) vs 'single-quotes' (late). + # + # However if we detect that "$SOME_VAR/..." is a subdir of $HOME, we try to rewrite + # it to be '$HOME/...' to get the best of both worlds. + # + # This script has a few different variants, the most complex one being the + # CARGO_HOME version which attempts to install things to Cargo's bin dir, + # potentially setting up a minimal version if the user hasn't ever installed Cargo. + # + # In this case we need to: + # + # * Install to $HOME/.cargo/bin/ + # * Create a shell script at $HOME/.cargo/env that: + # * Checks if $HOME/.cargo/bin/ is on PATH + # * and if not prepends it to PATH + # * Edits $HOME/.profile to run $HOME/.cargo/env (if the line doesn't exist) + # + # To do this we need these 4 values: + + # The actual path we're going to install to + local _install_dir + # The directory C dynamic/static libraries install to + local _lib_install_dir + # The install prefix we write to the receipt. + # For organized install methods like CargoHome, which have + # subdirectories, this is the root without `/bin`. For other + # methods, this is the same as `_install_dir`. + local _receipt_install_dir + # Path to the an shell script that adds install_dir to PATH + local _env_script_path + # Potentially-late-bound version of install_dir to write env_script + local _install_dir_expr + # Potentially-late-bound version of env_script_path to write to rcfiles like $HOME/.profile + local _env_script_path_expr + # Forces the install to occur at this path, not the default + local _force_install_dir + # Which install layout to use - "flat" or "hierarchical" + local _install_layout="unspecified" + + # Check the newer app-specific variable before falling back + # to the older generic one + if [ -n "${UV_INSTALL_DIR:-}" ]; then + _force_install_dir="$UV_INSTALL_DIR" + _install_layout="flat" + elif [ -n "${CARGO_DIST_FORCE_INSTALL_DIR:-}" ]; then + _force_install_dir="$CARGO_DIST_FORCE_INSTALL_DIR" + _install_layout="flat" + elif [ -n "$UNMANAGED_INSTALL" ]; then + _force_install_dir="$UNMANAGED_INSTALL" + _install_layout="flat" + fi + + # Check if the install layout should be changed from `flat` to `cargo-home` + # for backwards compatible updates of applications that switched layouts. + if [ -n "${_force_install_dir:-}" ]; then + if [ "$_install_layout" = "flat" ]; then + # If the install directory is targeting the Cargo home directory, then + # we assume this application was previously installed that layout + if [ "$_force_install_dir" = "${CARGO_HOME:-${HOME:-}/.cargo}" ]; then + _install_layout="cargo-home" + fi + fi + fi + + # Before actually consulting the configured install strategy, see + # if we're overriding it. + if [ -n "${_force_install_dir:-}" ]; then + case "$_install_layout" in + "hierarchical") + _install_dir="$_force_install_dir/bin" + _lib_install_dir="$_force_install_dir/lib" + _receipt_install_dir="$_force_install_dir" + _env_script_path="$_force_install_dir/env" + _install_dir_expr="$(replace_home "$_force_install_dir/bin")" + _env_script_path_expr="$(replace_home "$_force_install_dir/env")" + ;; + "cargo-home") + _install_dir="$_force_install_dir/bin" + _lib_install_dir="$_force_install_dir/bin" + _receipt_install_dir="$_force_install_dir" + _env_script_path="$_force_install_dir/env" + _install_dir_expr="$(replace_home "$_force_install_dir/bin")" + _env_script_path_expr="$(replace_home "$_force_install_dir/env")" + ;; + "flat") + _install_dir="$_force_install_dir" + _lib_install_dir="$_force_install_dir" + _receipt_install_dir="$_install_dir" + _env_script_path="$_force_install_dir/env" + _install_dir_expr="$(replace_home "$_force_install_dir")" + _env_script_path_expr="$(replace_home "$_force_install_dir/env")" + ;; + *) + err "Unrecognized install layout: $_install_layout" + ;; + esac + fi + if [ -z "${_install_dir:-}" ]; then + _install_layout="flat" + # Install to $XDG_BIN_HOME + if [ -n "${XDG_BIN_HOME:-}" ]; then + _install_dir="$XDG_BIN_HOME" + _lib_install_dir="$_install_dir" + _receipt_install_dir="$_install_dir" + _env_script_path="$XDG_BIN_HOME/env" + _install_dir_expr="$(replace_home "$_install_dir")" + _env_script_path_expr="$(replace_home "$_env_script_path")" + fi + fi + if [ -z "${_install_dir:-}" ]; then + _install_layout="flat" + # Install to $XDG_DATA_HOME/../bin + if [ -n "${XDG_DATA_HOME:-}" ]; then + _install_dir="$XDG_DATA_HOME/../bin" + _lib_install_dir="$_install_dir" + _receipt_install_dir="$_install_dir" + _env_script_path="$XDG_DATA_HOME/../bin/env" + _install_dir_expr="$(replace_home "$_install_dir")" + _env_script_path_expr="$(replace_home "$_env_script_path")" + fi + fi + if [ -z "${_install_dir:-}" ]; then + _install_layout="flat" + # Install to $HOME/.local/bin + if [ -n "${HOME:-}" ]; then + _install_dir="$HOME/.local/bin" + _lib_install_dir="$HOME/.local/bin" + _receipt_install_dir="$_install_dir" + _env_script_path="$HOME/.local/bin/env" + _install_dir_expr='$HOME/.local/bin' + _env_script_path_expr='$HOME/.local/bin/env' + fi + fi + + if [ -z "$_install_dir_expr" ]; then + err "could not find a valid path to install to!" + fi + + # Identical to the sh version, just with a .fish file extension + # We place it down here to wait until it's been assigned in every + # path. + _fish_env_script_path="${_env_script_path}.fish" + _fish_env_script_path_expr="${_env_script_path_expr}.fish" + + # Replace the temporary cargo home with the calculated one + RECEIPT=$(echo "$RECEIPT" | sed "s,AXO_INSTALL_PREFIX,$_receipt_install_dir,") + # Also replace the aliases with the arch-specific one + RECEIPT=$(echo "$RECEIPT" | sed "s'\"binary_aliases\":{}'\"binary_aliases\":$(json_binary_aliases "$_arch")'") + # And replace the install layout + RECEIPT=$(echo "$RECEIPT" | sed "s'\"install_layout\":\"unspecified\"'\"install_layout\":\"$_install_layout\"'") + if [ "$NO_MODIFY_PATH" = "1" ]; then + RECEIPT=$(echo "$RECEIPT" | sed "s'\"modify_path\":true'\"modify_path\":false'") + fi + + say "installing to $_install_dir" + ensure mkdir -p "$_install_dir" + ensure mkdir -p "$_lib_install_dir" + + # copy all the binaries to the install dir + local _src_dir="$1" + local _bins="$2" + local _libs="$3" + local _staticlibs="$4" + local _arch="$5" + for _bin_name in $_bins; do + local _bin="$_src_dir/$_bin_name" + ensure mv "$_bin" "$_install_dir" + # unzip seems to need this chmod + ensure chmod +x "$_install_dir/$_bin_name" + for _dest in $(aliases_for_binary "$_bin_name" "$_arch"); do + ln -sf "$_install_dir/$_bin_name" "$_install_dir/$_dest" + done + say " $_bin_name" + done + # Like the above, but no aliases + for _lib_name in $_libs; do + local _lib="$_src_dir/$_lib_name" + ensure mv "$_lib" "$_lib_install_dir" + # unzip seems to need this chmod + ensure chmod +x "$_lib_install_dir/$_lib_name" + say " $_lib_name" + done + for _lib_name in $_staticlibs; do + local _lib="$_src_dir/$_lib_name" + ensure mv "$_lib" "$_lib_install_dir" + # unzip seems to need this chmod + ensure chmod +x "$_lib_install_dir/$_lib_name" + say " $_lib_name" + done + + say "everything's installed!" + + # Avoid modifying the users PATH if they are managing their PATH manually + case :$PATH: + in *:$_install_dir:*) NO_MODIFY_PATH=1 ;; + *) ;; + esac + + if [ "0" = "$NO_MODIFY_PATH" ]; then + add_install_dir_to_ci_path "$_install_dir" + add_install_dir_to_path "$_install_dir_expr" "$_env_script_path" "$_env_script_path_expr" ".profile" "sh" + exit1=$? + shotgun_install_dir_to_path "$_install_dir_expr" "$_env_script_path" "$_env_script_path_expr" ".profile .bashrc .bash_profile .bash_login" "sh" + exit2=$? + add_install_dir_to_path "$_install_dir_expr" "$_env_script_path" "$_env_script_path_expr" ".zshrc .zshenv" "sh" + exit3=$? + # This path may not exist by default + ensure mkdir -p "$HOME/.config/fish/conf.d" + exit4=$? + add_install_dir_to_path "$_install_dir_expr" "$_fish_env_script_path" "$_fish_env_script_path_expr" ".config/fish/conf.d/$APP_NAME.env.fish" "fish" + exit5=$? + + if [ "${exit1:-0}" = 1 ] || [ "${exit2:-0}" = 1 ] || [ "${exit3:-0}" = 1 ] || [ "${exit4:-0}" = 1 ] || [ "${exit5:-0}" = 1 ]; then + say "" + say "To add $_install_dir_expr to your PATH, either restart your shell or run:" + say "" + say " source $_env_script_path_expr (sh, bash, zsh)" + say " source $_fish_env_script_path_expr (fish)" + fi + fi +} + +print_home_for_script() { + local script="$1" + + local _home + case "$script" in + # zsh has a special ZDOTDIR directory, which if set + # should be considered instead of $HOME + .zsh*) + if [ -n "${ZDOTDIR:-}" ]; then + _home="$ZDOTDIR" + else + _home="$HOME" + fi + ;; + *) + _home="$HOME" + ;; + esac + + echo "$_home" +} + +add_install_dir_to_ci_path() { + # Attempt to do CI-specific rituals to get the install-dir on PATH faster + local _install_dir="$1" + + # If GITHUB_PATH is present, then write install_dir to the file it refs. + # After each GitHub Action, the contents will be added to PATH. + # So if you put a curl | sh for this script in its own "run" step, + # the next step will have this dir on PATH. + # + # Note that GITHUB_PATH will not resolve any variables, so we in fact + # want to write install_dir and not install_dir_expr + if [ -n "${GITHUB_PATH:-}" ]; then + ensure echo "$_install_dir" >> "$GITHUB_PATH" + fi +} + +add_install_dir_to_path() { + # Edit rcfiles ($HOME/.profile) to add install_dir to $PATH + # + # We do this slightly indirectly by creating an "env" shell script which checks if install_dir + # is on $PATH already, and prepends it if not. The actual line we then add to rcfiles + # is to just source that script. This allows us to blast it into lots of different rcfiles and + # have it run multiple times without causing problems. It's also specifically compatible + # with the system rustup uses, so that we don't conflict with it. + local _install_dir_expr="$1" + local _env_script_path="$2" + local _env_script_path_expr="$3" + local _rcfiles="$4" + local _shell="$5" + + if [ -n "${HOME:-}" ]; then + local _target + local _home + + # Find the first file in the array that exists and choose + # that as our target to write to + for _rcfile_relative in $_rcfiles; do + _home="$(print_home_for_script "$_rcfile_relative")" + local _rcfile="$_home/$_rcfile_relative" + + if [ -f "$_rcfile" ]; then + _target="$_rcfile" + break + fi + done + + # If we didn't find anything, pick the first entry in the + # list as the default to create and write to + if [ -z "${_target:-}" ]; then + local _rcfile_relative + _rcfile_relative="$(echo "$_rcfiles" | awk '{ print $1 }')" + _home="$(print_home_for_script "$_rcfile_relative")" + _target="$_home/$_rcfile_relative" + fi + + # `source x` is an alias for `. x`, and the latter is more portable/actually-posix. + # This apparently comes up a lot on freebsd. It's easy enough to always add + # the more robust line to rcfiles, but when telling the user to apply the change + # to their current shell ". x" is pretty easy to misread/miscopy, so we use the + # prettier "source x" line there. Hopefully people with Weird Shells are aware + # this is a thing and know to tweak it (or just restart their shell). + local _robust_line=". \"$_env_script_path_expr\"" + local _pretty_line="source \"$_env_script_path_expr\"" + + # Add the env script if it doesn't already exist + if [ ! -f "$_env_script_path" ]; then + say_verbose "creating $_env_script_path" + if [ "$_shell" = "sh" ]; then + write_env_script_sh "$_install_dir_expr" "$_env_script_path" + else + write_env_script_fish "$_install_dir_expr" "$_env_script_path" + fi + else + say_verbose "$_env_script_path already exists" + fi + + # Check if the line is already in the rcfile + # grep: 0 if matched, 1 if no match, and 2 if an error occurred + # + # Ideally we could use quiet grep (-q), but that makes "match" and "error" + # have the same behaviour, when we want "no match" and "error" to be the same + # (on error we want to create the file, which >> conveniently does) + # + # We search for both kinds of line here just to do the right thing in more cases. + if ! grep -F "$_robust_line" "$_target" > /dev/null 2>/dev/null && \ + ! grep -F "$_pretty_line" "$_target" > /dev/null 2>/dev/null + then + # If the script now exists, add the line to source it to the rcfile + # (This will also create the rcfile if it doesn't exist) + if [ -f "$_env_script_path" ]; then + local _line + # Fish has deprecated `.` as an alias for `source` and + # it will be removed in a later version. + # https://fishshell.com/docs/current/cmds/source.html + # By contrast, `.` is the traditional syntax in sh and + # `source` isn't always supported in all circumstances. + if [ "$_shell" = "fish" ]; then + _line="$_pretty_line" + else + _line="$_robust_line" + fi + say_verbose "adding $_line to $_target" + # prepend an extra newline in case the user's file is missing a trailing one + ensure echo "" >> "$_target" + ensure echo "$_line" >> "$_target" + return 1 + fi + else + say_verbose "$_install_dir already on PATH" + fi + fi +} + +shotgun_install_dir_to_path() { + # Edit rcfiles ($HOME/.profile) to add install_dir to $PATH + # (Shotgun edition - write to all provided files that exist rather than just the first) + local _install_dir_expr="$1" + local _env_script_path="$2" + local _env_script_path_expr="$3" + local _rcfiles="$4" + local _shell="$5" + + if [ -n "${HOME:-}" ]; then + local _found=false + local _home + + for _rcfile_relative in $_rcfiles; do + _home="$(print_home_for_script "$_rcfile_relative")" + local _rcfile_abs="$_home/$_rcfile_relative" + + if [ -f "$_rcfile_abs" ]; then + _found=true + add_install_dir_to_path "$_install_dir_expr" "$_env_script_path" "$_env_script_path_expr" "$_rcfile_relative" "$_shell" + fi + done + + # Fall through to previous "create + write to first file in list" behavior + if [ "$_found" = false ]; then + add_install_dir_to_path "$_install_dir_expr" "$_env_script_path" "$_env_script_path_expr" "$_rcfiles" "$_shell" + fi + fi +} + +write_env_script_sh() { + # write this env script to the given path (this cat/EOF stuff is a "heredoc" string) + local _install_dir_expr="$1" + local _env_script_path="$2" + ensure cat < "$_env_script_path" +#!/bin/sh +# add binaries to PATH if they aren't added yet +# affix colons on either side of \$PATH to simplify matching +case ":\${PATH}:" in + *:"$_install_dir_expr":*) + ;; + *) + # Prepending path in case a system-installed binary needs to be overridden + export PATH="$_install_dir_expr:\$PATH" + ;; +esac +EOF +} + +write_env_script_fish() { + # write this env script to the given path (this cat/EOF stuff is a "heredoc" string) + local _install_dir_expr="$1" + local _env_script_path="$2" + ensure cat < "$_env_script_path" +if not contains "$_install_dir_expr" \$PATH + # Prepending path in case a system-installed binary needs to be overridden + set -x PATH "$_install_dir_expr" \$PATH +end +EOF +} + +check_proc() { + # Check for /proc by looking for the /proc/self/exe link + # This is only run on Linux + if ! test -L /proc/self/exe ; then + err "fatal: Unable to find /proc/self/exe. Is /proc mounted? Installation cannot proceed without /proc." + fi +} + +get_bitness() { + need_cmd head + # Architecture detection without dependencies beyond coreutils. + # ELF files start out "\x7fELF", and the following byte is + # 0x01 for 32-bit and + # 0x02 for 64-bit. + # The printf builtin on some shells like dash only supports octal + # escape sequences, so we use those. + local _current_exe_head + _current_exe_head=$(head -c 5 /proc/self/exe ) + if [ "$_current_exe_head" = "$(printf '\177ELF\001')" ]; then + echo 32 + elif [ "$_current_exe_head" = "$(printf '\177ELF\002')" ]; then + echo 64 + else + err "unknown platform bitness" + fi +} + +is_host_amd64_elf() { + need_cmd head + need_cmd tail + # ELF e_machine detection without dependencies beyond coreutils. + # Two-byte field at offset 0x12 indicates the CPU, + # but we're interested in it being 0x3E to indicate amd64, or not that. + local _current_exe_machine + _current_exe_machine=$(head -c 19 /proc/self/exe | tail -c 1) + [ "$_current_exe_machine" = "$(printf '\076')" ] +} + +get_endianness() { + local cputype=$1 + local suffix_eb=$2 + local suffix_el=$3 + + # detect endianness without od/hexdump, like get_bitness() does. + need_cmd head + need_cmd tail + + local _current_exe_endianness + _current_exe_endianness="$(head -c 6 /proc/self/exe | tail -c 1)" + if [ "$_current_exe_endianness" = "$(printf '\001')" ]; then + echo "${cputype}${suffix_el}" + elif [ "$_current_exe_endianness" = "$(printf '\002')" ]; then + echo "${cputype}${suffix_eb}" + else + err "unknown platform endianness" + fi +} + +get_architecture() { + local _ostype + local _cputype + _ostype="$(uname -s)" + _cputype="$(uname -m)" + local _clibtype="gnu" + local _local_glibc + + if [ "$_ostype" = Linux ]; then + if [ "$(uname -o)" = Android ]; then + _ostype=Android + fi + if ldd --version 2>&1 | grep -q 'musl'; then + _clibtype="musl-dynamic" + else + # Assume all other linuxes are glibc (even if wrong, static libc fallback will apply) + _clibtype="gnu" + fi + fi + + if [ "$_ostype" = Darwin ] && [ "$_cputype" = i386 ]; then + # Darwin `uname -m` lies + if sysctl hw.optional.x86_64 | grep -q ': 1'; then + _cputype=x86_64 + fi + fi + + if [ "$_ostype" = Darwin ] && [ "$_cputype" = x86_64 ]; then + # Rosetta on aarch64 + if [ "$(sysctl -n hw.optional.arm64 2>/dev/null)" = "1" ]; then + _cputype=aarch64 + fi + fi + + if [ "$_ostype" = SunOS ]; then + # Both Solaris and illumos presently announce as "SunOS" in "uname -s" + # so use "uname -o" to disambiguate. We use the full path to the + # system uname in case the user has coreutils uname first in PATH, + # which has historically sometimes printed the wrong value here. + if [ "$(/usr/bin/uname -o)" = illumos ]; then + _ostype=illumos + fi + + # illumos systems have multi-arch userlands, and "uname -m" reports the + # machine hardware name; e.g., "i86pc" on both 32- and 64-bit x86 + # systems. Check for the native (widest) instruction set on the + # running kernel: + if [ "$_cputype" = i86pc ]; then + _cputype="$(isainfo -n)" + fi + fi + + case "$_ostype" in + + Android) + _ostype=linux-android + ;; + + Linux) + check_proc + _ostype=unknown-linux-$_clibtype + _bitness=$(get_bitness) + ;; + + FreeBSD) + _ostype=unknown-freebsd + ;; + + NetBSD) + _ostype=unknown-netbsd + ;; + + DragonFly) + _ostype=unknown-dragonfly + ;; + + Darwin) + _ostype=apple-darwin + ;; + + illumos) + _ostype=unknown-illumos + ;; + + MINGW* | MSYS* | CYGWIN* | Windows_NT) + _ostype=pc-windows-gnu + ;; + + *) + err "unrecognized OS type: $_ostype" + ;; + + esac + + case "$_cputype" in + + i386 | i486 | i686 | i786 | x86) + _cputype=i686 + ;; + + xscale | arm) + _cputype=arm + if [ "$_ostype" = "linux-android" ]; then + _ostype=linux-androideabi + fi + ;; + + armv6l) + _cputype=arm + if [ "$_ostype" = "linux-android" ]; then + _ostype=linux-androideabi + else + _ostype="${_ostype}eabihf" + fi + ;; + + armv7l | armv8l) + _cputype=armv7 + if [ "$_ostype" = "linux-android" ]; then + _ostype=linux-androideabi + else + _ostype="${_ostype}eabihf" + fi + ;; + + aarch64 | arm64) + _cputype=aarch64 + ;; + + x86_64 | x86-64 | x64 | amd64) + _cputype=x86_64 + ;; + + mips) + _cputype=$(get_endianness mips '' el) + ;; + + mips64) + if [ "$_bitness" -eq 64 ]; then + # only n64 ABI is supported for now + _ostype="${_ostype}abi64" + _cputype=$(get_endianness mips64 '' el) + fi + ;; + + ppc) + _cputype=powerpc + ;; + + ppc64) + _cputype=powerpc64 + ;; + + ppc64le) + _cputype=powerpc64le + ;; + + s390x) + _cputype=s390x + ;; + riscv64) + _cputype=riscv64gc + ;; + loongarch64) + _cputype=loongarch64 + ;; + *) + err "unknown CPU type: $_cputype" + + esac + + # Detect 64-bit linux with 32-bit userland + if [ "${_ostype}" = unknown-linux-gnu ] && [ "${_bitness}" -eq 32 ]; then + case $_cputype in + x86_64) + # 32-bit executable for amd64 = x32 + if is_host_amd64_elf; then { + err "x32 linux unsupported" + }; else + _cputype=i686 + fi + ;; + mips64) + _cputype=$(get_endianness mips '' el) + ;; + powerpc64) + _cputype=powerpc + ;; + aarch64) + _cputype=armv7 + if [ "$_ostype" = "linux-android" ]; then + _ostype=linux-androideabi + else + _ostype="${_ostype}eabihf" + fi + ;; + riscv64gc) + err "riscv64 with 32-bit userland unsupported" + ;; + esac + fi + + # treat armv7 systems without neon as plain arm + if [ "$_ostype" = "unknown-linux-gnueabihf" ] && [ "$_cputype" = armv7 ]; then + if ensure grep '^Features' /proc/cpuinfo | grep -q -v neon; then + # At least one processor does not have NEON. + _cputype=arm + fi + fi + + _arch="${_cputype}-${_ostype}" + + RETVAL="$_arch" +} + +say() { + if [ "0" = "$PRINT_QUIET" ]; then + echo "$1" + fi +} + +say_verbose() { + if [ "1" = "$PRINT_VERBOSE" ]; then + echo "$1" + fi +} + +err() { + if [ "0" = "$PRINT_QUIET" ]; then + local red + local reset + red=$(tput setaf 1 2>/dev/null || echo '') + reset=$(tput sgr0 2>/dev/null || echo '') + say "${red}ERROR${reset}: $1" >&2 + fi + exit 1 +} + +need_cmd() { + if ! check_cmd "$1" + then err "need '$1' (command not found)" + fi +} + +check_cmd() { + command -v "$1" > /dev/null 2>&1 + return $? +} + +assert_nz() { + if [ -z "$1" ]; then err "assert_nz $2"; fi +} + +# Run a command that should never fail. If the command fails execution +# will immediately terminate with an error showing the failing +# command. +ensure() { + if ! "$@"; then err "command failed: $*"; fi +} + +# This is just for indicating that commands' results are being +# intentionally ignored. Usually, because it's being executed +# as part of error handling. +ignore() { + "$@" +} + +# This wraps curl or wget. Try curl first, if not installed, +# use wget instead. +downloader() { + if check_cmd curl + then _dld=curl + elif check_cmd wget + then _dld=wget + else _dld='curl or wget' # to be used in error message of need_cmd + fi + + if [ "$1" = --check ] + then need_cmd "$_dld" + elif [ "$_dld" = curl ] + then curl -sSfL "$1" -o "$2" + elif [ "$_dld" = wget ] + then wget "$1" -O "$2" + else err "Unknown downloader" # should not reach here + fi +} + +verify_checksum() { + local _file="$1" + local _checksum_style="$2" + local _checksum_value="$3" + local _calculated_checksum + + if [ -z "$_checksum_value" ]; then + return 0 + fi + case "$_checksum_style" in + sha256) + if ! check_cmd sha256sum; then + say "skipping sha256 checksum verification (it requires the 'sha256sum' command)" + return 0 + fi + _calculated_checksum="$(sha256sum -b "$_file" | awk '{printf $1}')" + ;; + sha512) + if ! check_cmd sha512sum; then + say "skipping sha512 checksum verification (it requires the 'sha512sum' command)" + return 0 + fi + _calculated_checksum="$(sha512sum -b "$_file" | awk '{printf $1}')" + ;; + sha3-256) + if ! check_cmd openssl; then + say "skipping sha3-256 checksum verification (it requires the 'openssl' command)" + return 0 + fi + _calculated_checksum="$(openssl dgst -sha3-256 "$_file" | awk '{printf $NF}')" + ;; + sha3-512) + if ! check_cmd openssl; then + say "skipping sha3-512 checksum verification (it requires the 'openssl' command)" + return 0 + fi + _calculated_checksum="$(openssl dgst -sha3-512 "$_file" | awk '{printf $NF}')" + ;; + blake2s) + if ! check_cmd b2sum; then + say "skipping blake2s checksum verification (it requires the 'b2sum' command)" + return 0 + fi + # Test if we have official b2sum with blake2s support + local _well_known_blake2s_checksum="93314a61f470985a40f8da62df10ba0546dc5216e1d45847bf1dbaa42a0e97af" + local _test_blake2s + _test_blake2s="$(printf "can do blake2s" | b2sum -a blake2s | awk '{printf $1}')" || _test_blake2s="" + + if [ "X$_test_blake2s" = "X$_well_known_blake2s_checksum" ]; then + _calculated_checksum="$(b2sum -a blake2s "$_file" | awk '{printf $1}')" || _calculated_checksum="" + else + say "skipping blake2s checksum verification (installed b2sum doesn't support blake2s)" + return 0 + fi + ;; + blake2b) + if ! check_cmd b2sum; then + say "skipping blake2b checksum verification (it requires the 'b2sum' command)" + return 0 + fi + _calculated_checksum="$(b2sum "$_file" | awk '{printf $1}')" + ;; + false) + ;; + *) + say "skipping unknown checksum style: $_checksum_style" + return 0 + ;; + esac + + if [ "$_calculated_checksum" != "$_checksum_value" ]; then + err "checksum mismatch + want: $_checksum_value + got: $_calculated_checksum" + fi +} + +download_binary_and_run_installer "$@" || exit 1