From c660839b62173e58af9a02ef4cb7295ff283703f Mon Sep 17 00:00:00 2001 From: Christian Fredrik Johnsen Date: Sun, 22 Dec 2024 12:49:16 +0100 Subject: [PATCH 1/8] fix: add `python -m pip` command to pip hook-script. When doing `pip install ...`, the command is redirected to `pyenv.d/exec/pip-rehash/pip`, which does a `pyenv rehash` after installation. A simple fix to issue #3031 is to make `python -m pip` commands go through the same special hook script. --- pyenv.d/exec/pip-rehash.bash | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyenv.d/exec/pip-rehash.bash b/pyenv.d/exec/pip-rehash.bash index 7080f45d..4cfca0b1 100644 --- a/pyenv.d/exec/pip-rehash.bash +++ b/pyenv.d/exec/pip-rehash.bash @@ -6,6 +6,10 @@ if [[ $PYENV_REHASH_COMMAND =~ ^(pip|easy_install)[23](\.\d)?$ ]]; then PYENV_REHASH_COMMAND="${BASH_REMATCH[1]}" fi +if [[ $1 == "python" && $2 == "-m" && $3 == "pip" ]]; then + PYENV_REHASH_COMMAND="pip" +fi + if [ -x "${PYENV_PIP_REHASH_ROOT}/${PYENV_REHASH_COMMAND}" ]; then PYENV_COMMAND_PATH="${PYENV_PIP_REHASH_ROOT}/${PYENV_REHASH_COMMAND##*/}" PYENV_BIN_PATH="${PYENV_PIP_REHASH_ROOT}" From 372ffab430e1f445245f48be54b8f3686373d03b Mon Sep 17 00:00:00 2001 From: Christian Fredrik Johnsen Date: Sun, 22 Dec 2024 13:40:47 +0100 Subject: [PATCH 2/8] fix: look for `-m` followed by `pip` instead of looking for `python -m pip` It isn't necessarily the case that the command will be python, it can be both python3, python2, python3.12, etc. --- pyenv.d/exec/pip-rehash.bash | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pyenv.d/exec/pip-rehash.bash b/pyenv.d/exec/pip-rehash.bash index 4cfca0b1..79f96721 100644 --- a/pyenv.d/exec/pip-rehash.bash +++ b/pyenv.d/exec/pip-rehash.bash @@ -6,9 +6,13 @@ if [[ $PYENV_REHASH_COMMAND =~ ^(pip|easy_install)[23](\.\d)?$ ]]; then PYENV_REHASH_COMMAND="${BASH_REMATCH[1]}" fi -if [[ $1 == "python" && $2 == "-m" && $3 == "pip" ]]; then - PYENV_REHASH_COMMAND="pip" -fi +for (( i=1; i<$#; i++ )); do + next=$((i+1)) + if [[ ${!i} == "-m" && ${!next} == "pip" ]]; then + PYENV_REHASH_COMMAND="pip" + break + fi +done if [ -x "${PYENV_PIP_REHASH_ROOT}/${PYENV_REHASH_COMMAND}" ]; then PYENV_COMMAND_PATH="${PYENV_PIP_REHASH_ROOT}/${PYENV_REHASH_COMMAND##*/}" From b154f3a7d8319cd79ef08f038e6fc83d00f5c6d5 Mon Sep 17 00:00:00 2001 From: Christian Fredrik Johnsen Date: Fri, 27 Dec 2024 21:19:59 +0100 Subject: [PATCH 3/8] refactor: use regex matching instead of for loop Instead of creating a for-loop where we go through each argument, to check if current argument is -m and next argument is pip, we instead do simple regex matching where we look for ` -m pip ` somewhere in the command which is to be executed. --- pyenv.d/exec/pip-rehash.bash | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/pyenv.d/exec/pip-rehash.bash b/pyenv.d/exec/pip-rehash.bash index 79f96721..f3687566 100644 --- a/pyenv.d/exec/pip-rehash.bash +++ b/pyenv.d/exec/pip-rehash.bash @@ -6,13 +6,10 @@ if [[ $PYENV_REHASH_COMMAND =~ ^(pip|easy_install)[23](\.\d)?$ ]]; then PYENV_REHASH_COMMAND="${BASH_REMATCH[1]}" fi -for (( i=1; i<$#; i++ )); do - next=$((i+1)) - if [[ ${!i} == "-m" && ${!next} == "pip" ]]; then - PYENV_REHASH_COMMAND="pip" - break - fi -done +# Check for ` -m pip ` in arguments +if [[ "$*" =~ [[:space:]]-m[[:space:]]pip[[:space:]] ]]; then + PYENV_REHASH_COMMAND="pip" +fi if [ -x "${PYENV_PIP_REHASH_ROOT}/${PYENV_REHASH_COMMAND}" ]; then PYENV_COMMAND_PATH="${PYENV_PIP_REHASH_ROOT}/${PYENV_REHASH_COMMAND##*/}" From 9e2f5082aea114f7afe525e0c450acfdf090aeb7 Mon Sep 17 00:00:00 2001 From: Christian Fredrik Johnsen Date: Fri, 27 Dec 2024 21:22:02 +0100 Subject: [PATCH 4/8] feat: add support for isolated environment testing In order to properly test the rehashing after `pip install` and `python -m pip install`, we need to add support for creating an isolated environment. --- test/test_helper.bash | 44 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/test/test_helper.bash b/test/test_helper.bash index 51dcd83d..f4f53995 100644 --- a/test/test_helper.bash +++ b/test/test_helper.bash @@ -18,21 +18,47 @@ if [ -z "$PYENV_TEST_DIR" ]; then export PYENV_ROOT="${PYENV_TEST_DIR}/root" export HOME="${PYENV_TEST_DIR}/home" export PYENV_HOOK_PATH="${PYENV_ROOT}/pyenv.d" + PYENV_LIBEXEC="${BATS_TEST_DIRNAME%/*}/libexec" - PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin - PATH="${PYENV_TEST_DIR}/bin:$PATH" - PATH="${BATS_TEST_DIRNAME%/*}/libexec:$PATH" - PATH="${BATS_TEST_DIRNAME}/libexec:$PATH" - PATH="${PYENV_ROOT}/shims:$PATH" - export PATH + if [ -n "$ISOLATED_ENVIRONMENT" ]; then + mkdir -p "$PYENV_ROOT" "$HOME" "$PYENV_HOOK_PATH" \ + "$PYENV_TEST_DIR"/bin "$PYENV_TEST_DIR"/plugins/python-build + + cp -r "$PYENV_LIBEXEC" "$PYENV_TEST_DIR" + cp -r "${BATS_TEST_DIRNAME%/*}/plugins/python-build" "$PYENV_TEST_DIR"/plugins/ + ln -s "$PYENV_TEST_DIR/libexec/pyenv" "$PYENV_TEST_DIR/bin/pyenv" + # cp -rL "${BATS_TEST_DIRNAME%/*}/bin" "$PYENV_TEST_DIR" + cp -r "${BATS_TEST_DIRNAME%/*}/completions" "$PYENV_TEST_DIR" + cp -r "${BATS_TEST_DIRNAME%/*}/pyenv.d" "$PYENV_TEST_DIR" + PATH="${PYENV_TEST_DIR}/bin:/usr/bin" + export PATH + + else + PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin + PATH="${PYENV_TEST_DIR}/bin:$PATH" + PATH="$PYENV_LIBEXEC:$PATH" + PATH="${BATS_TEST_DIRNAME}/libexec:$PATH" + PATH="${PYENV_ROOT}/shims:$PATH" + export PATH + fi for xdg_var in `env 2>/dev/null | grep ^XDG_ | cut -d= -f1`; do unset "$xdg_var"; done unset xdg_var fi -teardown() { - rm -rf "$PYENV_TEST_DIR" -} +# We don't want to remove the test directory between +# tests if we are running in an isolated environment. +# We want to set up the isolated environment once and +# delete it at after all tests are run. +if [ -n "$ISOLATED_ENVIRONMENT" ]; then + teardown_file() { + rm -rf "$PYENV_TEST_DIR" + } +else + teardown() { + rm -rf "$PYENV_TEST_DIR" + } +fi flunk() { { if [ "$#" -eq 0 ]; then cat - From 2cc568c161ee2d74aec0ce71ec9a91ffc61d3009 Mon Sep 17 00:00:00 2001 From: Christian Fredrik Johnsen Date: Sat, 28 Dec 2024 12:37:38 +0100 Subject: [PATCH 5/8] test: add tests for automatic rehash after pip install Using the brand new isolated_environment functionality in test_helper.bash to set up a fresh and clean environment where we can use pyenv to install a clean python version, and then check whether we are getting an automatic rehash after pip install. We are checking: - pip - pip3 - pip3.12 - python -m pip - python3 -m pip - python3.12 -m pip for automatic rehash. --- test/pip-rehash.bats | 132 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100755 test/pip-rehash.bats diff --git a/test/pip-rehash.bats b/test/pip-rehash.bats new file mode 100755 index 00000000..b617289e --- /dev/null +++ b/test/pip-rehash.bats @@ -0,0 +1,132 @@ +#!/usr/bin/env bats + +# Test the automatic rehashing after doing a pip install. + +# Tell test_helper.bash to create an isolated environment. +export ISOLATED_ENVIRONMENT=1 +load test_helper + +# Run once before all tests. +# Sets up a fresh environment for testing. +setup_file() { + eval "$(pyenv init -)" + assert_success + + run pyenv install 3.12.8 + assert_success + + pyenv global 3.12.8 + assert_success + + # Add a dummy executable in case the computer running + # the tests has black installed in system python. + echo -e "#!/bin/bash\nexit 1" > "${PYENV_TEST_DIR}/bin/black" + chmod +x "${PYENV_TEST_DIR}/bin/black" +} + +@test "auto rehash on pip install" { + # 1) Confirm that black is not found yet + run black --version + assert_failure + + # 2) Install black using pip + run pip install black + assert_success + + # 3) Confirm that black is found after install (i.e. rehash happened) + run black --version + assert_success + + # 4) Uninstall black using pip + run pip uninstall black -y + assert_success + + # 5) Confirm that black is not found after uninstall + run black --version + assert_failure +} + +@test "auto rehash on pip3 install" { + run black --version + assert_failure + + run pip3 install black + assert_success + + run black --version + assert_success + + run pip3 uninstall black -y + assert_success + + run black --version + assert_failure +} + +@test "auto rehash on pip3.12 install" { + run black --version + assert_failure + + run pip3.12 install black + assert_success + + run black --version + assert_success + + run pip3.12 uninstall black -y + assert_success + + run black --version + assert_failure +} + +@test "auto rehash on python -m pip install" { + run black --version + assert_failure + + run python -m pip install black + assert_success + + run black --version + assert_success + + run python -m pip uninstall black -y + assert_success + + run black --version + assert_failure +} + +@test "auto rehash on python3 -m pip install" { + run black --version + assert_failure + + run python3 -m pip install black + assert_success + + run black --version + assert_success + + run python3 -m pip uninstall black -y + assert_success + + run black --version + assert_failure +} + +@test "auto rehash on python3.12 -m pip install" { + run black --version + assert_failure + + run python3.12 -m pip install black + assert_success + + run black --version + assert_success + + run python3.12 -m pip uninstall black -y + assert_success + + run black --version + assert_failure +} From 6112062fdd57d84330def5e6f8a5e1634a019fd0 Mon Sep 17 00:00:00 2001 From: Christian Fredrik Johnsen Date: Sat, 28 Dec 2024 12:41:09 +0100 Subject: [PATCH 6/8] fix: add automatic rehash after pip3.x/pip3.xx install The tests showed that the original implementation actually never worked. You did NOT get a rehash after doing pip3.8 install, even when the comments in the code said so. --> replaced `\d` with [0-9] and slapped on a `+`, such that we match both pip3.x and `pip3.xx install` --- pyenv.d/exec/pip-rehash.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyenv.d/exec/pip-rehash.bash b/pyenv.d/exec/pip-rehash.bash index f3687566..a90317fa 100644 --- a/pyenv.d/exec/pip-rehash.bash +++ b/pyenv.d/exec/pip-rehash.bash @@ -2,7 +2,7 @@ PYENV_PIP_REHASH_ROOT="${BASH_SOURCE[0]%/*}/pip-rehash" PYENV_REHASH_COMMAND="${PYENV_COMMAND##*/}" # Remove any version information, from e.g. "pip2" or "pip3.4". -if [[ $PYENV_REHASH_COMMAND =~ ^(pip|easy_install)[23](\.\d)?$ ]]; then +if [[ $PYENV_REHASH_COMMAND =~ ^(pip|easy_install)[23](\.[0-9]+)?$ ]]; then PYENV_REHASH_COMMAND="${BASH_REMATCH[1]}" fi From 4017615af38bf7c1e00ca906c20e0479309a2a85 Mon Sep 17 00:00:00 2001 From: Christian Fredrik Johnsen Date: Sat, 28 Dec 2024 12:47:53 +0100 Subject: [PATCH 7/8] refactor: use elif when checking for ` -m pip ` in command to be executed. This is a logical change. If the command is `pip install ...`, then you don't need to look for `python -m pip install ...` in the same command. Either you have `pip install ...` or `python -m pip install ...` or something completely different. --- pyenv.d/exec/pip-rehash.bash | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyenv.d/exec/pip-rehash.bash b/pyenv.d/exec/pip-rehash.bash index a90317fa..02a90a56 100644 --- a/pyenv.d/exec/pip-rehash.bash +++ b/pyenv.d/exec/pip-rehash.bash @@ -4,10 +4,9 @@ PYENV_REHASH_COMMAND="${PYENV_COMMAND##*/}" # Remove any version information, from e.g. "pip2" or "pip3.4". if [[ $PYENV_REHASH_COMMAND =~ ^(pip|easy_install)[23](\.[0-9]+)?$ ]]; then PYENV_REHASH_COMMAND="${BASH_REMATCH[1]}" -fi # Check for ` -m pip ` in arguments -if [[ "$*" =~ [[:space:]]-m[[:space:]]pip[[:space:]] ]]; then +elif [[ "$*" =~ [[:space:]]-m[[:space:]]pip[[:space:]] ]]; then PYENV_REHASH_COMMAND="pip" fi From 0e200e714c9d870f1551e9fa28220e6a3afb867f Mon Sep 17 00:00:00 2001 From: Christian Fredrik Johnsen Date: Sun, 19 Jan 2025 23:57:09 +0100 Subject: [PATCH 8/8] feat: add automatic rehash on `uv pip install/uninstall`. --- pyenv.d/exec/pip-rehash.bash | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyenv.d/exec/pip-rehash.bash b/pyenv.d/exec/pip-rehash.bash index 02a90a56..3b077852 100644 --- a/pyenv.d/exec/pip-rehash.bash +++ b/pyenv.d/exec/pip-rehash.bash @@ -5,6 +5,10 @@ PYENV_REHASH_COMMAND="${PYENV_COMMAND##*/}" if [[ $PYENV_REHASH_COMMAND =~ ^(pip|easy_install)[23](\.[0-9]+)?$ ]]; then PYENV_REHASH_COMMAND="${BASH_REMATCH[1]}" +# Check for `uv pip ` in arguments +elif [[ "$*" =~ uv[[:space:]]pip[[:space:]] ]]; then + PYENV_REHASH_COMMAND="pip" + # Check for ` -m pip ` in arguments elif [[ "$*" =~ [[:space:]]-m[[:space:]]pip[[:space:]] ]]; then PYENV_REHASH_COMMAND="pip"