1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
|
#!/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##*/}"
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
|