mirror of
https://github.com/pyenv/pyenv.git
synced 2024-12-02 18:15:48 -05:00
76e64ff2ea
Homebrew places the rbenv executable in a location such as `/usr/local/bin/rbenv`, which is in PATH. However, that is a symlink to `/usr/local/Cellar/rbenv/<VERSION>/bin/rbenv`, which is itself a symlink to `/usr/local/Cellar/rbenv/<VERSION>/libexec/rbenv`. Upon executing, rbenv will add its own directory to PATH so that it can easily invoke its subcommands. When generating shims during `rbenv rehash`, rbenv will try to put the absolute path to itself inside each shim so that shims would work even if rbenv itself isn't in PATH. Under Homebrew, rbenv's directory will be the versioned directory in Homebrew's Cellar. However, due to Homebrew's auto-cleanup functionality, shims generated this way will be broken after upgrading rbenv because of the versioned Cellar path. This changes how rbenv discovers itself in PATH: it will look at the original PATH, not in the one modified by rbenv, with the intention of excluding results under rbenv's own `libexec/`. If rbenv wasn't found in PATH, return the absolute path to rbenv's own `bin/rbenv`.
167 lines
4.3 KiB
Bash
Executable file
167 lines
4.3 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Summary: Rehash rbenv shims (run this after installing executables)
|
|
|
|
set -e
|
|
[ -n "$RBENV_DEBUG" ] && set -x
|
|
|
|
SHIM_PATH="${RBENV_ROOT}/shims"
|
|
PROTOTYPE_SHIM_PATH="${SHIM_PATH}/.rbenv-shim"
|
|
|
|
# Create the shims directory if it doesn't already exist.
|
|
mkdir -p "$SHIM_PATH"
|
|
|
|
# Ensure only one instance of rbenv-rehash is running at a time by
|
|
# setting the shell's `noclobber` option and attempting to write to
|
|
# the prototype shim file. If the file already exists, print a warning
|
|
# to stderr and exit with a non-zero status.
|
|
set -o noclobber
|
|
{ echo > "$PROTOTYPE_SHIM_PATH"
|
|
} 2>| /dev/null ||
|
|
{ if [ -w "$SHIM_PATH" ]; then
|
|
echo "rbenv: cannot rehash: $PROTOTYPE_SHIM_PATH exists"
|
|
else
|
|
echo "rbenv: cannot rehash: $SHIM_PATH isn't writable"
|
|
fi
|
|
exit 1
|
|
} >&2
|
|
set +o noclobber
|
|
|
|
# If we were able to obtain a lock, register a trap to clean up the
|
|
# prototype shim when the process exits.
|
|
trap remove_prototype_shim EXIT
|
|
|
|
remove_prototype_shim() {
|
|
rm -f "$PROTOTYPE_SHIM_PATH"
|
|
}
|
|
|
|
# Locates rbenv as found in the user's PATH. Otherwise, returns an
|
|
# absolute path to the rbenv executable itself.
|
|
rbenv_path() {
|
|
local found
|
|
found="$(PATH="$RBENV_ORIG_PATH" command -v rbenv)"
|
|
if [[ $found == /* ]]; then
|
|
echo "$found"
|
|
elif [[ -n "$found" ]]; then
|
|
echo "$PWD/${found#./}"
|
|
else
|
|
# Assume rbenv isn't in PATH.
|
|
local here="${BASH_SOURCE%/*}"
|
|
echo "${here%/*}/bin/rbenv"
|
|
fi
|
|
}
|
|
|
|
# The prototype shim file is a script that re-execs itself, passing
|
|
# its filename and any arguments to `rbenv exec`. This file is
|
|
# hard-linked for every executable and then removed. The linking
|
|
# technique is fast, uses less disk space than unique files, and also
|
|
# serves as a locking mechanism.
|
|
create_prototype_shim() {
|
|
cat > "$PROTOTYPE_SHIM_PATH" <<SH
|
|
#!/usr/bin/env bash
|
|
set -e
|
|
[ -n "\$RBENV_DEBUG" ] && set -x
|
|
|
|
program="\${0##*/}"
|
|
if [ "\$program" = "ruby" ]; then
|
|
for arg; do
|
|
case "\$arg" in
|
|
-e* | -- ) break ;;
|
|
*/* )
|
|
if [ -f "\$arg" ]; then
|
|
export RBENV_DIR="\${arg%/*}"
|
|
break
|
|
fi
|
|
;;
|
|
esac
|
|
done
|
|
fi
|
|
|
|
export RBENV_ROOT="$RBENV_ROOT"
|
|
exec "$(rbenv_path)" exec "\$program" "\$@"
|
|
SH
|
|
chmod +x "$PROTOTYPE_SHIM_PATH"
|
|
}
|
|
|
|
# If the contents of the prototype shim file differ from the contents
|
|
# of the first shim in the shims directory, assume rbenv has been
|
|
# upgraded and the existing shims need to be removed.
|
|
remove_outdated_shims() {
|
|
local shim
|
|
for shim in "$SHIM_PATH"/*; do
|
|
if ! diff "$PROTOTYPE_SHIM_PATH" "$shim" >/dev/null 2>&1; then
|
|
rm -f "$SHIM_PATH"/*
|
|
fi
|
|
break
|
|
done
|
|
}
|
|
|
|
# List basenames of executables for every Ruby version
|
|
list_executable_names() {
|
|
local version file
|
|
rbenv-versions --bare --skip-aliases | \
|
|
while read -r version; do
|
|
for file in "${RBENV_ROOT}/versions/${version}/bin/"*; do
|
|
echo "${file##*/}"
|
|
done
|
|
done
|
|
}
|
|
|
|
# The basename of each argument passed to `make_shims` will be
|
|
# registered for installation as a shim. In this way, plugins may call
|
|
# `make_shims` with a glob to register many shims at once.
|
|
make_shims() {
|
|
local file shim
|
|
for file; do
|
|
shim="${file##*/}"
|
|
registered_shims+=("$shim")
|
|
done
|
|
}
|
|
|
|
# Registers the name of a shim to be generated.
|
|
register_shim() {
|
|
registered_shims+=("$1")
|
|
}
|
|
|
|
# Install all the shims registered via `make_shims` or `register_shim` directly.
|
|
install_registered_shims() {
|
|
local shim file
|
|
for shim in "${registered_shims[@]}"; do
|
|
file="${SHIM_PATH}/${shim}"
|
|
[ -e "$file" ] || cp "$PROTOTYPE_SHIM_PATH" "$file"
|
|
done
|
|
}
|
|
|
|
# Once the registered shims have been installed, we make a second pass
|
|
# over the contents of the shims directory. Any file that is present
|
|
# in the directory but has not been registered as a shim should be
|
|
# removed.
|
|
remove_stale_shims() {
|
|
local shim
|
|
local known_shims=" ${registered_shims[*]} "
|
|
for shim in "$SHIM_PATH"/*; do
|
|
if [[ "$known_shims" != *" ${shim##*/} "* ]]; then
|
|
rm -f "$shim"
|
|
fi
|
|
done
|
|
}
|
|
|
|
shopt -s nullglob
|
|
|
|
# Create the prototype shim, then register shims for all known
|
|
# executables.
|
|
create_prototype_shim
|
|
remove_outdated_shims
|
|
# shellcheck disable=SC2207
|
|
registered_shims=( $(list_executable_names | sort -u) )
|
|
|
|
# Allow plugins to register shims.
|
|
OLDIFS="$IFS"
|
|
IFS=$'\n' scripts=(`rbenv-hooks rehash`)
|
|
IFS="$OLDIFS"
|
|
|
|
for script in "${scripts[@]}"; do
|
|
source "$script"
|
|
done
|
|
|
|
install_registered_shims
|
|
remove_stale_shims
|