pyenv-exec: add the --environment option for programs that embed libpython without a path (#3537)

---------

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Co-authored-by: native-api <vano@mail.mipt.ru>
This commit is contained in:
Michał 2026-10-02 10:17:30 +02:00 committed by GitHub
parent 6ff25c51d3
commit f8b1d7ae09
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 105 additions and 3 deletions

View File

@ -315,7 +315,7 @@ Lists all Python versions with the given command installed.
## `pyenv exec`
Usage: pyenv exec <command> [arg1 arg2...]
Usage: pyenv exec [-N|--environment] <command> [arg1 arg2...]
Runs an executable by first preparing PATH so that the selected Python
version's `bin` directory is at the front.
@ -326,7 +326,18 @@ For example, if the currently selected Python version is 3.9.7:
is equivalent to:
PATH="$PYENV_ROOT/versions/3.9.7/bin:$PATH" pip install -r requirements.txt
PYENV_VERSION="3.9.7" PATH="$PYENV_ROOT/versions/3.9.7/bin:$PATH" pip install -r requirements.txt
The `--environment` option sets `PYTHONHOME` and `LD_LIBRARY_PATH` (`DYLD_LIBRARY_PATH` in macOS) in the program's environment.
This is needed to run programs that embed Python by loading `libpython` without specifying rpath or an explicit path.
This breaks linking for programs that expect to link to a different `libpython` with the same name as one
in selected Python -- thus it's not done by default.
In macOS, System Integrity Protection (SIP) removes `DYLD_LIBRARY_PATH` from a process' environment
upon `exec` (not `fork`) if the executable being run is protected. The protection covers preinstalled system software
(including preinstalled shells) and does not cover user-installed apps and 3rd-party software. See
[About System Integrity Protection on your Mac - Apple Support](https://support.apple.com/en-us/102149)
for details.
## `pyenv root`

View File

@ -2,7 +2,17 @@
#
# Summary: Run an executable with the selected Python version
#
# Usage: pyenv exec <command> [arg1 arg2...]
# Usage: pyenv exec [-N|--environment] <command> [arg1 arg2...]
#
# -N/--environment Set PYTHONHOME and LD_LIBRARY_PATH (DYLD_LIBRARY_PATH in macOS)
# envvars for the running command.
# This allows to run programs that embed Python without using rpath or
# exact library path to libpython.
# WARNING: For the running command and its child processes, this will
# break linkage for programs that expect to be linked to a different
# libpython instance with the same name!
# WARNING: In macOS, DYLD_LIBRARY_PATH will be unset by the system for
# processes covered by System Integrity Protection.
#
# Runs an executable by first preparing PATH so that the selected Python
# version's `bin' directory is at the front.
@ -18,9 +28,16 @@ set -e
# Provide pyenv completions
if [ "$1" = "--complete" ]; then
echo --environment
exec pyenv-shims --short
fi
unset MODIFY_ENV
if [ "$1" = "-N" ] || [ "$1" = "--environment" ]; then
MODIFY_ENV=true
shift
fi
PYENV_VERSION="$(pyenv-version-name -f)"
PYENV_COMMAND="$1"
@ -54,4 +71,27 @@ if [ "${PYENV_BIN_PATH#${PYENV_ROOT}}" != "${PYENV_BIN_PATH}" ]; then
# Only add to $PATH for non-system version.
export PATH="${PYENV_BIN_PATH}:${PATH}"
fi
if [ -n "$MODIFY_ENV" ]; then
# Check if multiple Python versions are selected
VERSIONS=$(pyenv-prefix)
if [[ "$VERSIONS" == *:* ]]; then
VERSION="${VERSIONS%%:*}"
echo "pyenv: Warning: multiple Python versions are selected." \
"Setting environment variables for the first one, (${VERSION#$PYENV_ROOT})" >&2
else
VERSION="$VERSIONS"
fi
# Assign the correct version of PYTHONHOME
export PYTHONHOME="$VERSION"
case "$(uname -s)" in
Darwin)
export DYLD_LIBRARY_PATH="${PYTHONHOME}/lib${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}}"
;;
*)
export LD_LIBRARY_PATH="${PYTHONHOME}/lib${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}"
;;
esac
fi
exec "$PYENV_COMMAND_PATH" "$@"

View File

@ -34,6 +34,7 @@ EOF
assert_success
assert_output <<OUT
--help
--environment
fab
python
OUT
@ -139,3 +140,53 @@ $envvarname=/unusual/shim/location:/another/shim/location
_PYENV_SHIM_PATH=
!
}
@test "--environment sets envvars in Linux" {
export PYENV_VERSION="test"
create_alt_executable print_env <<!
#!$BASH
echo PYTHONHOME="\$PYTHONHOME"
echo LD_LIBRARY_PATH="\$LD_LIBRARY_PATH"
!
create_path_executable uname "echo Linux"
LD_LIBRARY_PATH= run pyenv-exec -N print_env
assert_output_glob <<!
PYTHONHOME=*/test
LD_LIBRARY_PATH=*/test/lib
!
LD_LIBRARY_PATH=/foo/bar run pyenv-exec -N print_env
assert_output_glob <<!
PYTHONHOME=*/test
LD_LIBRARY_PATH=*/test/lib:/foo/bar
!
}
@test "--environment sets envvars in macOS" {
if [[ $(uname -s) == "Darwin" && $BASH == "/bin/bash" \
&& $(LC_ALL=C csrutil status) == *"enabled." ]]; then
skip "macOS with Bash covered by SIP"
fi
export PYENV_VERSION="test"
create_alt_executable print_env <<!
#!$BASH
echo PYTHONHOME="\$PYTHONHOME"
echo DYLD_LIBRARY_PATH="\$DYLD_LIBRARY_PATH"
!
create_path_executable uname "echo Darwin"
DYLD_LIBRARY_PATH= run pyenv-exec -N print_env
assert_output_glob <<!
PYTHONHOME=*/test
DYLD_LIBRARY_PATH=*/test/lib
!
DYLD_LIBRARY_PATH=/foo/bar run pyenv-exec -N print_env
assert_output_glob <<!
PYTHONHOME=*/test
DYLD_LIBRARY_PATH=*/test/lib:/foo/bar
!
}