#!/usr/bin/env bash # # Summary: Install a Ruby version using ruby-build # # Usage: rbenv install [-f|-s] [-kpv] [-- ] # rbenv install [-f|-s] [-kpv] # rbenv install --list # rbenv install --version # # -l, --list List latest stable versions for each Ruby # -L, --list-all List all local versions, including outdated ones # -f, --force Allow overwriting an existing installed version # -s, --skip-existing Avoid overwriting an existing installed version # # ruby-build options: # # -v, --verbose Verbose mode: forward all build output to stdout/stderr # -p, --patch Apply a patch from stdin before building # -k, --keep Keep source tree in RBENV_BUILD_ROOT after installation # (defaults to "RBENV_ROOT/sources") # --version Show version of ruby-build # # For detailed information on installing Ruby versions with ruby-build, # including a list of environment variables for adjusting compilation, # see: https://github.com/rbenv/ruby-build#usage # set -e [ -n "$RBENV_DEBUG" ] && set -x # Add `share/ruby-build/` directory from each rbenv plugin to the list of # paths where build definitions are looked up. shopt -s nullglob for plugin_path in "$RBENV_ROOT"/plugins/*/share/ruby-build; do RUBY_BUILD_DEFINITIONS="${RUBY_BUILD_DEFINITIONS}:${plugin_path}" done export RUBY_BUILD_DEFINITIONS shopt -u nullglob # Provide rbenv completions if [ "$1" = "--complete" ]; then echo --list echo --list-all echo --force echo --skip-existing echo --keep echo --patch echo --verbose echo --version exec ruby-build --definitions fi # Load shared library functions eval "$(ruby-build --lib)" usage() { rbenv-help install 2>/dev/null [ -z "$1" ] || exit "$1" } definitions() { local query="$1" ruby-build --definitions | $(type -p ggrep grep | head -1) -F "$query" || true } suggest_selecting_global() { # shellcheck disable=SC2155 local version_file="$(rbenv-version-file)" [[ "$version_file" != "$RBENV_ROOT"/version || -e "$version_file" ]] && return 0 echo colorize 1 "NOTE:" echo -n " to activate this Ruby version as the new default, run: " colorize 33 "rbenv global $VERSION_NAME" echo } colorize() { if [ -t 1 ]; then printf "\e[%sm%s\e[m" "$1" "$2" else printf "%s" "$2" fi } indent() { sed 's/^/ /' } unset FORCE unset SKIP_EXISTING unset KEEP unset VERBOSE unset HAS_PATCH parse_options "$@" for option in "${OPTIONS[@]}"; do case "$option" in "h" | "help" ) usage 0 ;; "l" | "list" ) ruby-build --list [ ! -t 1 ] || { echo echo "Only latest stable releases for each Ruby implementation are shown." echo "Use \`rbenv install --list-all' to show all local versions." } 1>&2 exit ;; "L" | "list-all" ) ruby-build --definitions exit ;; "f" | "force" ) FORCE=true ;; "s" | "skip-existing" ) SKIP_EXISTING=true ;; "k" | "keep" ) [ -n "${RBENV_BUILD_ROOT}" ] || RBENV_BUILD_ROOT="${RBENV_ROOT}/sources" ;; "v" | "verbose" ) VERBOSE="-v" ;; "p" | "patch" ) HAS_PATCH="-p" ;; "version" ) exec ruby-build --version ;; * ) usage 1 >&2 ;; esac done [ "${#ARGUMENTS[@]}" -le 1 ] || usage 1 >&2 unset VERSION_NAME # The first argument contains the definition to install. If the # argument is missing, try to install whatever local app-specific # version is specified by rbenv. Show usage instructions if a local # version is not specified. DEFINITION="${ARGUMENTS[0]}" [ -n "$DEFINITION" ] || DEFINITION="$(rbenv-local 2>/dev/null || true)" [ -n "$DEFINITION" ] || usage 1 >&2 # Define `before_install` and `after_install` functions that allow # plugin hooks to register a string of code for execution before or # after the installation process. declare -a before_hooks after_hooks # shellcheck disable=SC2317 before_install() { local hook="$1" before_hooks["${#before_hooks[@]}"]="$hook" } # shellcheck disable=SC2317 after_install() { local hook="$1" after_hooks["${#after_hooks[@]}"]="$hook" } IFS=$'\n' read -d '' -r -a scripts <<<"$(rbenv-hooks install)" || true # shellcheck disable=SC1090 for script in "${scripts[@]}"; do source "$script"; done # Set VERSION_NAME from $DEFINITION, if it is not already set. Then # compute the installation prefix. [ -n "$VERSION_NAME" ] || VERSION_NAME="${DEFINITION##*/}" PREFIX="${RBENV_ROOT}/versions/${VERSION_NAME}" [ -d "${PREFIX}" ] && PREFIX_EXISTS=1 # If the installation prefix exists, prompt for confirmation unless # the --force option was specified. if [ -d "${PREFIX}/bin" ]; then if [ -z "$FORCE" ] && [ -z "$SKIP_EXISTING" ]; then echo "rbenv: $PREFIX already exists" >&2 if [ ! -t 0 ]; then echo "rbenv: must use \`--force' or \`--skip-existing' in non-interactive mode" >&2 exit 1 fi read -rp "continue with installation? (y/N) " case "$REPLY" in y* | Y* ) ;; * ) exit 1 ;; esac elif [ -n "$SKIP_EXISTING" ]; then # Since we know the ruby version is already installed, and are opting to # not force installation of existing versions, we just `exit 0` here to # leave things happy exit 0 fi fi # If RBENV_BUILD_ROOT is set, always pass keep options to ruby-build. if [ -n "${RBENV_BUILD_ROOT}" ]; then export RUBY_BUILD_BUILD_PATH="${RBENV_BUILD_ROOT}/${VERSION_NAME}" KEEP="-k" fi # Set RUBY_BUILD_CACHE_PATH to $RBENV_ROOT/cache, if the directory # exists and the variable is not already set. if [ -z "${RUBY_BUILD_CACHE_PATH}" ] && [ -d "${RBENV_ROOT}/cache" ]; then export RUBY_BUILD_CACHE_PATH="${RBENV_ROOT}/cache" fi # Default RBENV_VERSION to the globally-specified Ruby version. (The # REE installer requires an existing Ruby installation to run. An # unsatisfied local .ruby-version file can cause the installer to # fail.) # shellcheck disable=SC2155 export RBENV_VERSION="$(rbenv-global 2>/dev/null || true)" # Execute `before_install` hooks. for hook in "${before_hooks[@]}"; do eval "$hook"; done # Plan cleanup on unsuccessful installation. cleanup() { [ -z "${PREFIX_EXISTS}" ] && rm -rf "$PREFIX" } trap cleanup SIGINT build_args=(${KEEP:+--keep} ${VERBOSE:+--verbose} ${HAS_PATCH:+--patch} "$DEFINITION" "$PREFIX") [ ${#EXTRA_ARGUMENTS[@]} -eq 0 ] || build_args+=(-- "${EXTRA_ARGUMENTS[@]}") # Invoke `ruby-build` and record the exit status in $STATUS. STATUS=0 ruby-build "${build_args[@]}" || STATUS="$?" # Display a more helpful message if the definition wasn't found. if [ "$STATUS" == "2" ]; then { candidates="$(definitions "$DEFINITION")" here="$(dirname "${0%/*}")" if [ -n "$candidates" ]; then echo echo "The following versions contain \`$DEFINITION' in the name:" echo "$candidates" | indent fi echo echo "See all available versions with \`rbenv install --list-all'." echo echo -n "If the version you need is missing, try upgrading ruby-build" if [ "$here" != "${here#"$(brew --prefix 2>/dev/null)"}" ]; then printf ":\n\n" echo " brew upgrade ruby-build" elif [ -d "${here}/.git" ]; then printf ":\n\n" echo " git -C ${here/${HOME}\//~/} pull" else printf ".\n" fi } >&2 fi # Execute `after_install` hooks. for hook in "${after_hooks[@]}"; do eval "$hook"; done # Run `rbenv-rehash` after a successful installation. if [ "$STATUS" == "0" ]; then rbenv-rehash suggest_selecting_global else cleanup fi exit "$STATUS"