Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Honor interpreter constraints even when PEX_PYTHON and PEX_PYTHON_PATH not set #668

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions pex/pex_bootstrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,14 @@ def maybe_reexec_pex(compatibility_constraints):
interpreter specified by PEX_PYTHON. If PEX_PYTHON_PATH is set, it attempts to search the path for
a matching interpreter in accordance with the interpreter constraints. If both variables are
present in a pexrc, this function gives precedence to PEX_PYTHON_PATH and errors out if no
compatible interpreters can be found on said path. If neither variable is set, fall through to
plain pex execution using PATH searching or the currently executing interpreter.
compatible interpreters can be found on said path.

If neither variable is set, we fall back to plain PEX execution using PATH searching or the
currently executing interpreter. If compatibility constraints are used, we match those constraints
against these interpreters.

:param compatibility_constraints: list of requirements-style strings that constrain the
Python interpreter to re-exec this pex with.

"""
if os.environ.pop('SHOULD_EXIT_BOOTSTRAP_REEXEC', None):
# We've already been here and selected an interpreter. Continue to execution.
Expand All @@ -116,6 +118,13 @@ def maybe_reexec_pex(compatibility_constraints):
elif ENV.PEX_PYTHON_PATH:
target = _select_interpreter(ENV.PEX_PYTHON_PATH, compatibility_constraints)

elif compatibility_constraints:
# Apply constraints to target using regular PATH
target = _select_interpreter(
pex_python_path=None,
compatibility_constraints=compatibility_constraints
)

if target and os.path.realpath(target) != os.path.realpath(sys.executable):
cmdline = [target] + sys.argv
TRACER.log('Re-executing: cmdline="%s", sys.executable="%s", PEX_PYTHON="%s", '
Expand Down
58 changes: 53 additions & 5 deletions tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
NOT_CPYTHON36,
NOT_CPYTHON36_OR_LINUX,
PY27,
PY35,
PY36,
ensure_python_interpreter,
get_dep_dist_names_from_pex,
Expand Down Expand Up @@ -416,6 +417,40 @@ def test_interpreter_resolution_with_pex_python_path():
assert str(pex_python_path.split(':')[0]).encode() in stdout


@pytest.mark.skipif(IS_PYPY)
def test_interpreter_constraints_honored_without_ppp_or_pp():
# Create a pex with interpreter constraints, but for not the default interpreter in the path.
with temporary_dir() as td:
py36_path = ensure_python_interpreter(PY36)
py35_path = ensure_python_interpreter(PY35)
blorente marked this conversation as resolved.
Show resolved Hide resolved

pex_out_path = os.path.join(td, 'pex.pex')
env = make_env(
PEX_IGNORE_RCFILES="1",
PATH=os.pathsep.join([
os.path.dirname(py35_path),
os.path.dirname(py36_path),
])
)
res = run_pex_command(['--disable-cache',
'--interpreter-constraint===%s' % PY36,
'-o', pex_out_path],
env=env
)
res.assert_success()

# We want to try to run that pex with no environment variables set
stdin_payload = b'import sys; print(sys.executable); sys.exit(0)'

stdout, rc = run_simple_pex(pex_out_path, stdin=stdin_payload, env=env)
assert rc == 0

# If the constraints are honored, it will have run python3.6 and not python3.5
# Without constraints, we would expect it to use python3.5 as it is the minimum interpreter
# in the PATH.
assert str(py36_path).encode() in stdout


@pytest.mark.skipif(NOT_CPYTHON36)
def test_interpreter_resolution_pex_python_path_precedence_over_pex_python():
with temporary_dir() as td:
Expand Down Expand Up @@ -447,12 +482,19 @@ def test_interpreter_resolution_pex_python_path_precedence_over_pex_python():
def test_plain_pex_exec_no_ppp_no_pp_no_constraints():
with temporary_dir() as td:
pex_out_path = os.path.join(td, 'pex.pex')
res = run_pex_command(['--disable-cache',
'-o', pex_out_path])
env = make_env(
PEX_IGNORE_RCFILES="1",
PATH=os.path.dirname(os.path.realpath(sys.executable))
)
res = run_pex_command([
'--disable-cache',
'-o', pex_out_path],
env=env
)
res.assert_success()

stdin_payload = b'import os, sys; print(os.path.realpath(sys.executable)); sys.exit(0)'
stdout, rc = run_simple_pex(pex_out_path, stdin=stdin_payload)
stdout, rc = run_simple_pex(pex_out_path, stdin=stdin_payload, env=env)
assert rc == 0
assert os.path.realpath(sys.executable).encode() in stdout

Expand Down Expand Up @@ -1118,13 +1160,19 @@ def test_setup_interpreter_constraint():
interpreter = ensure_python_interpreter(PY27)
with temporary_dir() as out:
pex = os.path.join(out, 'pex.pex')
env = make_env(
PEX_IGNORE_RCFILES='1',
PATH=os.path.dirname(interpreter),
)
results = run_pex_command(['jsonschema==2.6.0',
'--disable-cache',
'--interpreter-constraint=CPython=={}'.format(PY27),
'-o', pex],
env=make_env(PATH=os.path.dirname(interpreter)))
env=env)
results.assert_success()
subprocess.check_call([pex, '-c', 'import jsonschema'])

stdout, rc = run_simple_pex(pex, env=env, stdin=b'import jsonschema')
assert rc == 0


@pytest.mark.skipif(IS_PYPY,
Expand Down