diff --git a/libexec/pyenv-rehash b/libexec/pyenv-rehash index e2041301..253ab68a 100755 --- a/libexec/pyenv-rehash +++ b/libexec/pyenv-rehash @@ -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. diff --git a/libexec/pyenv-versions b/libexec/pyenv-versions index afe8747f..bfd67c70 100755 --- a/libexec/pyenv-versions +++ b/libexec/pyenv-versions @@ -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 /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 diff --git a/test/test_helper.bash b/test/test_helper.bash index dba2e58c..9df7a1ce 100644 --- a/test/test_helper.bash +++ b/test/test_helper.bash @@ -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 diff --git a/test/versions.bats b/test/versions.bats index 58ab032c..07acc4f6 100644 --- a/test/versions.bats +++ b/test/versions.bats @@ -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 <