From cc56f76733c8f57ccd1f3d2f7110221351e5fc89 Mon Sep 17 00:00:00 2001 From: Isaac Levy Date: Fri, 18 Nov 2022 16:01:59 -0500 Subject: [PATCH] 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. --- COMMANDS.md | 3 +- libexec/pyenv-init | 81 +++++++++++++++++++++++++++++++++------------- test/init.bats | 45 +++++++++++++++++++++++++- 3 files changed, 104 insertions(+), 25 deletions(-) diff --git a/COMMANDS.md b/COMMANDS.md index 5d52e13f..203250d4 100644 --- a/COMMANDS.md +++ b/COMMANDS.md @@ -386,11 +386,12 @@ List existing pyenv shims. Configure the shell environment for pyenv - Usage: eval "$(pyenv init [-|--path] [--no-rehash] [])" + Usage: eval "$(pyenv init [-|--path] [--no-push-path] [--no-rehash] [])" - Initialize shims directory, print PYENV_SHELL variable, completions path and shell function --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 ## `pyenv completions` diff --git a/libexec/pyenv-init b/libexec/pyenv-init index 24ef3b16..7d222377 100755 --- a/libexec/pyenv-init +++ b/libexec/pyenv-init @@ -1,6 +1,6 @@ #!/usr/bin/env bash # Summary: Configure the shell environment for pyenv -# Usage: eval "$(pyenv init [-|--path] [--no-rehash] [])" +# Usage: eval "$(pyenv init [-|--path] [--no-push-path] [--no-rehash] [])" set -e [ -n "$PYENV_DEBUG" ] && set -x @@ -9,6 +9,7 @@ set -e if [ "$1" = "--complete" ]; then echo - echo --path + echo --no-push-path echo --no-rehash echo bash echo fish @@ -19,6 +20,7 @@ fi mode="help" no_rehash="" +no_push_path="" for args in "$@" do if [ "$args" = "-" ]; then @@ -30,6 +32,11 @@ do mode="path" shift fi + + if [ "$args" = "--no-push-path" ]; then + no_push_path=1 + shift + fi if [ "$args" = "--no-rehash" ]; then no_rehash=1 @@ -141,28 +148,56 @@ function init_dirs() { } function print_path() { - case "$shell" in - fish ) - echo 'while set index (contains -i -- '\'"${PYENV_ROOT}/shims"\'' $PATH)' - echo 'set -eg PATH[$index]; end; set -e index' - echo 'set -gx PATH '\'"${PYENV_ROOT}/shims"\'' $PATH' - ;; - * ) - # 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[*]}"'\'')"' - echo 'export PATH="'"${PYENV_ROOT}"'/shims:${PATH}"' - ;; - esac + # if no_push_path is set, guard the PATH manipulation with a check on whether + # the shim is already in the PATH. + if [ -n "$no_push_path" ]; then + case "$shell" in + fish ) + echo 'if not contains -- "'"${PYENV_ROOT}/shims"'" $PATH' + print_path_prepend_shims + echo 'end' + ;; + * ) + echo 'if [[ ":$PATH:" != *'\':"${PYENV_ROOT}"/shims:\''* ]]; then' + print_path_prepend_shims + echo 'fi' + ;; + esac + else + case "$shell" in + fish ) + echo 'while set pyenv_index (contains -i -- "'"${PYENV_ROOT}/shims"'" $PATH)' + echo 'set -eg PATH[$pyenv_index]; end; set -e pyenv_index' + print_path_prepend_shims + ;; + * ) + # 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() { diff --git a/test/init.bats b/test/init.bats index 154d340b..bfe253e3 100755 --- a/test/init.bats +++ b/test/init.bats @@ -98,7 +98,6 @@ echo "\$PATH" command -v fish >/dev/null || skip "-- fish not installed" OLDPATH="$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 </dev/null || skip "-- fish not installed" + export PATH="${BATS_TEST_DIRNAME}/../libexec:/usr/bin:/bin:/usr/local/bin" + run fish </dev/null || skip "-- fish not installed" + export PATH="${BATS_TEST_DIRNAME}/../libexec:/usr/bin:${PYENV_ROOT}/shims:/bin:/usr/local/bin" + run fish <