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
Integrate PEX interpreter selection based on target-level interpreter compatibility constraints #5160
Integrate PEX interpreter selection based on target-level interpreter compatibility constraints #5160
Changes from 6 commits
24bf2cf
9567613
243522c
0f63542
ad3f714
7a45792
ac28d60
b83a2f7
28cbdf7
c99b4d0
50df988
212f87c
b7120a0
7da186e
1d37a89
f69c5f0
82e6e74
a2eefe9
23e2417
4d1b606
b15dbe3
71fd8c0
063bb0d
95b102f
6031d7a
a9fad7c
8ac4b70
da2890d
af61c1e
d987cc6
9ea24e2
7302859
1196098
4ef2aef
cbf0929
a402d44
7910e9d
364c5f9
a6fcb24
0e5be87
1ed2438
82e6788
bd71291
4e8baa8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,7 @@ | |
from pex.interpreter import PythonIdentity, PythonInterpreter | ||
from pex.package import EggPackage, Package, SourcePackage | ||
from pex.resolver import resolve | ||
from pex.variables import Variables | ||
|
||
from pants.backend.python.targets.python_target import PythonTarget | ||
from pants.base.exceptions import TaskError | ||
|
@@ -39,6 +40,22 @@ def _matching(cls, interpreters, filters): | |
if cls._matches(interpreter, filters): | ||
yield interpreter | ||
|
||
@memoized_property | ||
def pex_python_paths(self): | ||
"""A list of paths to Python interpreter binaries as defined by a | ||
PEX_PYTHON_PATH defined in either in '/etc/pexrc', '~/.pexrc', or ./.pexrc'. | ||
PEX_PYTHON_PATH defines a colon-seperated list of paths to interpreters | ||
that a pex can be built and ran against. | ||
|
||
:return: paths to interpreters as specified by PEX_PYTHON_PATH | ||
:rtype: list | ||
""" | ||
ppp = Variables.from_rc().get('PEX_PYTHON_PATH', '') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe just |
||
if ppp: | ||
return ppp.split(os.pathsep) | ||
else: | ||
return [] | ||
|
||
def __init__(self, python_setup, python_repos, logger=None): | ||
self._python_setup = python_setup | ||
self._python_repos = python_repos | ||
|
@@ -127,11 +144,14 @@ def setup(self, paths=(), filters=(b'',)): | |
:returns: A list of cached interpreters | ||
:rtype: list of :class:`pex.interpreter.PythonInterpreter` | ||
""" | ||
pex_python_paths = self.pex_python_paths | ||
# We filter the interpreter cache itself (and not just the interpreters we pull from it) | ||
# because setting up some python versions (e.g., 3<=python<3.3) crashes, and this gives us | ||
# an escape hatch. | ||
filters = filters if any(filters) else self._python_setup.interpreter_constraints | ||
if not any(filters): | ||
filters = self._python_setup.interpreter_constraints | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. seems like there's a tension here between the notion of global interpreter constraints defined on what happens now if the global interpreter constraints conflict with target level constraints (e.g. if global constraints only ensure a seems to me like currently, this might entirely bypass the interpreter cache which could lead to breakage down the road. a test around that case (and any required fixes) would be good. |
||
setup_paths = (paths | ||
or pex_python_paths | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would directly reference |
||
or self._python_setup.interpreter_search_paths | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should probably include a note on the option for |
||
or os.getenv('PATH').split(os.pathsep)) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -64,7 +64,9 @@ def register_options(cls, register): | |
'If unspecified, a standard path under the workdir is used.') | ||
register('--interpreter-search-paths', advanced=True, type=list, default=[], | ||
metavar='<binary-paths>', | ||
help='A list of paths to search for python interpreters.') | ||
help='A list of paths to search for python interpreters. Note that if a PEX_PYTHON_PATH ' | ||
'variable is defined in a pexrc file, those interpreter paths will take precedence over ' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this the right precedence, and why? Should the paths be merged instead? I don't have an opinion, just trying to understand the design choice. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we use /etc/pexrc both at build-time and at (pex) run-time for a globally consistent view of "blessed" interpreters, so the original thinking was to align strictly to that or else fallback to pants' config.. but I could see merging being useful for testing purposes. will leave it up to @CMLivingston to make the final decision. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To me it makes the most sense to give it precedence over the setup.interpreter_constraints so we know we are using the same interpreter across all tasks and the runtime of the pex in play. I could see a situation where an interpreter is selected for subsequent tasks because it is a lower version than all interpreters in PPP, and it is possible that this could lead to unexpected errors (although tbh I don't know what they would be exactly because the interpreter product is used in multiple tasks). For now I will go with giving PPP precedence and writing a log.warn if interpreter-search-paths is passed to let the user know that they have a pexrc somewhere. To me, if someone defines a pexrc, they are saying that they want created pexes to use this config and as of right now I don't see any reason why the Pants case should be an exception. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SG |
||
'this option.') | ||
|
||
@property | ||
def interpreter_constraints(self): | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -103,6 +103,10 @@ def _create_binary(self, binary_tgt, results_dir): | |
|
||
builder = PEXBuilder(path=tmpdir, interpreter=interpreter, pex_info=pex_info, copy=True) | ||
|
||
# Add binary target's interpreter compatibility to pex info | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Period at end of sentence. |
||
for constraint in binary_tgt.compatibility: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you can scrap this: The closure iterated over a few lines down includes binary_tgt itself. And |
||
builder.add_interpreter_constraint(constraint) | ||
|
||
if binary_tgt.shebang: | ||
self.context.log.info('Found Python binary target {} with customized shebang, using it: {}' | ||
.format(binary_tgt.name, binary_tgt.shebang)) | ||
|
@@ -116,6 +120,10 @@ def _create_binary(self, binary_tgt, results_dir): | |
for tgt in binary_tgt.closure(exclude_scopes=Scopes.COMPILE): | ||
if has_python_sources(tgt) or has_resources(tgt): | ||
source_tgts.append(tgt) | ||
# Add target's interpreter compatibility constraints to pex info | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ditto, and throughout. |
||
if has_python_sources(tgt): | ||
for constraint in tgt.compatibility: | ||
builder.add_interpreter_constraint(constraint) | ||
elif has_python_requirements(tgt): | ||
req_tgts.append(tgt) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
# Copyright 2017 Pants project contributors (see CONTRIBUTORS.md). | ||
# Licensed under the Apache License, Version 2.0 (see LICENSE). | ||
|
||
# This BUILD file is intended to be an example of python 3 compatible targets. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Neat! |
||
# To successfully build and run this binary target, the user will need to define a | ||
# .pexrc file in /etx/pexrc, ~/.pexrc, or in a .pexrc in the pants root dir. The pexrc | ||
# will need to contain a PEX_PYTHON_PATH variable containing an absolute path to a | ||
# python 3 interpreter. | ||
# | ||
# An example of a PEX_PYTHON_PATH variable in a pexrc: | ||
# PEX_PYTHON_PATH=/path/to/python2.7:/path/to/python3.6 | ||
# | ||
# Note that the basename of the python binaries specified must either be 'python' or | ||
# 'pythonX.Y'. Specifing only major version (i.e. python3) will be ignored by Pants | ||
# interpreter filtration. | ||
# | ||
|
||
python_binary( | ||
name='main', | ||
source='main.py', | ||
compatibility=['CPython>3'], | ||
dependencies=[ | ||
':lib' | ||
] | ||
) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggest to add a |
||
python_library( | ||
name='lib', | ||
sources=['lib.py'], | ||
compatibility=['CPython>3'] | ||
) | ||
|
||
python_tests( | ||
name='test', | ||
sources=[ | ||
'test.py', | ||
], | ||
dependencies=[ | ||
':main' | ||
], | ||
compatibility=['CPython>3'] | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# coding=utf-8 | ||
# Copyright 2017 Pants project contributors (see CONTRIBUTORS.md). | ||
# Licensed under the Apache License, Version 2.0 (see LICENSE). | ||
|
||
from __future__ import (absolute_import, division, generators, nested_scopes, print_function, | ||
unicode_literals, with_statement) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
import sys | ||
|
||
|
||
# A simple example to include in a python 3 binary target | ||
|
||
def say_something(): | ||
print('I am a python 3 library method.') | ||
assert 1/2 == 0.5 | ||
|
||
# Return 1/2 for testing the `./pants run` task | ||
# Note that 1/2 = 0 in python 2 and 1/2 = 0.5 in python 3 | ||
return 1/2 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# coding=utf-8 | ||
# Copyright 2017 Pants project contributors (see CONTRIBUTORS.md). | ||
# Licensed under the Apache License, Version 2.0 (see LICENSE). | ||
|
||
from __future__ import (absolute_import, division, generators, nested_scopes, print_function, | ||
unicode_literals, with_statement) | ||
|
||
import sys | ||
|
||
from interpreter_selection.python_3_binary.lib import say_something | ||
|
||
|
||
# A simple example to test building/running/testing a python 3 binary target | ||
|
||
|
||
def main(): | ||
v = sys.version_info | ||
print(sys.executable) | ||
print('%d.%d.%d' % v[0:3]) | ||
return say_something() | ||
|
||
if __name__ == '__main__': | ||
main() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. indent |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# coding=utf-8 | ||
# Copyright 2017 Pants project contributors (see CONTRIBUTORS.md). | ||
# Licensed under the Apache License, Version 2.0 (see LICENSE). | ||
|
||
from __future__ import (absolute_import, division, generators, nested_scopes, print_function, | ||
unicode_literals, with_statement) | ||
|
||
import sys | ||
|
||
from interpreter_selection.python_3_binary.main import main | ||
|
||
def test_main(): | ||
print(sys.executable) | ||
# Note that 1/2 = 0 in python 2 and 1/2 = 0.5 in python 3 | ||
assert main() == 0.5 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pants docstring style standard should look like this (note the indentation):