Add --no-push-path option (#2526)

In some advanced shell setups, the order of custom-added PATH entries is important.
We disregard it by default, always pushing shims to the front of PATH,
to ensure that Pyenv works even in poorly maintained shell environments
and with minimal hassle for non-export users
(an attempt to do the opposite (#1898) has ended in a disaster).
Some advanced users are however ready and able to carefully maintain their environment
and deal with breakages and inconvenience. This option is for them.
This commit is contained in:
Isaac Levy 2022-11-18 16:01:59 -05:00 committed by GitHub
parent 4c261e6ea1
commit cc56f76733
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 104 additions and 25 deletions

View File

@ -386,11 +386,12 @@ List existing pyenv shims.
Configure the shell environment for pyenv Configure the shell environment for pyenv
Usage: eval "$(pyenv init [-|--path] [--no-rehash] [<shell>])" Usage: eval "$(pyenv init [-|--path] [--no-push-path] [--no-rehash] [<shell>])"
- Initialize shims directory, print PYENV_SHELL variable, completions path - Initialize shims directory, print PYENV_SHELL variable, completions path
and shell function and shell function
--path Print shims path --path Print shims path
--no-push-path Do not push shim to the start of PATH if they're already there
--no-rehash Add no rehash command to output --no-rehash Add no rehash command to output
## `pyenv completions` ## `pyenv completions`

View File

@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Summary: Configure the shell environment for pyenv # Summary: Configure the shell environment for pyenv
# Usage: eval "$(pyenv init [-|--path] [--no-rehash] [<shell>])" # Usage: eval "$(pyenv init [-|--path] [--no-push-path] [--no-rehash] [<shell>])"
set -e set -e
[ -n "$PYENV_DEBUG" ] && set -x [ -n "$PYENV_DEBUG" ] && set -x
@ -9,6 +9,7 @@ set -e
if [ "$1" = "--complete" ]; then if [ "$1" = "--complete" ]; then
echo - echo -
echo --path echo --path
echo --no-push-path
echo --no-rehash echo --no-rehash
echo bash echo bash
echo fish echo fish
@ -19,6 +20,7 @@ fi
mode="help" mode="help"
no_rehash="" no_rehash=""
no_push_path=""
for args in "$@" for args in "$@"
do do
if [ "$args" = "-" ]; then if [ "$args" = "-" ]; then
@ -30,6 +32,11 @@ do
mode="path" mode="path"
shift shift
fi fi
if [ "$args" = "--no-push-path" ]; then
no_push_path=1
shift
fi
if [ "$args" = "--no-rehash" ]; then if [ "$args" = "--no-rehash" ]; then
no_rehash=1 no_rehash=1
@ -141,28 +148,56 @@ function init_dirs() {
} }
function print_path() { function print_path() {
case "$shell" in # if no_push_path is set, guard the PATH manipulation with a check on whether
fish ) # the shim is already in the PATH.
echo 'while set index (contains -i -- '\'"${PYENV_ROOT}/shims"\'' $PATH)' if [ -n "$no_push_path" ]; then
echo 'set -eg PATH[$index]; end; set -e index' case "$shell" in
echo 'set -gx PATH '\'"${PYENV_ROOT}/shims"\'' $PATH' fish )
;; echo 'if not contains -- "'"${PYENV_ROOT}/shims"'" $PATH'
* ) print_path_prepend_shims
# Some distros (notably Debian-based) set Bash's SSH_SOURCE_BASHRC compilation option echo 'end'
# that makes it source `bashrc` under SSH even when not interactive. ;;
# This is inhibited by a guard in Debian's stock `bashrc` but some people remove it * )
# in order to get proper environment for noninteractive remote commands echo 'if [[ ":$PATH:" != *'\':"${PYENV_ROOT}"/shims:\''* ]]; then'
# (SSH provides /etc/ssh/sshrc and ~/.ssh/rc for that but no-one seems to use them for some reason). print_path_prepend_shims
# This has caused an infinite `bashrc` execution loop for those people in the below nested Bash invocation (#2367). echo 'fi'
# --norc negates this behavior of such a customized Bash. ;;
echo 'PATH="$(bash --norc -ec '\''IFS=:; paths=($PATH); ' esac
echo 'for i in ${!paths[@]}; do ' else
echo 'if [[ ${paths[i]} == "'\'\'"${PYENV_ROOT}/shims"\'\''" ]]; then unset '\'\\\'\''paths[i]'\'\\\'\''; ' case "$shell" in
echo 'fi; done; ' fish )
echo 'echo "${paths[*]}"'\'')"' echo 'while set pyenv_index (contains -i -- "'"${PYENV_ROOT}/shims"'" $PATH)'
echo 'export PATH="'"${PYENV_ROOT}"'/shims:${PATH}"' echo 'set -eg PATH[$pyenv_index]; end; set -e pyenv_index'
;; print_path_prepend_shims
esac ;;
* )
# Some distros (notably Debian-based) set Bash's SSH_SOURCE_BASHRC compilation option
# that makes it source `bashrc` under SSH even when not interactive.
# This is inhibited by a guard in Debian's stock `bashrc` but some people remove it
# in order to get proper environment for noninteractive remote commands
# (SSH provides /etc/ssh/sshrc and ~/.ssh/rc for that but no-one seems to use them for some reason).
# This has caused an infinite `bashrc` execution loop for those people in the below nested Bash invocation (#2367).
# --norc negates this behavior of such a customized Bash.
echo 'PATH="$(bash --norc -ec '\''IFS=:; paths=($PATH); '
echo 'for i in ${!paths[@]}; do '
echo 'if [[ ${paths[i]} == "'\'\'"${PYENV_ROOT}/shims"\'\''" ]]; then unset '\'\\\'\''paths[i]'\'\\\'\''; '
echo 'fi; done; '
echo 'echo "${paths[*]}"'\'')"'
print_path_prepend_shims
;;
esac
fi
}
function print_path_prepend_shims() {
case "$shell" in
fish )
echo 'set -gx PATH '\'"${PYENV_ROOT}/shims"\'' $PATH'
;;
* )
echo 'export PATH="'"${PYENV_ROOT}"'/shims:${PATH}"'
;;
esac
} }
function print_env() { function print_env() {

View File

@ -98,7 +98,6 @@ echo "\$PATH"
command -v fish >/dev/null || skip "-- fish not installed" command -v fish >/dev/null || skip "-- fish not installed"
OLDPATH="$PATH" OLDPATH="$PATH"
export PATH="${BATS_TEST_DIRNAME}/nonexistent:${PYENV_ROOT}/shims:$PATH" export PATH="${BATS_TEST_DIRNAME}/nonexistent:${PYENV_ROOT}/shims:$PATH"
# fish 2 (Ubuntu Bionic) adds spurious messages when setting PATH, messing up the output
run fish <<! run fish <<!
set -x PATH "$PATH" set -x PATH "$PATH"
pyenv init - | source pyenv init - | source
@ -108,6 +107,50 @@ echo "\$PATH"
assert_output "${PYENV_ROOT}/shims:${BATS_TEST_DIRNAME}/nonexistent:${OLDPATH//${PYENV_ROOT}\/shims:/}" assert_output "${PYENV_ROOT}/shims:${BATS_TEST_DIRNAME}/nonexistent:${OLDPATH//${PYENV_ROOT}\/shims:/}"
} }
@test "adds shims to PATH with --no-push-path if they're not on PATH" {
export PATH="${BATS_TEST_DIRNAME}/../libexec:/usr/bin:/bin:/usr/local/bin"
run bash -e <<!
eval "\$(pyenv-init - --no-push-path)"
echo "\$PATH"
!
assert_success
assert_output "${PYENV_ROOT}/shims:${PATH}"
}
@test "adds shims to PATH with --no-push-path if they're not on PATH (fish)" {
command -v fish >/dev/null || skip "-- fish not installed"
export PATH="${BATS_TEST_DIRNAME}/../libexec:/usr/bin:/bin:/usr/local/bin"
run fish <<!
set -x PATH "$PATH"
pyenv-init - --no-push-path| source
echo "\$PATH"
!
assert_success
assert_output "${PYENV_ROOT}/shims:${PATH}"
}
@test "doesn't change PATH with --no-push-path if shims are already on PATH" {
export PATH="${BATS_TEST_DIRNAME}/../libexec:${PYENV_ROOT}/shims:/usr/bin:/bin:/usr/local/bin"
run bash -e <<!
eval "\$(pyenv-init - --no-push-path)"
echo "\$PATH"
!
assert_success
assert_output "${PATH}"
}
@test "doesn't change PATH with --no-push-path if shims are already on PATH (fish)" {
command -v fish >/dev/null || skip "-- fish not installed"
export PATH="${BATS_TEST_DIRNAME}/../libexec:/usr/bin:${PYENV_ROOT}/shims:/bin:/usr/local/bin"
run fish <<!
set -x PATH "$PATH"
pyenv-init - --no-push-path| source
echo "\$PATH"
!
assert_success
assert_output "${PATH}"
}
@test "outputs sh-compatible syntax" { @test "outputs sh-compatible syntax" {
run pyenv-init - bash run pyenv-init - bash
assert_success assert_success