diff --git a/src/python/pants/python/BUILD b/src/python/pants/python/BUILD index 8babb0c9236..e839d3fd64a 100644 --- a/src/python/pants/python/BUILD +++ b/src/python/pants/python/BUILD @@ -21,6 +21,7 @@ python_library( python_tests( name='tests', + sources=['*_test.py', '!*_integration.py'], dependencies=[ '3rdparty/python:pex', ':python', @@ -33,3 +34,16 @@ python_tests( ], tags = {'partially_type_checked'}, ) + + +python_tests( + name='integration', + sources=['*_integration.py'], + dependencies=[ + '3rdparty/python:pex', + 'src/python/pants/util:contextutil', + 'src/python/pants/testutil:int-test', + 'testprojects/src/python:python_targets_directory', + ], + tags = {'integration', 'partially_type_checked'}, +) diff --git a/src/python/pants/python/pex_build_util.py b/src/python/pants/python/pex_build_util.py index 1dcd595c96f..fef27427353 100644 --- a/src/python/pants/python/pex_build_util.py +++ b/src/python/pants/python/pex_build_util.py @@ -9,7 +9,7 @@ from typing import Callable, Dict, List, Optional, Sequence, Set, Tuple from pex.fetcher import Fetcher -from pex.interpreter import PythonInterpreter +from pex.interpreter import PythonIdentity, PythonInterpreter from pex.pex_builder import PEXBuilder from pex.pex_info import PexInfo from pex.platforms import Platform @@ -449,14 +449,23 @@ def _prepare_inits(self) -> Set[str]: def set_emit_warnings(self, emit_warnings): self._builder.info.emit_warnings = emit_warnings + def _set_major_minor_interpreter_constraint_for_ipex( + self, info: PexInfo, identity: PythonIdentity, + ) -> PexInfo: + interpreter_name = identity.requirement.name + major, minor, _patch = identity.version + major_minor_only_constraint = f"{interpreter_name}=={major}.{minor}.*" + return ipex_launcher.modify_pex_info( + info, interpreter_constraints=[str(major_minor_only_constraint)] + ) + def _shuffle_underlying_pex_builder(self) -> Tuple[PexInfo, Path]: """Replace the original builder with a new one, and just pull files from the old chroot.""" # Ensure that (the interpreter selected to resolve requirements when the ipex is first run) is # (the exact same interpreter we used to resolve those requirements here). This is the only (?) # way to ensure that the ipex bootstrap uses the *exact* same interpreter version. - self._builder.info = ipex_launcher.modify_pex_info( - self._builder.info, - interpreter_constraints=[str(self._builder.interpreter.identity.requirement)], + self._builder.info = self._set_major_minor_interpreter_constraint_for_ipex( + self._builder.info, self._builder.interpreter.identity ) orig_info = self._builder.info.copy() @@ -465,6 +474,9 @@ def _shuffle_underlying_pex_builder(self) -> Tuple[PexInfo, Path]: # Mutate the PexBuilder object which is manipulated by this subsystem. self._builder = PEXBuilder(interpreter=self._builder.interpreter) + self._builder.info = self._set_major_minor_interpreter_constraint_for_ipex( + self._builder.info, self._builder.interpreter.identity + ) return (orig_info, Path(orig_chroot.path())) diff --git a/src/python/pants/python/pex_build_util_test_integration.py b/src/python/pants/python/pex_build_util_test_integration.py new file mode 100644 index 00000000000..2450bf79d62 --- /dev/null +++ b/src/python/pants/python/pex_build_util_test_integration.py @@ -0,0 +1,50 @@ +# Copyright 2020 Pants project contributors (see CONTRIBUTORS.md). +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +import json +import os +import subprocess + +from pex.interpreter import PythonInterpreter + +from pants.testutil.pants_run_integration_test import PantsRunIntegrationTest +from pants.util.collections import assert_single_element +from pants.util.contextutil import open_zip, temporary_dir + + +class PexBuildUtilIntegrationTest(PantsRunIntegrationTest): + + binary_target_address = "testprojects/src/python/python_targets:test" + + def test_ipex_gets_imprecise_constraint(self) -> None: + cur_interpreter_id = PythonInterpreter.get().identity + interpreter_name = cur_interpreter_id.requirement.name + major, minor, patch = cur_interpreter_id.version + + # Pin the selected interpreter to the one used by pants to execute this test. + cur_interpreter_constraint = f"{interpreter_name}=={major}.{minor}.{patch}" + + # Validate the the .ipex file specifically matches the major and minor versions, but allows + # any patch version. + imprecise_constraint = f"{interpreter_name}=={major}.{minor}.*" + + with temporary_dir() as tmp_dir: + self.do_command( + "--binary-py-generate-ipex", + "binary", + self.binary_target_address, + config={ + "GLOBAL": {"pants_distdir": tmp_dir}, + "python-setup": {"interpreter_constraints": [cur_interpreter_constraint],}, + }, + ) + + pex_path = os.path.join(tmp_dir, "test.ipex") + assert os.path.isfile(pex_path) + pex_execution_result = subprocess.run([pex_path], stdout=subprocess.PIPE, check=True) + assert pex_execution_result.stdout.decode() == "test!\n" + + with open_zip(pex_path) as zf: + info = json.loads(zf.read("PEX-INFO")) + constraint = assert_single_element(info["interpreter_constraints"]) + assert constraint == imprecise_constraint