diff --git a/CHANGES.md b/CHANGES.md index bab31cd29..11f98e170 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,12 @@ # Release Notes +## 2.77.1 + +This release fixes a very old bug where the Pex PEX (or any other PEX created with +`--no-strip-pex-env`) would, in fact, strip `PEX_PYTHON` and `PEX_PYTHON_PATH`. + +* Fix `PEX_PYTHON{,_PATH}` stripping on Pex re-exec. (#3063) + ## 2.77.0 This release has no fixes or new features per-se, but just changes the set of distributions that diff --git a/pex/pex.py b/pex/pex.py index 2a86c3ca5..287b32876 100644 --- a/pex/pex.py +++ b/pex/pex.py @@ -149,11 +149,12 @@ class InvalidEntryPoint(Error): @classmethod def _clean_environment(cls, env=None, strip_pex_env=True): + if not strip_pex_env: + return env = env or os.environ - if strip_pex_env: - for key in list(env): - if key.startswith("PEX_"): - del env[key] + for key in list(env): + if key and key.startswith("PEX_"): + del env[key] def __init__( self, @@ -838,7 +839,7 @@ def run(self, args=(), with_chroot=False, blocking=True, setsid=False, env=None, env = env.copy() else: env = os.environ.copy() - self._clean_environment(env=env) + self._clean_environment(env=env, strip_pex_env=self._pex_info.strip_pex_env) kwargs = dict(subprocess_daemon_kwargs() if setsid else {}, **kwargs) diff --git a/pex/pex_bootstrapper.py b/pex/pex_bootstrapper.py index 8b92ae3ae..35dfbb69d 100644 --- a/pex/pex_bootstrapper.py +++ b/pex/pex_bootstrapper.py @@ -401,9 +401,6 @@ def maybe_reexec_pex( except UnsatisfiableInterpreterConstraintsError as e: die(str(e)) - os.environ.pop("PEX_PYTHON", None) - os.environ.pop("PEX_PYTHON_PATH", None) - if interpreter_test.pex_info.inherit_path == InheritPath.FALSE: # Now that we've found a compatible Python interpreter, make sure we resolve out of any # virtual environments it may be contained in since virtual environments created with diff --git a/pex/version.py b/pex/version.py index 9b1e2e40a..ab71a37e4 100644 --- a/pex/version.py +++ b/pex/version.py @@ -1,4 +1,4 @@ # Copyright 2015 Pex project contributors. # Licensed under the Apache License, Version 2.0 (see LICENSE). -__version__ = "2.77.0" +__version__ = "2.77.1" diff --git a/tests/integration/test_issue_1075.py b/tests/integration/test_issue_1075.py new file mode 100644 index 000000000..286b97b71 --- /dev/null +++ b/tests/integration/test_issue_1075.py @@ -0,0 +1,59 @@ +# Copyright 2026 Pex project contributors. +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +from __future__ import absolute_import + +import os.path +import subprocess + +from pex.interpreter import PythonInterpreter +from pex.os import is_exe +from pex.venv.virtualenv import Virtualenv +from testing import make_env +from testing.pytest_utils.tmp import Tempdir + + +def test_pex_pex_pex_python_path( + tmpdir, # type: Tempdir + py310, # type: PythonInterpreter + py311, # type: PythonInterpreter +): + # type: (...) -> None + + dist_dir = tmpdir.join("dist") + + # The package command can be slow to run which locks up uv; so we just ensure a synced + # uv venv (fast), then run the dev-cmd console script directly to avoid uv lock + # timeouts in CI. + subprocess.check_call(args=["uv", "sync", "--frozen"]) + subprocess.check_call( + args=[Virtualenv(".venv").bin_path("dev-cmd"), "package", "--", "--dist-dir", dist_dir] + ) + pex_pex = os.path.join(dist_dir, "pex") + assert is_exe(pex_pex) + + def assert_python_selected( + expected_python, # type: str + **env # type: str + ): + # type: (...) -> None + + assert ( + os.path.realpath(expected_python) + == subprocess.check_output( + args=[ + pex_pex, + "--interpreter-constraint", + ">=3.10,<3.12", + "--", + "-c", + "import sys, os; print(os.path.realpath(sys.executable))", + ], + env=make_env(**env), + ) + .decode("utf-8") + .strip() + ) + + assert_python_selected(py310.binary, PEX_PYTHON_PATH=py310.binary) + assert_python_selected(py311.binary, PEX_PYTHON_PATH=py311.binary)