rehash: streamline executables discovery; fix regression (#3418)

Separate the corner-cutting code for `rehash' into a dedicated branch.

Saves some more time (about 100ms for the large fixture)
This commit is contained in:
native-api 2026-03-07 13:09:14 +03:00 committed by GitHub
parent d5fa1e225e
commit 8037f22665
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 53 additions and 36 deletions

View File

@ -110,17 +110,6 @@ remove_outdated_shims() {
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.
@ -196,7 +185,7 @@ shopt -s nullglob
create_prototype_shim
remove_outdated_shims
# shellcheck disable=SC2046
make_shims $(list_executable_names)
make_shims $(pyenv-versions --executables)
# Allow plugins to register shims.

View File

@ -1,13 +1,21 @@
#!/usr/bin/env bash
# Summary: List all Python versions available to pyenv
# Usage: pyenv versions [--bare] [--skip-aliases] [--skip-envs]
# Usage: pyenv versions [--bare] [--skip-aliases] [--skip-envs] [--executables]
#
# Lists all Python versions found in `$PYENV_ROOT/versions/*'.
#
# --bare List just the names, omit `system'
# --skip-aliases Skip symlinks to other versions and to virtual environments
# --skip-envs Skip virtual environments (under <version>/envs)
# --executables Internal. Overrides other options.
# Optimally get a deduplicated list of all executable names in Pyenv-managed
# versions and environments for `pyenv rehash'
#
set -e
[ -n "$PYENV_DEBUG" ] && set -x
unset bare skip_aliases skip_envs
unset bare skip_aliases skip_envs executables
# Provide pyenv completions
for arg; do
case "$arg" in
@ -16,6 +24,7 @@ for arg; do
echo --skip-aliases
echo --skip-envs
exit ;;
--executables ) executables=1; break ;;
--bare ) bare=1 ;;
--skip-aliases ) skip_aliases=1 ;;
--skip-envs ) skip_envs=1 ;;
@ -26,31 +35,21 @@ for arg; do
esac
done
# Fast path for --bare --skip-aliases: skip sort and full realpath resolution
if [[ -n "$bare" && -n "$skip_aliases" ]]; then
versions_dir="${PYENV_ROOT}/versions"
versions_dir="${PYENV_ROOT}/versions"
# Fast path for rehash: skip filtering and link resolution
if [[ -n "$executables" ]]; then
if [ -d "$versions_dir" ]; then
shopt -s dotglob nullglob
for path in "$versions_dir"/*/; do
path="${path%/}"
if [ -L "$path" ]; then
# Relative link = internal alias → skip; absolute = external → keep
[[ "$(readlink "$path")" == /* ]] || continue
fi
echo "${path##*/}"
if [[ -z "$skip_envs" ]]; then
for env_path in "${path}/envs/"*; do
[ -d "$env_path" ] && echo "${env_path#${versions_dir}/}"
done
fi
done
# MacOS 12+ and FreeBSD 15 support `xargs -r -0' and `basename -a'
# `sort -u` is simpler and a bit faster than `awk '!seen[$0]++'`, with the same result for rehash purposes
printf '%s\0' "$versions_dir"/*/bin/* "$versions_dir"/*/envs/*/bin/* | xargs -0 -r basename -a | sort -u
shopt -u dotglob nullglob
fi
exit 0
fi
versions_dir="${PYENV_ROOT}/versions"
if ! enable -f "${BASH_SOURCE%/*}"/pyenv-realpath.dylib realpath 2>/dev/null; then
if [ -n "$PYENV_NATIVE_EXT" ]; then
echo "pyenv: failed to load \`realpath' builtin" >&2

View File

@ -135,7 +135,7 @@ path_without() {
if [ "$found" != "${PYENV_ROOT}/shims" ]; then
alt="${PYENV_TEST_DIR}/$(echo "${found#/}" | tr '/' '-')"
mkdir -p "$alt"
for util in bash head cut readlink greadlink tr sed; do
for util in bash head cut readlink greadlink tr sed xargs basename sort; do
if [ -x "${found}/$util" ]; then
ln -s "${found}/$util" "${alt}/$util"
fi

View File

@ -11,6 +11,11 @@ create_alias() {
ln -s "$2" "${PYENV_ROOT}/versions/$1"
}
create_external_version() {
mkdir -p "$PYENV_TEST_DIR/${1:?}"
create_alias "${1:?}" "$PYENV_TEST_DIR/${1:?}"
}
_setup() {
mkdir -p "$PYENV_TEST_DIR"
cd "$PYENV_TEST_DIR"
@ -168,15 +173,39 @@ OUT
@test "doesn't list symlink aliases when --skip-aliases" {
create_version "1.8.7"
create_alias "1.8" "1.8.7"
mkdir moo
create_alias "1.9" "${PWD}/moo"
create_external_version "moo"
run pyenv-versions --bare --skip-aliases
assert_success
assert_output <<OUT
1.8.7
1.9
moo
OUT
}
@test "--executables lists executables everywhere and overrides other switches" {
create_alt_executable_in_version "3.5.0" "python"
create_alt_executable_in_version "3.5.0" "python1"
create_alt_executable_in_version "3.6.0" "python"
create_alt_executable_in_version "3.5.0/envs/foo" "python_foo"
create_alt_executable_in_version "3.6.0/envs/bar" "python_bar"
create_alias "bar" "3.6.0/envs/bar"
create_external_version "moo"
create_alt_executable_in_version "moo" "moopython"
run pyenv-versions --skip-aliases --skip-envs --executables
assert_success
#The sort order does not matter for this functionality. However,
#MacOS 15 `sort` sorts differently that Linux's due to a different LC_COLLATE definition for en-US:
#https://unix.stackexchange.com/questions/362728/why-does-gnu-sort-sort-differently-on-my-osx-machine-and-linux-machine
#So to get a match, we have to check against the same order that the local `sort` produces
sort <<OUT | assert_output
moopython
python
python1
python_bar
python_foo
OUT
}