-
-
Notifications
You must be signed in to change notification settings - Fork 627
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 42 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 | ||
|
||
@classmethod | ||
def pex_python_paths(cls): | ||
"""A list of paths to Python interpreter binaries as defined by a | ||
PEX_PYTHON_PATH defined in either in '/etc/pexrc', '~/.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 | ||
""" | ||
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. pants docstring style standard should look like this (note the indentation):
|
||
ppp = Variables.from_rc().get('PEX_PYTHON_PATH') | ||
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 | ||
|
@@ -132,6 +149,7 @@ def setup(self, paths=(), filters=(b'',)): | |
# an escape hatch. | ||
filters = filters if any(filters) else self._python_setup.interpreter_constraints | ||
setup_paths = (paths | ||
or self.pex_python_paths() | ||
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 |
---|---|---|
|
@@ -51,6 +51,9 @@ def execute(self): | |
python_tgts = self.context.targets(lambda tgt: isinstance(tgt, PythonTarget)) | ||
fs = PythonInterpreterFingerprintStrategy() | ||
with self.invalidated(python_tgts, fingerprint_strategy=fs) as invalidation_check: | ||
if PythonSetup.global_instance().interpreter_search_paths and PythonInterpreterCache.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.
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. Fixed it to be a class method call, however I tried memoizing with multiple decorators from 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. Still broken: if PythonSetup.global_instance().interpreter_search_paths and PythonInterpreterCache.pex_python_paths: Did you forget to push a diff? 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. FWIW, the secret sauce is the following ordering: @classmethod
@memoized_method
def foo(cls):
... A |
||
self.context.log.warn("Detected both PEX_PYTHON_PATH and --python-setup-interpreter-search-paths. " | ||
"Ignoring --python-setup-interpreter-search-paths.") | ||
# If there are no relevant targets, we still go through the motions of selecting | ||
# an interpreter, to prevent downstream tasks from having to check for this special case. | ||
if invalidation_check.all_vts: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
# 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 and | ||
# to serve as a set of test cases for interpreter selection based on different targets. | ||
# 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_py3', | ||
source='main_py3.py', | ||
compatibility=['CPython>3'], | ||
dependencies=[ | ||
':lib_py3' | ||
] | ||
) | ||
|
||
python_library( | ||
name='lib_py3', | ||
sources=['lib_py3.py'], | ||
compatibility=['CPython>3'] | ||
) | ||
|
||
python_tests( | ||
name='test_py3', | ||
sources=[ | ||
'test_py3.py', | ||
], | ||
dependencies=[ | ||
':main_py3' | ||
], | ||
compatibility=['CPython>3'] | ||
) | ||
|
||
python_binary( | ||
name='main_py2', | ||
source='main_py2.py', | ||
compatibility=['CPython>2.7.6,<3'], | ||
dependencies=[ | ||
':lib_py2' | ||
] | ||
) | ||
|
||
python_library( | ||
name='lib_py2', | ||
sources=['lib_py2.py'], | ||
compatibility=['CPython>2.7.6,<3'] | ||
) | ||
|
||
python_tests( | ||
name='test_py2', | ||
sources=[ | ||
'test_py2.py', | ||
], | ||
dependencies=[ | ||
':main_py2' | ||
], | ||
compatibility=['CPython>2.7.6,<3'] | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# 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) | ||
|
||
|
||
# A simple example to include in a python 2 binary target | ||
|
||
def say_something(): | ||
print('I am a python 2 library method.') | ||
# Note that ascii exists as a built-in in Python 3 and | ||
# does not exist in Python 2. | ||
try: | ||
ret = ascii | ||
except NameError: | ||
ret = None | ||
assert ret is None | ||
return ret |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# coding=utf-8 | ||
# Copyright 2017 Pants project contributors (see CONTRIBUTORS.md). | ||
# Licensed under the Apache License, Version 2.0 (see LICENSE). | ||
|
||
# A simple example to include in a python 3 binary target | ||
|
||
def say_something(): | ||
print('I am a python 3 library method.') | ||
return ascii |
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_selection_testing.lib_py2 import say_something | ||
|
||
|
||
# A simple example to test building/running/testing a python 2 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() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# coding=utf-8 | ||
# Copyright 2017 Pants project contributors (see CONTRIBUTORS.md). | ||
# Licensed under the Apache License, Version 2.0 (see LICENSE). | ||
|
||
import sys | ||
|
||
from interpreter_selection.python_3_selection_testing.lib_py3 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() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# 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_selection_testing.main_py2 import main | ||
|
||
|
||
def test_main(): | ||
print(sys.executable) | ||
# Note that ascii exists as a built-in in Python 3 and | ||
# does not exist in Python 2 | ||
ret = main() | ||
assert ret == None |
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). | ||
|
||
import sys | ||
|
||
from interpreter_selection.python_3_selection_testing.main_py3 import main | ||
|
||
|
||
def test_main(): | ||
print(sys.executable) | ||
# Note that ascii exists as a built-in in Python 3 and | ||
# does not exist in Python 2 | ||
ret = main() | ||
assert ret == ascii |
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.
should be able to use
@memoized_method
here to avoid doing this lookup 2x+ per run