Skip to content

Commit dc7409e

Browse files
tobnijsirois
andauthored
Make ephemeral exec, venv repl and scie compatible (#3129)
Fix so that scie pex (zipapp, venv) can 1. drop into repl and 2. run in ephemeral run mode via `-- -c`. ```shell #!/usr/bin/env bash set -euo pipefail TMPDIR=$(mktemp -d) PEX_ROOT="$TMPDIR/pex_root" uv run python -m pex . -c pex -o "$TMPDIR/pex.pex" --scie eager --venv --pex-root "$PEX_ROOT" --runtime-pex-root "$PEX_ROOT" 2>&1 | tail -1 echo "=== Venv scie: -- -c ===" "$TMPDIR/pex" --pex-root "$PEX_ROOT" --runtime-pex-root "$PEX_ROOT" --interpreter-constraint 'CPython>=3.12' -- -c 'import sys; print(sys.executable)' 2>&1 echo "EXIT: $?" echo "" echo "=== Venv scie: REPL ===" echo 'import sys; print("REPL:", sys.executable); quit()' | "$TMPDIR/pex" --pex-root "$PEX_ROOT" --runtime-pex-root "$PEX_ROOT" --interpreter-constraint 'CPython>=3.12' -- 2>&1 echo "EXIT: $?" echo "" echo "=== Non-venv scie: -- -c ===" TMPDIR2=$(mktemp -d) uv run python -m pex . -c pex -o "$TMPDIR2/pex.pex" --scie eager 2>&1 | tail -1 "$TMPDIR2/pex" --interpreter-constraint 'CPython>=3.12' -- -c 'import sys; print(sys.executable)' 2>&1 echo "EXIT: $?" ``` This gives me on branch: ```console (pex-dev) ➜ pex git:(fix/scie-ephemeral-run-mode) ✗ bash test_scie_ephemeral.sh /tmp/tmp.Uj3pbC1P18/pex === Venv scie: -- -c === /tmp/tmp.Uj3pbC1P18/pex_root/scies/0/base/86232383051a0950a74a9486949c87f88da45978bafa8d826c2f268629a3f508/cpython-3.12.13+20260325-x86_64-unknown-linux-gnu-install_only.tar.gz/python/bin/python3.12 EXIT: 0 === Venv scie: REPL === Pex 2.91.4 ephemeral hermetic environment with no dependencies. Python 3.12.13 (main, Mar 24 2026, 22:49:22) [Clang 22.1.1 ] on linux Type "help", "pex", "copyright", "credits" or "license" for more information. >>> REPL: /tmp/tmp.Uj3pbC1P18/pex_root/scies/0/base/86232383051a0950a74a9486949c87f88da45978bafa8d826c2f268629a3f508/cpython-3.12.13+20260325-x86_64-unknown-linux-gnu-install_only.tar.gz/python/bin/python3.12 EXIT: 0 === Non-venv scie: -- -c === /tmp/tmp.3ibEcHDvMR/pex /home/tobias/.cache/nce/86232383051a0950a74a9486949c87f88da45978bafa8d826c2f268629a3f508/cpython-3.12.13+20260325-x86_64-unknown-linux-gnu-install_only.tar.gz/python/bin/python3.12 EXIT: 0 ``` On main ephemeral exec and venv+repl fail in different ways: ```console (pex-dev) ➜ pex git:(main) ✗ bash test_scie_ephemeral.sh /tmp/tmp.tPRXEL8YLa/pex === Venv scie: -- -c === Ignoring the following environment variables in Pex venv mode: _PEX_CLI_RUN=/tmp/tmp.tPRXEL8YLa/pex __PEX_ENTRY_POINT__=/tmp/tmp.tPRXEL8YLa/pex_root/venvs/3/d806b12a09b8ad5281bf3d990cb91bfb694c1be1/414e2ae92b4573e3049bcfe135ed1296986a612d Could not find script 'import sys; print(sys.executable)' in any distribution within PEX! EXIT: 1 === Venv scie: REPL === Ignoring the following environment variables in Pex venv mode: _PEX_CLI_RUN=/tmp/tmp.tPRXEL8YLa/pex __PEX_ENTRY_POINT__=/tmp/tmp.tPRXEL8YLa/pex_root/venvs/3/d806b12a09b8ad5281bf3d990cb91bfb694c1be1/414e2ae92b4573e3049bcfe135ed1296986a612d Ignoring the following environment variables in Pex venv mode: _PEX_CLI_RUN=/tmp/tmp.tPRXEL8YLa/pex _PEX_CLI_RUN_NO_ARGS=/tmp/tmp.tPRXEL8YLa/pex __PEX_ENTRY_POINT__=/tmp/tmp.tPRXEL8YLa/pex_root/venvs/3/d806b12a09b8ad5281bf3d990cb91bfb694c1be1/414e2ae92b4573e3049bcfe135ed1296986a612d ... # this loops forever === Non-venv scie: -- -c === /tmp/tmp.seG0un8tmn/pex Could not find script 'import sys; print(sys.executable)' in any distribution within PEX! EXIT: 1 ``` --------- Co-authored-by: John Sirois <john.sirois@gmail.com>
1 parent 013a078 commit dc7409e

File tree

6 files changed

+84
-6
lines changed

6 files changed

+84
-6
lines changed

CHANGES.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Release Notes
22

3+
## 2.91.5
4+
5+
This release fixes Pex PEX scie behavior to match Pex.
6+
7+
* Make ephemeral exec, venv repl and scie compatible (#3129)
8+
39
## 2.91.4
410

511
This release brings 2 performance fixes from @tobni:

pex/bin/pex.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1289,6 +1289,11 @@ def main(args=None):
12891289

12901290
try:
12911291
with global_environment(options) as env:
1292+
# These are set if we're running from within a Pex PEX scie and already installed.
1293+
# We clear them to allow recursive use of Pex / PEX.
1294+
env.pop("__PEX_EXE__", None)
1295+
env.pop("__PEX_ENTRY_POINT__", None)
1296+
12921297
try:
12931298
resolver_configuration = resolver_options.configure(
12941299
options,

pex/pex_boot.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -229,12 +229,11 @@ def boot(
229229
if os.path.isfile(pex_exe):
230230
sys.argv[0] = pex_exe
231231

232-
overridden_entry_point = os.environ.get("__PEX_ENTRY_POINT__", None)
232+
overridden_pex = os.environ.get("__PEX_EXE__", None)
233233
sys.path[0] = os.path.abspath(sys.path[0])
234-
sys.path.insert(
235-
0, os.path.abspath(os.path.join(overridden_entry_point or entry_point, bootstrap_dir))
236-
)
234+
sys.path.insert(0, os.path.abspath(os.path.join(overridden_pex or entry_point, bootstrap_dir)))
237235

236+
overridden_entry_point = os.environ.get("__PEX_ENTRY_POINT__", None)
238237
if overridden_entry_point and overridden_entry_point != entry_point:
239238
# This PEX has already been installed out of band; so we short-circuit to execute the
240239
# pre-installed PEX.

pex/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# Copyright 2015 Pex project contributors.
22
# Licensed under the Apache License, Version 2.0 (see LICENSE).
33

4-
__version__ = "2.91.4"
4+
__version__ = "2.91.5"

tests/integration/cli/commands/test_lock_subset.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ def test_lock_subset_miss(lock):
139139
# type: (str) -> None
140140

141141
_, original_locked_reqs = index(lock)
142-
requests_version = original_locked_reqs[ProjectName("requests")].pin.version
142+
requests_version = original_locked_reqs[ProjectName("requests")].pin.version.raw
143143
run_pex3(
144144
"lock", "subset", "--lock", lock, "requests!={version}".format(version=requests_version)
145145
).assert_failure(
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Copyright 2026 Pex project contributors.
2+
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3+
4+
from __future__ import absolute_import
5+
6+
import subprocess
7+
import sys
8+
9+
import pytest
10+
11+
from pex.typing import TYPE_CHECKING
12+
from testing import IS_PYPY, make_env, run_pex_command
13+
from testing.pytest_utils.tmp import Tempdir
14+
from testing.scie import skip_if_no_provider
15+
16+
if TYPE_CHECKING:
17+
from typing import List
18+
19+
20+
@pytest.mark.parametrize(
21+
"execution_mode_args",
22+
[
23+
pytest.param([], id="ZIPAPP"),
24+
pytest.param(["--venv"], id="VENV"),
25+
],
26+
)
27+
@skip_if_no_provider
28+
def test_scie_ephemeral_run(
29+
tmpdir, # type: Tempdir
30+
pex_wheel, # type: str
31+
execution_mode_args, # type: List[str]
32+
):
33+
# type: (...) -> None
34+
35+
pex_scie = tmpdir.join("pex")
36+
run_pex_command(
37+
args=[pex_wheel, "-c", "pex", "-o", pex_scie, "--scie", "eager"] + execution_mode_args
38+
).assert_success()
39+
40+
ic = "{impl}=={major}.{minor}.*".format(
41+
impl="PyPy" if IS_PYPY else "CPython", major=sys.version_info[0], minor=sys.version_info[1]
42+
)
43+
44+
# Verify the scie can perform an ephemeral run with `-- -c`.
45+
output = subprocess.check_output(
46+
args=[
47+
pex_scie,
48+
"--interpreter-constraint",
49+
ic,
50+
"--",
51+
"-c",
52+
"import sys; print(sys.executable)",
53+
],
54+
env=make_env(PATH=None),
55+
)
56+
assert output.decode("utf-8").strip()
57+
58+
# Verify the scie can drop into a REPL via ephemeral run.
59+
process = subprocess.Popen(
60+
args=[pex_scie, "--interpreter-constraint", ic, "--"],
61+
stdin=subprocess.PIPE,
62+
stdout=subprocess.PIPE,
63+
stderr=subprocess.PIPE,
64+
env=make_env(PATH=None),
65+
)
66+
stdout, stderr = process.communicate(input=b"import sys; print(sys.executable)\nquit()\n")
67+
assert process.returncode == 0, stderr.decode("utf-8")
68+
assert b">>>" in stdout or b">>>" in stderr

0 commit comments

Comments
 (0)