diff --git a/src/python/pants/backend/python/register.py b/src/python/pants/backend/python/register.py index e5ccf2b9de4d..847e731434cc 100644 --- a/src/python/pants/backend/python/register.py +++ b/src/python/pants/backend/python/register.py @@ -8,6 +8,7 @@ from pants.backend.python.rules import ( download_pex_bin, inject_init, + interpreter_constraints, pex, python_run_binary, python_test_runner, @@ -99,6 +100,7 @@ def rules(): return ( download_pex_bin.rules() + inject_init.rules() + + interpreter_constraints.rules() + python_test_runner.rules() + python_run_binary.rules() + python_native_code_rules() + diff --git a/src/python/pants/backend/python/rules/interpreter_constraints.py b/src/python/pants/backend/python/rules/interpreter_constraints.py new file mode 100644 index 000000000000..bccb5865c1ca --- /dev/null +++ b/src/python/pants/backend/python/rules/interpreter_constraints.py @@ -0,0 +1,42 @@ +# Copyright 2019 Pants project contributors (see CONTRIBUTORS.md). +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +from dataclasses import dataclass +from typing import FrozenSet, List, Tuple + +from pants.backend.python.subsystems.python_setup import PythonSetup +from pants.engine.legacy.structs import PythonTargetAdaptor +from pants.engine.rules import RootRule, rule + + +@dataclass(frozen=True) +class BuildConstraintsForAdaptors: + adaptors: Tuple[PythonTargetAdaptor] + + +@dataclass(frozen=True) +class PexInterpreterContraints: + constraint_set: FrozenSet[str] = frozenset() + + def generate_pex_arg_list(self) -> List[str]: + args = [] + for constraint in self.constraint_set: + args.extend(["--interpreter-constraint", constraint]) + return args + + +@rule +def handle_constraints(build_constraints_for_adaptors: BuildConstraintsForAdaptors, python_setup: PythonSetup) -> PexInterpreterContraints: + interpreter_constraints = frozenset( + [constraint + for target_adaptor in build_constraints_for_adaptors.adaptors + for constraint in python_setup.compatibility_or_constraints( + getattr(target_adaptor, 'compatibility', None) + )] + ) + + yield PexInterpreterContraints(constraint_set=interpreter_constraints) + + +def rules(): + return [handle_constraints, RootRule(BuildConstraintsForAdaptors)] diff --git a/src/python/pants/backend/python/rules/pex.py b/src/python/pants/backend/python/rules/pex.py index 8ed3d61ff2b5..891dd2a90629 100644 --- a/src/python/pants/backend/python/rules/pex.py +++ b/src/python/pants/backend/python/rules/pex.py @@ -6,6 +6,7 @@ from pants.backend.python.rules.download_pex_bin import DownloadedPexBin from pants.backend.python.rules.hermetic_pex import HermeticPex +from pants.backend.python.rules.interpreter_constraints import PexInterpreterContraints from pants.backend.python.subsystems.python_native_code import PexBuildEnvironment from pants.backend.python.subsystems.python_setup import PythonSetup from pants.backend.python.subsystems.subprocess_environment import SubprocessEncodingEnvironment @@ -31,7 +32,7 @@ class CreatePex: """Represents a generic request to create a PEX from its inputs.""" output_filename: str requirements: Tuple[str] = () - interpreter_constraints: Tuple[str] = () + interpreter_constraints: PexInterpreterContraints = PexInterpreterContraints() entry_point: Optional[str] = None input_files_digest: Optional[Digest] = None @@ -57,9 +58,7 @@ def create_pex( """Returns a PEX with the given requirements, optional entry point, and optional interpreter constraints.""" - interpreter_constraint_args = [] - for constraint in request.interpreter_constraints: - interpreter_constraint_args.extend(["--interpreter-constraint", constraint]) + interpreter_constraint_args = request.interpreter_constraints.generate_pex_arg_list() argv = ["--output-file", request.output_filename] if request.entry_point is not None: @@ -118,7 +117,7 @@ class RunnablePex: # necessarily the interpreter that PEX will use to execute the generated .pex file. @rule def pex_execute_request( - runnable_pex: RunnablePex, + runnable_pex: RunnablePex, #TODO if this rule is a Pex the rule graph blows up wtf? subprocess_encoding_environment: SubprocessEncodingEnvironment, python_setup: PythonSetup ) -> ExecuteProcessRequest: diff --git a/src/python/pants/backend/python/rules/python_run_binary.py b/src/python/pants/backend/python/rules/python_run_binary.py index 2a69fa67e4bd..17f8fb7eec69 100644 --- a/src/python/pants/backend/python/rules/python_run_binary.py +++ b/src/python/pants/backend/python/rules/python_run_binary.py @@ -2,6 +2,10 @@ # Licensed under the Apache License, Version 2.0 (see LICENSE). from pants.backend.python.rules.inject_init import InjectedInitDigest +from pants.backend.python.rules.interpreter_constraints import ( + BuildConstraintsForAdaptors, + PexInterpreterContraints, +) from pants.backend.python.rules.pex import CreatePex, RunnablePex from pants.backend.python.subsystems.python_setup import PythonSetup from pants.backend.python.targets.python_binary import PythonBinary @@ -25,13 +29,9 @@ def run_python_binary(python_binary_target: PythonBinaryAdaptor, all_targets = transitive_hydrated_targets.closure all_target_adaptors = [t.adaptor for t in all_targets] - interpreter_constraints = { - constraint - for target_adaptor in all_target_adaptors - for constraint in python_setup.compatibility_or_constraints( - getattr(target_adaptor, 'compatibility', None) - ) - } + + interpreter_constraints = yield Get(PexInterpreterContraints, + BuildConstraintsForAdaptors(adaptors=tuple(all_target_adaptors))) source_root_stripped_sources = yield [ Get(SourceRootStrippedSources, HydratedTarget, target_adaptor) @@ -74,7 +74,7 @@ def run_python_binary(python_binary_target: PythonBinaryAdaptor, create_requirements_pex = CreatePex( output_filename=output_thirdparty_requirements_pex_filename, requirements=tuple(sorted(all_requirements)), - interpreter_constraints=tuple(sorted(interpreter_constraints)), + interpreter_constraints=interpreter_constraints, entry_point=entry_point, input_files_digest=merged_input_files, ) diff --git a/src/python/pants/backend/python/rules/python_test_runner.py b/src/python/pants/backend/python/rules/python_test_runner.py index 764ed5158a6d..47dc2c9018d7 100644 --- a/src/python/pants/backend/python/rules/python_test_runner.py +++ b/src/python/pants/backend/python/rules/python_test_runner.py @@ -2,6 +2,10 @@ # Licensed under the Apache License, Version 2.0 (see LICENSE). from pants.backend.python.rules.inject_init import InjectedInitDigest +from pants.backend.python.rules.interpreter_constraints import ( + BuildConstraintsForAdaptors, + PexInterpreterContraints, +) from pants.backend.python.rules.pex import CreatePex, Pex from pants.backend.python.subsystems.pytest import PyTest from pants.backend.python.subsystems.python_setup import PythonSetup @@ -33,13 +37,8 @@ def run_python_test( ) all_targets = transitive_hydrated_targets.closure - interpreter_constraints = { - constraint - for target_adaptor in all_targets - for constraint in python_setup.compatibility_or_constraints( - getattr(target_adaptor, 'compatibility', None) - ) - } + interpreter_constraints = yield Get(PexInterpreterContraints, + BuildConstraintsForAdaptors(adaptors=tuple(all_targets))) # Produce a pex containing pytest and all transitive 3rdparty requirements. output_pytest_requirements_pex_filename = 'pytest-with-requirements.pex' @@ -58,7 +57,7 @@ def run_python_test( Pex, CreatePex( output_filename=output_pytest_requirements_pex_filename, requirements=tuple(sorted(all_requirements)), - interpreter_constraints=tuple(sorted(interpreter_constraints)), + interpreter_constraints=interpreter_constraints, entry_point="pytest:main", ) )