mirror of
https://github.com/pyenv/pyenv.git
synced 2026-04-06 21:55:11 +09:00
register_shim() already deduplicates via associative array (bash 4+) or by checking if a shim already exists (bash 3.2). The sort -u pipe in the make_shims call is thus redundant -- the dedup happens downstream regardless. Shim creation is order-independent and idempotent, so sorting has no semantic effect either. Saves one subprocess fork during every rehash invocation.
213 lines
5.8 KiB
Bash
Executable File
213 lines
5.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Summary: Rehash pyenv shims (run this after installing executables)
|
|
|
|
set -e
|
|
[ -n "$PYENV_DEBUG" ] && set -x
|
|
|
|
SHIM_PATH="${PYENV_ROOT}/shims"
|
|
PROTOTYPE_SHIM_PATH="${SHIM_PATH}/.pyenv-shim"
|
|
|
|
# Create the shims directory if it doesn't already exist.
|
|
mkdir -p "$SHIM_PATH"
|
|
|
|
declare last_acquire_error
|
|
|
|
acquire_lock() {
|
|
# Ensure only one instance of pyenv-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.
|
|
local ret
|
|
set -o noclobber
|
|
last_acquire_error="$( { ( echo -n > "$PROTOTYPE_SHIM_PATH"; ) 2>&1 1>&3 3>&1-; } 3>&1)" || ret=1
|
|
set +o noclobber
|
|
[ -z "${ret}" ]
|
|
}
|
|
|
|
remove_prototype_shim() {
|
|
rm -f "$PROTOTYPE_SHIM_PATH"
|
|
}
|
|
|
|
release_lock() {
|
|
remove_prototype_shim
|
|
}
|
|
|
|
if [ ! -w "$SHIM_PATH" ]; then
|
|
echo "pyenv: cannot rehash: $SHIM_PATH isn't writable" >&2
|
|
exit 1
|
|
fi
|
|
|
|
declare acquired tested_for_other_write_errors
|
|
declare start=$SECONDS
|
|
PYENV_REHASH_TIMEOUT=${PYENV_REHASH_TIMEOUT:-60}
|
|
while (( SECONDS <= start + PYENV_REHASH_TIMEOUT )); do
|
|
if acquire_lock; then
|
|
acquired=1
|
|
|
|
# If we were able to obtain a lock, register a trap to clean up the
|
|
# prototype shim when the process exits.
|
|
trap release_lock EXIT
|
|
|
|
break
|
|
else
|
|
#Landlock sandbox subsystem in the Linux kernel returns false information in access() as of 6.14.0,
|
|
# making -w "$SHIM_PATH" not catch the fact that the shims dir is not writable in this case.
|
|
#Bash doesn't provide access to errno to check for non-EEXIST error code in acquire_lock.
|
|
#So check for writablity by trying to write to a different file,
|
|
# in a way that taxes the usual use case as little as possible.
|
|
if [[ -z $tested_for_other_write_errors ]]; then
|
|
( t="$(TMPDIR="$SHIM_PATH" mktemp)" && rm "$t" ) && tested_for_other_write_errors=1 ||
|
|
{ echo "pyenv: cannot rehash: $SHIM_PATH isn't writable" >&2; break; }
|
|
fi
|
|
# POSIX sleep(1) doesn't provide subsecond precision, but many others do
|
|
sleep 0.1 2>/dev/null || sleep 1
|
|
fi
|
|
done
|
|
|
|
if [ -z "${acquired}" ]; then
|
|
if [[ -n $tested_for_other_write_errors ]]; then
|
|
echo "pyenv: cannot rehash: couldn't acquire lock"\
|
|
"$PROTOTYPE_SHIM_PATH for $PYENV_REHASH_TIMEOUT seconds. Last error message:" >&2
|
|
echo "$last_acquire_error" >&2
|
|
fi
|
|
exit 1
|
|
fi
|
|
unset tested_for_other_write_errors
|
|
|
|
# The prototype shim file is a script that re-execs itself, passing
|
|
# its filename and any arguments to `pyenv 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 "\$PYENV_DEBUG" ] && set -x
|
|
|
|
program="\${0##*/}"
|
|
|
|
export PYENV_ROOT="$PYENV_ROOT"
|
|
SHIM_PATH=\${0%/*}
|
|
if [[ \$SHIM_PATH != "$PYENV_ROOT/shims" ]]; then
|
|
export _PYENV_SHIM_PATH="\$SHIM_PATH"
|
|
fi
|
|
exec "$(command -v pyenv)" 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 pyenv 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 Python version
|
|
list_executable_names() {
|
|
local version file
|
|
pyenv-versions --bare --skip-aliases | \
|
|
while read -r version; do
|
|
for file in "${PYENV_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##*/}"
|
|
register_shim "$shim"
|
|
done
|
|
}
|
|
|
|
if ((${BASH_VERSINFO[0]} > 3)); then
|
|
|
|
declare -A registered_shims
|
|
|
|
# Registers the name of a shim to be generated.
|
|
register_shim() {
|
|
registered_shims["$1"]=1
|
|
}
|
|
|
|
# Install all 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
|
|
for shim in "$SHIM_PATH"/*; do
|
|
if [[ ! ${registered_shims["${shim##*/}"]} ]]; then
|
|
rm -f "$shim"
|
|
fi
|
|
done
|
|
}
|
|
|
|
else # Same for bash < 4.
|
|
|
|
registered_shims=" "
|
|
|
|
register_shim() {
|
|
registered_shims="${registered_shims}${1} "
|
|
}
|
|
|
|
install_registered_shims() {
|
|
local shim file
|
|
for shim in $registered_shims; do
|
|
file="${SHIM_PATH}/${shim}"
|
|
[ -e "$file" ] || cp "$PROTOTYPE_SHIM_PATH" "$file"
|
|
done
|
|
}
|
|
|
|
remove_stale_shims() {
|
|
local shim
|
|
for shim in "$SHIM_PATH"/*; do
|
|
if [[ "$registered_shims" != *" ${shim##*/} "* ]]; then
|
|
rm -f "$shim"
|
|
fi
|
|
done
|
|
}
|
|
fi
|
|
|
|
shopt -s nullglob
|
|
|
|
# Create the prototype shim, then register shims for all known
|
|
# executables.
|
|
create_prototype_shim
|
|
remove_outdated_shims
|
|
# shellcheck disable=SC2046
|
|
make_shims $(list_executable_names)
|
|
|
|
|
|
# Allow plugins to register shims.
|
|
OLDIFS="$IFS"
|
|
IFS=$'\n' scripts=(`pyenv-hooks rehash`)
|
|
IFS="$OLDIFS"
|
|
|
|
for script in "${scripts[@]}"; do
|
|
source "$script"
|
|
done
|
|
|
|
install_registered_shims
|
|
remove_stale_shims
|