- #!/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"
-
- 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
- echo > "$PROTOTYPE_SHIM_PATH" 2>| /dev/null || ret=1
- set +o noclobber
- [ -z "${ret}" ]
- }
-
- # 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
-
- 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"
- exit 1
- fi
-
- unset acquired
- start=$SECONDS
- while (( SECONDS <= start + ${PYENV_REHASH_TIMEOUT:-60} )); do
- if acquire_lock 2>/dev/null; then
- acquired=1
- break
- else
- # 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
- echo "pyenv: cannot rehash: $PROTOTYPE_SHIM_PATH exists"
- exit 1
- fi
-
- # 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##*/}"
- if [[ "\$program" = "python"* ]]; then
- for arg; do
- case "\$arg" in
- -c* | -- ) break ;;
- */* )
- if [ -f "\$arg" ]; then
- export PYENV_FILE_ARG="\$arg"
- break
- fi
- ;;
- esac
- done
- fi
-
- export PYENV_ROOT="$PYENV_ROOT"
- 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 | sort -u)
-
-
- # 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
|