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

OR interpreter constraints when multiple given #678

Merged
merged 12 commits into from
Mar 10, 2019
8 changes: 4 additions & 4 deletions pex/bin/pex.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,10 +317,10 @@ def configure_clp_pex_environment(parser):
default=[],
type='str',
action='append',
help='A constraint that determines the interpreter compatibility for '
'this pex, using the Requirement-style format, e.g. "CPython>=3", or ">=2.7" '
'for requirements agnostic to interpreter class. This option can be passed multiple '
'times.')
help='Constrain the selected Python interpreter. Specify with Requirement-style syntax, '
'e.g. "CPython>=2.7,<3" (A CPython interpreter with version >=2.7 AND version <3) '
'or "PyPy" (A pypy interpreter of any version). This argument may be repeated multiple '
'times to OR the constraints.')

group.add_option(
'--rcfile',
Expand Down
15 changes: 6 additions & 9 deletions pex/interpreter_constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,18 @@ def validate_constraints(constraints):
die("Compatibility requirements are not formatted properly: %s" % str(e))


def matched_interpreters(interpreters, constraints, meet_all_constraints=False):
"""Given some filters, yield any interpreter that matches at least one of them, or all of them
if meet_all_constraints is set to True.
def matched_interpreters(interpreters, constraints):
"""Given some filters, yield any interpreter that matches at least one of them.

:param interpreters: a list of PythonInterpreter objects for filtering
:param constraints: A sequence of strings that constrain the interpreter compatibility for this
pex, using the Requirement-style format, e.g. ``'CPython>=3', or just ['>=2.7','<3']``
for requirements agnostic to interpreter class.
:param meet_all_constraints: whether to match against all filters.
Defaults to matching interpreters that match at least one filter.
pex. Each string uses the Requirement-style format, e.g. 'CPython>=3' or '>=2.7,<3' for
requirements agnostic to interpreter class. Multiple requirement strings may be combined
into a list to OR the constraints, such as ['CPython>=2.7,<3', 'CPython>=3.4'].
:return interpreter: returns a generator that yields compatible interpreters
"""
check = all if meet_all_constraints else any
for interpreter in interpreters:
if check(interpreter.identity.matches(filt) for filt in constraints):
if any(interpreter.identity.matches(filt) for filt in constraints):
TRACER.log("Constraints on interpreters: %s, Matching Interpreter: %s"
% (constraints, interpreter.binary), V=3)
yield interpreter
9 changes: 6 additions & 3 deletions pex/pex_bootstrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,11 @@ def find_compatible_interpreters(pex_python_path, compatibility_constraints):
# get all qualifying interpreters found in $PATH
interpreters = PythonInterpreter.all()

return list(matched_interpreters(
interpreters, compatibility_constraints, meet_all_constraints=True))
return list(
matched_interpreters(interpreters, compatibility_constraints)
if compatibility_constraints
else interpreters
)


def _select_pex_python_interpreter(target_python, compatibility_constraints):
Expand All @@ -96,7 +99,7 @@ def _select_pex_python_interpreter(target_python, compatibility_constraints):
die('Failed to find interpreter specified by PEX_PYTHON: %s' % target)
if compatibility_constraints:
pi = PythonInterpreter.from_binary(target)
if not list(matched_interpreters([pi], compatibility_constraints, meet_all_constraints=True)):
if not list(matched_interpreters([pi], compatibility_constraints)):
die('Interpreter specified by PEX_PYTHON (%s) is not compatible with specified '
'interpreter constraints: %s' % (target, str(compatibility_constraints)))
if not os.path.exists(target):
Expand Down
38 changes: 24 additions & 14 deletions tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,12 +334,12 @@ def test_interpreter_constraints_to_pex_info_py2():
# target python 2
pex_out_path = os.path.join(output_dir, 'pex_py2.pex')
res = run_pex_command(['--disable-cache',
'--interpreter-constraint=>=2.7',
'--interpreter-constraint=<3',
'--interpreter-constraint=>=2.7,<3',
'--interpreter-constraint=>=3.5',
'-o', pex_out_path])
res.assert_success()
pex_info = get_pex_info(pex_out_path)
assert set(['>=2.7', '<3']) == set(pex_info.interpreter_constraints)
assert {'>=2.7,<3', '>=3.5'} == set(pex_info.interpreter_constraints)


@pytest.mark.skipif(IS_PYPY)
Expand All @@ -359,12 +359,26 @@ def test_interpreter_resolution_with_constraint_option():
with temporary_dir() as output_dir:
pex_out_path = os.path.join(output_dir, 'pex1.pex')
res = run_pex_command(['--disable-cache',
'--interpreter-constraint=>=2.7',
'--interpreter-constraint=<3',
'--interpreter-constraint=>=2.7,<3',
'-o', pex_out_path])
res.assert_success()
pex_info = get_pex_info(pex_out_path)
assert set(['>=2.7', '<3']) == set(pex_info.interpreter_constraints)
assert ['>=2.7,<3'] == pex_info.interpreter_constraints
assert pex_info.build_properties['version'][0] < 3


def test_interpreter_resolution_with_multiple_constraint_options():
with temporary_dir() as output_dir:
pex_out_path = os.path.join(output_dir, 'pex1.pex')
res = run_pex_command(['--disable-cache',
'--interpreter-constraint=>=2.7,<3',
# Add a constraint that's impossible to satisfy. Because multiple
# constraints OR, the interpeter should still resolve to Python 2.7.
'--interpreter-constraint=>=500',
'-o', pex_out_path])
res.assert_success()
pex_info = get_pex_info(pex_out_path)
assert {'>=2.7,<3', '>=500'} == set(pex_info.interpreter_constraints)
assert pex_info.build_properties['version'][0] < 3


Expand All @@ -388,8 +402,7 @@ def test_interpreter_resolution_with_pex_python_path():
pex_out_path = os.path.join(td, 'pex.pex')
res = run_pex_command(['--disable-cache',
'--rcfile=%s' % pexrc_path,
'--interpreter-constraint=%s' % interpreter_constraint1,
'--interpreter-constraint=%s' % interpreter_constraint2,
'--interpreter-constraint=%s,%s' % (interpreter_constraint1, interpreter_constraint2),
'-o', pex_out_path])
res.assert_success()

Expand Down Expand Up @@ -420,8 +433,7 @@ def test_interpreter_resolution_pex_python_path_precedence_over_pex_python():
pex_out_path = os.path.join(td, 'pex.pex')
res = run_pex_command(['--disable-cache',
'--rcfile=%s' % pexrc_path,
'--interpreter-constraint=>3',
'--interpreter-constraint=<3.8',
'--interpreter-constraint=>3,<3.8',
'-o', pex_out_path])
res.assert_success()

Expand Down Expand Up @@ -515,8 +527,7 @@ def test_pex_python():
pex_out_path = os.path.join(td, 'pex.pex')
res = run_pex_command(['--disable-cache',
'--rcfile=%s' % pexrc_path,
'--interpreter-constraint=>3',
'--interpreter-constraint=<3.8',
'--interpreter-constraint=>3,<3.8',
'-o', pex_out_path],
env=env)
res.assert_success()
Expand All @@ -536,8 +547,7 @@ def test_pex_python():
pex_out_path = os.path.join(td, 'pex2.pex')
res = run_pex_command(['--disable-cache',
'--rcfile=%s' % pexrc_path,
'--interpreter-constraint=>3',
'--interpreter-constraint=<3.8',
'--interpreter-constraint=>3,<3.8',
'-o', pex_out_path],
env=env)
res.assert_success()
Expand Down