#!/usr/bin/env bash # Copied from # https://github.com/volta-cli/volta/blob/master/dev/unix/volta-install.sh # LICENSE: # BSD 2-CLAUSE LICENSE # # Copyright (c) 2017, The go-cli-prototype Contributors. # All rights reserved. # # This product includes: # # Contributions from LinkedIn Corporation # Copyright (c) 2017, LinkedIn Corporation. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # The views and conclusions contained in the software and documentation are those # of the authors and should not be interpreted as representing official policies, # either expressed or implied, of the FreeBSD Project. get_latest_release() { curl --silent "https://api.github.com/repos/elliotxx/go-cli-prototype/releases/latest" | \ grep tag_name | \ cut -d '"' -f 4 } release_url() { echo "https://github.com/elliotxx/go-cli-prototype/releases" } download_release_from_repo() { local version="$1" local arch="$2" local os_info="$3" local tmpdir="$4" local filename="go-cli-prototype_${version:1}_${os_info}_$arch.tar.gz" local download_file="$tmpdir/$filename" local archive_url="$(release_url)/download/$version/$filename" info $archive_url curl --progress-bar --show-error --location --fail "$archive_url" \ --output "$download_file" && echo "$download_file" } usage() { cat >&2 < Install a specific release version of go-cli-prototype END_USAGE } info() { local action="$1" local details="$2" command printf '\033[1;32m%12s\033[0m %s\n' "$action" "$details" 1>&2 } error() { command printf '\033[1;31mError\033[0m: %s\n\n' "$1" 1>&2 } warning() { command printf '\033[1;33mWarning\033[0m: %s\n\n' "$1" 1>&2 } request() { command printf '\033[1m%s\033[0m\n' "$1" 1>&2 } eprintf() { command printf '%s\n' "$1" 1>&2 } bold() { command printf '\033[1m%s\033[0m' "$1" } # If file exists, echo it echo_fexists() { [ -f "$1" ] && echo "$1" } detect_profile() { local shellname="$1" local uname="$2" if [ -f "$PROFILE" ]; then echo "$PROFILE" return fi # try to detect the current shell case "$shellname" in bash) # Shells on macOS default to opening with a login shell, while Linuxes # default to a *non*-login shell, so if this is macOS we look for # `.bash_profile` first; if it's Linux, we look for `.bashrc` first. The # `*` fallthrough covers more than just Linux: it's everything that is not # macOS (Darwin). It can be made narrower later if need be. case $uname in Darwin) echo_fexists "$HOME/.bash_profile" || echo_fexists "$HOME/.bashrc" ;; *) echo_fexists "$HOME/.bashrc" || echo_fexists "$HOME/.bash_profile" ;; esac ;; zsh) echo "$HOME/.zshrc" ;; fish) echo "$HOME/.config/fish/config.fish" ;; *) # Fall back to checking for profile file existence. Once again, the order # differs between macOS and everything else. local profiles case $uname in Darwin) profiles=( .profile .bash_profile .bashrc .zshrc .config/fish/config.fish ) ;; *) profiles=( .profile .bashrc .bash_profile .zshrc .config/fish/config.fish ) ;; esac for profile in "${profiles[@]}"; do echo_fexists "$HOME/$profile" && break done ;; esac } # generate shell code to source the loading script and modify the path for the input profile build_path_str() { local profile="$1" local profile_install_dir="$2" if [[ $profile =~ \.fish$ ]]; then # fish uses a little different syntax to modify the PATH cat < /dev/null; or set -gx PATH "\$CLI_HOME/bin" \$PATH END_FISH_SCRIPT else # bash and zsh cat <> "$detected_profile" else warning "Your profile ($detected_profile) already mentions go-cli-prototype and has not been changed." fi fi } # Check if it is OK to upgrade to the new version upgrade_is_ok() { local will_install_version="$1" local install_dir="$2" local is_dev_install="$3" local cli_bin="$install_dir/go-cli-prototype" if [[ -n "$install_dir" && -x "$cli_bin" ]]; then local prev_version="$( ($cli_bin --version 2>/dev/null || echo 0.1) | sed -E 's/^.*([0-9]+\.[0-9]+\.[0-9]+).*$/\1/')" # if this is a local dev install, skip the equality check # if installing the same version, this is a no-op if [ "$is_dev_install" != "true" ] && [ "$prev_version" == "$will_install_version" ]; then eprintf "Version $will_install_version already installed" return 1 fi # in the future, check $prev_version for incompatible upgrades fi return 0 } # returns the os name to be used in the packaged release, # including the openssl info if necessary parse_os_info() { local uname_str="$1" local openssl_version="$2" case "$uname_str" in Linux) echo "Linux" ;; Darwin) echo "Darwin" ;; *) return 1 esac return 0 } parse_os_pretty() { local uname_str="$1" case "$uname_str" in Linux) echo "Linux" ;; Darwin) echo "macOS" ;; *) echo "$uname_str" esac } # return true(0) if the element is contained in the input arguments # called like: # if element_in "foo" "${array[@]}"; then ... element_in() { local match="$1"; shift local element; # loop over the input arguments and return when a match is found for element in "$@"; do [ "$element" == "$match" ] && return 0 done return 1 } create_tree() { local install_dir="$1" info 'Creating' "directory layout" # .go-cli-prototype/ # bin/ mkdir -p "$install_dir" mkdir -p "$install_dir"/bin } install_version() { local version_to_install="$1" local install_dir="$2" if ! cli_home_is_ok; then exit 1 fi case "$version_to_install" in latest) local latest_version="$(get_latest_release)" info 'Installing' "latest version of go-cli-prototype ($latest_version)" install_release "$latest_version" "$install_dir" ;; local-dev) info 'Installing' "go-cli-prototype locally after compiling" install_local "dev" "$install_dir" ;; local-release) info 'Installing' "go-cli-prototype locally after compiling with '--release'" install_local "release" "$install_dir" ;; *) # assume anything else is a specific version info 'Installing' "go-cli-prototype version $version_to_install" install_release "$version_to_install" "$install_dir" ;; esac if [ "$?" == 0 ] then update_profile "$install_dir" && info "Finished" 'installation. Open a new terminal to start using go-cli-prototype!' fi } # parse the 'version = "X.Y.Z"' line from the input Cargo.toml contents # and return the version string parse_cargo_version() { local contents="$1" while read -r line do if [[ "$line" =~ ^version\ =\ \"(.*)\" ]] then echo "${BASH_REMATCH[1]}" return 0 fi done <<< "$contents" error "Could not determine the current version from Cargo.toml" return 1 } install_release() { local version="$1" local install_dir="$2" local is_dev_install="false" info 'Checking' "for existing go-cli-prototype installation" if upgrade_is_ok "$version" "$install_dir" "$is_dev_install" then download_archive="$(download_release "$version"; exit "$?")" exit_status="$?" if [ "$exit_status" != 0 ] then error "Could not download go-cli-prototype version '$version'. See $(release_url) for a list of available releases" return "$exit_status" fi install_from_file "$download_archive" "$install_dir" else # existing legacy install, or upgrade problem return 1 fi } install_local() { local dev_or_release="$1" local install_dir="$2" # this is a local install, so skip the version equality check local is_dev_install="true" info 'Checking' "for existing go-cli-prototype installation" install_version="$(parse_cargo_version "$(/dev/null 2>&1 && pwd )" # call the release script to create the packaged archive file # '2> >(tee /dev/stderr)' copies stderr to stdout, to collect it and parse the filename release_output="$( "$DIR/release.sh" "--$dev_or_release" 2> >(tee /dev/stderr) )" [ "$?" != 0 ] && return 1 # parse the release filename and return that if [[ "$release_output" =~ release\ in\ file\ (target[^\ ]+) ]]; then echo "${BASH_REMATCH[1]}" else error "Could not determine output filename" return 1 fi } download_release() { local version="$1" local arch="$(get_architecture)" local uname_str="$(uname -s)" local os_info os_info="$(parse_os_info "$uname_str")" if [ "$?" != 0 ]; then error "The current operating system ($uname_str) does not appear to be supported by go-cli-prototype." return 1 fi local pretty_os_name="$(parse_os_pretty "$uname_str")" info 'Fetching' "archive for $pretty_os_name, version $version" # store the downloaded archive in a temporary directory local download_dir="$(mktemp -d)" download_release_from_repo "$version" "$arch" "$os_info" "$download_dir" } install_from_file() { local archive="$1" local copy_to="$2" local extract_to="$(dirname $archive)" # local extracted_path="$extract_to/$(basename $archive .tar.xz)" create_tree "$copy_to" info 'Extracting' "go-cli-prototype binaries" # extract the files to the temp directory tar -xvf "$archive" -C "$extract_to" # copy the files to the specified directory # binaries go into the bin folder cp "$extract_to/go-cli-prototype" "$copy_to/bin" # the others directly into the specified folder cp "$extract_to/LICENSE" "$extract_to/README.md" "$copy_to" } get_architecture() { local arch="$(uname -m)" case "$arch" in # macOS on aarch64 says "arm64" instead. arm64) arch=aarch64 ;; esac echo "$arch" } check_architecture() { local version="$1" local arch="$2" local os="$3" # Local version always allowed. if [[ "$version" == "local"* ]]; then return 0 fi # Otherwise, check the matrix of OS/architecture support. case "$arch/$os" in aarch64/Linux) return 0 ;; aarch64/Darwin) return 0 ;; s390x/Linux) return 0 ;; x86_64/*) return 0 ;; esac error "Sorry! go-cli-prototype currently only provides pre-built binaries for x86_64 (Linux, macOS, Windows), aarch64 (Linux, macOS), and s390x (Linux)." return 1 } # return if sourced (for testing the functions above) return 0 2>/dev/null # default to installing the latest available version version_to_install="latest" # install to CLI_HOME, defaulting to ~/.go-cli-prototype install_dir="${CLI_HOME:-"$HOME/.go-cli-prototype"}" # parse command line options while [ $# -gt 0 ] do arg="$1" case "$arg" in -h|--help) usage exit 0 ;; --dev) shift # shift off the argument version_to_install="local-dev" ;; --release) shift # shift off the argument version_to_install="local-release" ;; --version) shift # shift off the argument version_to_install="$1" shift # shift off the value ;; *) error "unknown option: '$arg'" usage exit 1 ;; esac done check_architecture "$version_to_install" "$(get_architecture)" "$(uname)" || exit 1 install_version "$version_to_install" "$install_dir"