diff --git a/src/python/pants/backend/plugin_development/pants_requirements.py b/src/python/pants/backend/plugin_development/pants_requirements.py index d685cd7eadb..59d77ca267b 100644 --- a/src/python/pants/backend/plugin_development/pants_requirements.py +++ b/src/python/pants/backend/plugin_development/pants_requirements.py @@ -2,8 +2,8 @@ # Licensed under the Apache License, Version 2.0 (see LICENSE). from pants.backend.python.target_types import ( - PythonRequirementCompatibleResolvesField, PythonRequirementModulesField, + PythonRequirementResolveField, PythonRequirementsField, PythonRequirementTarget, ) @@ -45,7 +45,7 @@ class PantsRequirementsTargetGenerator(Target): core_fields = ( *COMMON_TARGET_FIELDS, PantsRequirementsTestutilField, - PythonRequirementCompatibleResolvesField, + PythonRequirementResolveField, ) @@ -88,9 +88,7 @@ def create_tgt(dist: str, module: str) -> PythonRequirementTarget: { PythonRequirementsField.alias: (f"{dist}{version}",), PythonRequirementModulesField.alias: (module,), - PythonRequirementCompatibleResolvesField.alias: generator[ - PythonRequirementCompatibleResolvesField - ].value, + PythonRequirementResolveField.alias: generator[PythonRequirementResolveField].value, }, generator.address.create_generated(dist), ) diff --git a/src/python/pants/backend/plugin_development/pants_requirements_test.py b/src/python/pants/backend/plugin_development/pants_requirements_test.py index a2f2bb49ce8..ebdd07af945 100644 --- a/src/python/pants/backend/plugin_development/pants_requirements_test.py +++ b/src/python/pants/backend/plugin_development/pants_requirements_test.py @@ -12,8 +12,8 @@ ) from pants.backend.python.pip_requirement import PipRequirement from pants.backend.python.target_types import ( - PythonRequirementCompatibleResolvesField, PythonRequirementModulesField, + PythonRequirementResolveField, PythonRequirementsField, ) from pants.engine.addresses import Address @@ -48,7 +48,7 @@ def test_target_generator() -> None: "BUILD": ( "pants_requirements(name='default')\n" "pants_requirements(\n" - " name='no_testutil', testutil=False, compatible_resolves=['a']\n" + " name='no_testutil', testutil=False, resolve='a'\n" ")" ) } @@ -72,7 +72,7 @@ def test_target_generator() -> None: PipRequirement.parse(f"pantsbuild.pants.testutil{determine_version()}"), ) for t in (pants_req, testutil_req): - assert not t[PythonRequirementCompatibleResolvesField].value + assert not t[PythonRequirementResolveField].value generator = rule_runner.get_target(Address("", target_name="no_testutil")) result = rule_runner.request( @@ -81,4 +81,4 @@ def test_target_generator() -> None: assert len(result) == 1 assert next(iter(result.keys())).generated_name == "pantsbuild.pants" pants_req = next(iter(result.values())) - assert pants_req[PythonRequirementCompatibleResolvesField].value == ("a",) + assert pants_req[PythonRequirementResolveField].value == "a" diff --git a/src/python/pants/backend/python/dependency_inference/module_mapper.py b/src/python/pants/backend/python/dependency_inference/module_mapper.py index 478a7d7d621..027169312e0 100644 --- a/src/python/pants/backend/python/dependency_inference/module_mapper.py +++ b/src/python/pants/backend/python/dependency_inference/module_mapper.py @@ -19,8 +19,8 @@ ) from pants.backend.python.subsystems.setup import PythonSetup from pants.backend.python.target_types import ( - PythonRequirementCompatibleResolvesField, PythonRequirementModulesField, + PythonRequirementResolveField, PythonRequirementsField, PythonRequirementTypeStubModulesField, PythonSourceField, @@ -222,19 +222,18 @@ async def map_third_party_modules_to_addresses( ] = {} for tgt in all_python_tgts.third_party: - resolves = tgt[PythonRequirementCompatibleResolvesField].normalized_value(python_setup) + resolve = tgt[PythonRequirementResolveField].normalized_value(python_setup) def add_modules(modules: Iterable[str], *, type_stub: bool = False) -> None: - for resolve in resolves: - if resolve not in resolves_to_modules_to_providers: - resolves_to_modules_to_providers[resolve] = defaultdict(list) - for module in modules: - resolves_to_modules_to_providers[resolve][module].append( - ModuleProvider( - tgt.address, - ModuleProviderType.TYPE_STUB if type_stub else ModuleProviderType.IMPL, - ) + if resolve not in resolves_to_modules_to_providers: + resolves_to_modules_to_providers[resolve] = defaultdict(list) + for module in modules: + resolves_to_modules_to_providers[resolve][module].append( + ModuleProvider( + tgt.address, + ModuleProviderType.TYPE_STUB if type_stub else ModuleProviderType.IMPL, ) + ) explicit_modules = tgt.get(PythonRequirementModulesField).value if explicit_modules: diff --git a/src/python/pants/backend/python/dependency_inference/module_mapper_test.py b/src/python/pants/backend/python/dependency_inference/module_mapper_test.py index 04827dcb3d2..6a2a8db817a 100644 --- a/src/python/pants/backend/python/dependency_inference/module_mapper_test.py +++ b/src/python/pants/backend/python/dependency_inference/module_mapper_test.py @@ -299,13 +299,13 @@ def req( *, modules: list[str] | None = None, stub_modules: list[str] | None = None, - resolves: list[str] | None = None, + resolve: str = "default", ) -> str: return ( f"python_requirement(name='{tgt_name}', requirements=['{req_str}'], " f"modules={modules or []}," f"type_stub_modules={stub_modules or []}," - f"compatible_resolves={resolves or ['default']})" + f"resolve={repr(resolve)})" ) build_file = "\n\n".join( @@ -323,8 +323,8 @@ def req( req("typed-dep5", "typed-dep5-foo", stub_modules=["typed_dep5"]), # A 3rd-party dependency can have both a type stub and implementation. req("multiple_owners1", "multiple_owners==1"), - req("multiple_owners2", "multiple_owners==2", resolves=["another"]), - req("multiple_owners_types", "types-multiple_owners==1", resolves=["another"]), + req("multiple_owners2", "multiple_owners==2", resolve="another"), + req("multiple_owners_types", "types-multiple_owners==1", resolve="another"), # Only assume it's a type stubs dep if we are certain it's not an implementation. req("looks_like_stubs", "looks-like-stubs-types", modules=["looks_like_stubs"]), ] @@ -592,13 +592,13 @@ def test_map_module_considers_resolves(rule_runner: RuleRunner) -> None: # result in ambiguity. python_requirement( name="dep1", - compatible_resolves=["a"], + resolve="a", requirements=["dep"], ) python_requirement( name="dep2", - compatible_resolves=["b"], + resolve="b", requirements=["dep"], ) """ diff --git a/src/python/pants/backend/python/goals/lockfile.py b/src/python/pants/backend/python/goals/lockfile.py index 45115228c5e..854360a2d76 100644 --- a/src/python/pants/backend/python/goals/lockfile.py +++ b/src/python/pants/backend/python/goals/lockfile.py @@ -20,7 +20,7 @@ from pants.backend.python.subsystems.setup import PythonSetup from pants.backend.python.target_types import ( EntryPoint, - PythonRequirementCompatibleResolvesField, + PythonRequirementResolveField, PythonRequirementsField, ) from pants.backend.python.util_rules.interpreter_constraints import InterpreterConstraints @@ -270,10 +270,10 @@ async def setup_user_lockfile_requests( resolve_to_requirements_fields = defaultdict(set) for tgt in all_targets: - if not tgt.has_fields((PythonRequirementCompatibleResolvesField, PythonRequirementsField)): + if not tgt.has_fields((PythonRequirementResolveField, PythonRequirementsField)): continue - for resolve in tgt[PythonRequirementCompatibleResolvesField].normalized_value(python_setup): - resolve_to_requirements_fields[resolve].add(tgt[PythonRequirementsField]) + resolve = tgt[PythonRequirementResolveField].normalized_value(python_setup) + resolve_to_requirements_fields[resolve].add(tgt[PythonRequirementsField]) return UserGenerateLockfiles( GeneratePythonLockfile( diff --git a/src/python/pants/backend/python/goals/lockfile_test.py b/src/python/pants/backend/python/goals/lockfile_test.py index 86ee390865f..ad1958a2f38 100644 --- a/src/python/pants/backend/python/goals/lockfile_test.py +++ b/src/python/pants/backend/python/goals/lockfile_test.py @@ -88,20 +88,15 @@ def test_multiple_resolves() -> None: { "BUILD": dedent( """\ - python_requirement( - name='both', - requirements=['both1', 'both2'], - compatible_resolves=['a', 'b'], - ) python_requirement( name='a', requirements=['a'], - compatible_resolves=['a'], + resolve='a', ) python_requirement( name='b', requirements=['b'], - compatible_resolves=['b'], + resolve='b', ) """ ), @@ -121,7 +116,7 @@ def test_multiple_resolves() -> None: ) assert set(result) == { GeneratePythonLockfile( - requirements=FrozenOrderedSet(["a", "both1", "both2"]), + requirements=FrozenOrderedSet(["a"]), interpreter_constraints=InterpreterConstraints( PythonSetup.default_interpreter_constraints ), @@ -129,7 +124,7 @@ def test_multiple_resolves() -> None: lockfile_dest="a.lock", ), GeneratePythonLockfile( - requirements=FrozenOrderedSet(["b", "both1", "both2"]), + requirements=FrozenOrderedSet(["b"]), interpreter_constraints=InterpreterConstraints(["==3.7.*"]), resolve_name="b", lockfile_dest="b.lock", diff --git a/src/python/pants/backend/python/goals/repl_integration_test.py b/src/python/pants/backend/python/goals/repl_integration_test.py index 409292c23e7..06eaf173078 100644 --- a/src/python/pants/backend/python/goals/repl_integration_test.py +++ b/src/python/pants/backend/python/goals/repl_integration_test.py @@ -11,7 +11,11 @@ from pants.backend.python.goals import repl as python_repl from pants.backend.python.subsystems.ipython import rules as ipython_subsystem_rules from pants.backend.python.subsystems.setup import PythonSetup -from pants.backend.python.target_types import PythonSourcesGeneratorTarget, PythonSourceTarget +from pants.backend.python.target_types import ( + PythonRequirementTarget, + PythonSourcesGeneratorTarget, + PythonSourceTarget, +) from pants.backend.python.util_rules import local_dists, pex_from_targets from pants.backend.python.util_rules.pex import PexProcess from pants.backend.python.util_rules.pex_from_targets import NoCompatibleResolveException @@ -39,7 +43,12 @@ def rule_runner() -> RuleRunner: *local_dists.rules(), QueryRule(Process, (PexProcess,)), ], - target_types=[PythonSourcesGeneratorTarget, ProtobufSourceTarget, PythonSourceTarget], + target_types=[ + PythonSourcesGeneratorTarget, + ProtobufSourceTarget, + PythonSourceTarget, + PythonRequirementTarget, + ], ) rule_runner.write_files( { @@ -97,7 +106,7 @@ def test_eagerly_validate_roots_have_common_resolve(rule_runner: RuleRunner) -> { "BUILD": dedent( """\ - python_source(name='t1', source='f.py', resolve='a') + python_requirement(name='t1', requirements=[], resolve='a') python_source(name='t2', source='f.py', resolve='b') """ ) diff --git a/src/python/pants/backend/python/macros/pipenv_requirements.py b/src/python/pants/backend/python/macros/pipenv_requirements.py index 8c4754f15f0..5d54a4270a1 100644 --- a/src/python/pants/backend/python/macros/pipenv_requirements.py +++ b/src/python/pants/backend/python/macros/pipenv_requirements.py @@ -15,8 +15,8 @@ from pants.backend.python.pip_requirement import PipRequirement from pants.backend.python.subsystems.setup import PythonSetup from pants.backend.python.target_types import ( - PythonRequirementCompatibleResolvesField, PythonRequirementModulesField, + PythonRequirementResolveField, PythonRequirementsField, PythonRequirementsFileSourcesField, PythonRequirementsFileTarget, @@ -63,7 +63,7 @@ class PipenvRequirementsTargetGenerator(Target): PipenvSourceField, PipenvPipfileTargetField, RequirementsOverrideField, - PythonRequirementCompatibleResolvesField, + PythonRequirementResolveField, ) @@ -100,7 +100,8 @@ async def generate_from_pipenv_requirement( ) lock_info = json.loads(digest_contents[0].content) - generator[PythonRequirementCompatibleResolvesField].normalized_value(python_setup) + # Validate the resolve is legal. + generator[PythonRequirementResolveField].normalized_value(python_setup) module_mapping = generator[ModuleMappingField].value stubs_mapping = generator[TypeStubsModuleMappingField].value @@ -108,7 +109,7 @@ async def generate_from_pipenv_requirement( inherited_fields = { field.alias: field.value for field in request.generator.field_values.values() - if isinstance(field, (*COMMON_TARGET_FIELDS, PythonRequirementCompatibleResolvesField)) + if isinstance(field, (*COMMON_TARGET_FIELDS, PythonRequirementResolveField)) } def generate_tgt(raw_req: str, info: dict) -> PythonRequirementTarget: diff --git a/src/python/pants/backend/python/macros/poetry_requirements.py b/src/python/pants/backend/python/macros/poetry_requirements.py index 2aaf41d1cfe..3f2ae51516f 100644 --- a/src/python/pants/backend/python/macros/poetry_requirements.py +++ b/src/python/pants/backend/python/macros/poetry_requirements.py @@ -23,8 +23,8 @@ from pants.backend.python.pip_requirement import PipRequirement from pants.backend.python.subsystems.setup import PythonSetup from pants.backend.python.target_types import ( - PythonRequirementCompatibleResolvesField, PythonRequirementModulesField, + PythonRequirementResolveField, PythonRequirementsField, PythonRequirementsFileSourcesField, PythonRequirementsFileTarget, @@ -406,7 +406,7 @@ class PoetryRequirementsTargetGenerator(Target): TypeStubsModuleMappingField, PoetryRequirementsSourceField, RequirementsOverrideField, - PythonRequirementCompatibleResolvesField, + PythonRequirementResolveField, ) @@ -448,7 +448,8 @@ async def generate_from_python_requirement( ) ) - generator[PythonRequirementCompatibleResolvesField].normalized_value(python_setup) + # Validate the resolve is legal. + generator[PythonRequirementResolveField].normalized_value(python_setup) module_mapping = generator[ModuleMappingField].value stubs_mapping = generator[TypeStubsModuleMappingField].value @@ -456,7 +457,7 @@ async def generate_from_python_requirement( inherited_fields = { field.alias: field.value for field in request.generator.field_values.values() - if isinstance(field, (*COMMON_TARGET_FIELDS, PythonRequirementCompatibleResolvesField)) + if isinstance(field, (*COMMON_TARGET_FIELDS, PythonRequirementResolveField)) } def generate_tgt(parsed_req: PipRequirement) -> PythonRequirementTarget: diff --git a/src/python/pants/backend/python/macros/python_requirements.py b/src/python/pants/backend/python/macros/python_requirements.py index 1e8c0775710..7f9f13c0710 100644 --- a/src/python/pants/backend/python/macros/python_requirements.py +++ b/src/python/pants/backend/python/macros/python_requirements.py @@ -16,8 +16,8 @@ from pants.backend.python.pip_requirement import PipRequirement from pants.backend.python.subsystems.setup import PythonSetup from pants.backend.python.target_types import ( - PythonRequirementCompatibleResolvesField, PythonRequirementModulesField, + PythonRequirementResolveField, PythonRequirementsField, PythonRequirementsFileSourcesField, PythonRequirementsFileTarget, @@ -63,7 +63,7 @@ class PythonRequirementsTargetGenerator(Target): TypeStubsModuleMappingField, PythonRequirementsSourceField, RequirementsOverrideField, - PythonRequirementCompatibleResolvesField, + PythonRequirementResolveField, ) @@ -103,7 +103,8 @@ async def generate_from_python_requirement( requirements, lambda parsed_req: parsed_req.project_name ) - generator[PythonRequirementCompatibleResolvesField].normalized_value(python_setup) + # Validate the resolve is legal. + generator[PythonRequirementResolveField].normalized_value(python_setup) module_mapping = generator[ModuleMappingField].value stubs_mapping = generator[TypeStubsModuleMappingField].value @@ -111,7 +112,7 @@ async def generate_from_python_requirement( inherited_fields = { field.alias: field.value for field in request.generator.field_values.values() - if isinstance(field, (*COMMON_TARGET_FIELDS, PythonRequirementCompatibleResolvesField)) + if isinstance(field, (*COMMON_TARGET_FIELDS, PythonRequirementResolveField)) } def generate_tgt( diff --git a/src/python/pants/backend/python/subsystems/setup.py b/src/python/pants/backend/python/subsystems/setup.py index 1958a17d2f7..ac83dd3978c 100644 --- a/src/python/pants/backend/python/subsystems/setup.py +++ b/src/python/pants/backend/python/subsystems/setup.py @@ -143,20 +143,19 @@ def register_options(cls, register): "resolves, such as if you use two conflicting versions of a requirement in " "your repository.\n\n" "For now, Pants only has first-class support for disjoint resolves, meaning that " - "you cannot ergonomically set a `python_source` target, for example, to work " - "with multiple resolves. Practically, this means that you cannot yet reuse common " - "code, such as util files, across projects using different resolves. Support for " - "overlapping resolves is coming soon.\n\n" - "If you only need a single resolve, run `./pants generate-lockfiles` to generate " - "the lockfile.\n\n" + "you cannot ergonomically set a `python_requirement` or `python_source` target, " + "for example, to work with multiple resolves. Practically, this means that you " + "cannot yet ergonomically reuse common code, such as util files, across projects " + "using different resolves. Support for overlapping resolves is coming soon.\n\n" + "If you only need a single resolve, run `./pants generate-lockfiles` to " + "generate the lockfile.\n\n" "If you need multiple resolves:\n\n" " 1. Via this option, define multiple resolve " "names and their lockfile paths. The names should be meaningful to your " "repository, such as `data-science` or `pants-plugins`.\n" - " 2. Set the default with " - "`[python].default_resolve`.\n" + " 2. Set the default with `[python].default_resolve`.\n" " 3. Update your `python_requirement` targets with the " - "`compatible_resolves` field to declare which resolve(s) they should " + "`resolve` field to declare which resolve they should " "be available in. They default to `[python].default_resolve`, so you " "only need to update targets that you want in non-default resolves. " "(Often you'll set this via the `python_requirements` or `poetry_requirements` " @@ -175,7 +174,7 @@ def register_options(cls, register): type=str, default="python-default", help=( - "The default value used for the `resolve` and `compatible_resolves` fields.\n\n" + "The default value used for the `resolve` field.\n\n" "The name must be defined as a resolve in `[python].resolves`." ), ) diff --git a/src/python/pants/backend/python/target_types.py b/src/python/pants/backend/python/target_types.py index 58d1eae23c3..4712bb9fc55 100644 --- a/src/python/pants/backend/python/target_types.py +++ b/src/python/pants/backend/python/target_types.py @@ -110,10 +110,7 @@ class PythonResolveField(StringField, AsyncFieldMixin): help = ( "The resolve from `[python].resolves` to use.\n\n" "If not defined, will default to `[python].default_resolve`.\n\n" - "All dependencies must share the same resolve. This means that you can only depend on " - "first-party targets like `python_source` that set their `resolve` field " - "to the same value, and on `python_requirement` targets that include the resolve in " - "their `compatible_resolves` field." + "All dependencies must share the same value for their `resolve` field." ) def normalized_value(self, python_setup: PythonSetup) -> str: @@ -127,16 +124,6 @@ def normalized_value(self, python_setup: PythonSetup) -> str: ) return resolve - def resolve_and_lockfile(self, python_setup: PythonSetup) -> tuple[str, str] | None: - """If configured, return the resolve name with its lockfile. - - Error if the resolve name is invalid. - """ - if not python_setup.enable_resolves: - return None - resolve = self.normalized_value(python_setup) - return (resolve, python_setup.resolves[resolve]) - # ----------------------------------------------------------------------------------------------- # `pex_binary` and `pex_binaries` target @@ -939,33 +926,20 @@ def normalize_module_mapping( return FrozenDict({canonicalize_project_name(k): tuple(v) for k, v in (mapping or {}).items()}) -class PythonRequirementCompatibleResolvesField(StringSequenceField, AsyncFieldMixin): - alias = "compatible_resolves" +class PythonRequirementResolveField(PythonResolveField): + alias = "resolve" required = False help = ( - "The resolves from `[python].resolves` that this requirement should be " - "included in.\n\n" + "The resolve from `[python].resolves` that this requirement is included in.\n\n" "If not defined, will default to `[python].default_resolve`.\n\n" "When generating a lockfile for a particular resolve via the `generate-lockfiles` goal, " - "it will include all requirements that are declared compatible with that resolve. " + "it will include all requirements that are declared with that resolve. " "First-party targets like `python_source` and `pex_binary` then declare which resolve " - "they use via the `resolve` field; so, for your first-party code to use a " + "they use via their `resolve` field; so, for your first-party code to use a " "particular `python_requirement` target, that requirement must be included in the resolve " "used by that code." ) - def normalized_value(self, python_setup: PythonSetup) -> tuple[str, ...]: - """Get the value after applying the default and validating every key is recognized.""" - value_or_default = self.value or (python_setup.default_resolve,) - invalid_resolves = set(value_or_default) - set(python_setup.resolves) - if invalid_resolves: - raise UnrecognizedResolveNamesError( - sorted(invalid_resolves), - python_setup.resolves.keys(), - description_of_origin=f"the field `{self.alias}` in the target {self.address}", - ) - return value_or_default - class PythonRequirementTarget(Target): alias = "python_requirement" @@ -975,16 +949,15 @@ class PythonRequirementTarget(Target): PythonRequirementsField, PythonRequirementModulesField, PythonRequirementTypeStubModulesField, - PythonRequirementCompatibleResolvesField, + PythonRequirementResolveField, ) help = ( "A Python requirement installable by pip.\n\n" "This target is useful when you want to declare Python requirements inline in a " "BUILD file. If you have a `requirements.txt` file already, you can instead use " - "the macro `python_requirements()` to convert each " - "requirement into a `python_requirement()` target automatically. For Poetry, use " - "`poetry_requirements()`." - "\n\n" + "the target generator `python_requirements` to convert each " + "requirement into a `python_requirement` target automatically. For Poetry, use " + "`poetry_requirements`.\n\n" f"See {doc_url('python-third-party-dependencies')}." ) diff --git a/src/python/pants/backend/python/util_rules/pex_from_targets.py b/src/python/pants/backend/python/util_rules/pex_from_targets.py index 9b2b1b64fa4..66ff6a6ceb1 100644 --- a/src/python/pants/backend/python/util_rules/pex_from_targets.py +++ b/src/python/pants/backend/python/util_rules/pex_from_targets.py @@ -16,7 +16,6 @@ from pants.backend.python.target_types import ( MainSpecification, PexLayout, - PythonRequirementCompatibleResolvesField, PythonRequirementsField, PythonResolveField, parse_requirements_file, @@ -177,7 +176,8 @@ async def interpreter_constraints_for_targets( transitive_targets.closure, python_setup ) # If there are no targets, we fall back to the global constraints. This is relevant, - # for example, when running `./pants repl` with no specs. + # for example, when running `./pants repl` with no specs or only on targets without + # `interpreter_constraints` (e.g. `python_requirement`). interpreter_constraints = calculated_constraints or InterpreterConstraints( python_setup.interpreter_constraints ) @@ -211,12 +211,6 @@ def __init__( if tgt.has_field(PythonResolveField): resolve = tgt[PythonResolveField].normalized_value(python_setup) resolves_to_addresses[resolve].append(tgt.address.spec) - elif tgt.has_field(PythonRequirementCompatibleResolvesField): - resolves = tgt[PythonRequirementCompatibleResolvesField].normalized_value( - python_setup - ) - for resolve in resolves: - resolves_to_addresses[resolve].append(tgt.address.spec) formatted_resolve_lists = "\n\n".join( f"{resolve}:\n{bullet_list(sorted(addresses))}" @@ -226,9 +220,8 @@ def __init__( f"{msg_prefix}:\n\n" f"{formatted_resolve_lists}\n\n" "Targets which will be used together must all have the same resolve (from the " - f"[resolve]({doc_url('reference-python_test#coderesolvecode')}) or " - f"[compatible_resolves]({doc_url('reference-python_requirement#codecompatible_resolvescode')}) " - "fields) in common." + (f"\n\n{msg_suffix}" if msg_suffix else "") + f"[resolve]({doc_url('reference-python_test#coderesolvecode')}) " + "field) in common." + (f"\n\n{msg_suffix}" if msg_suffix else "") ) @@ -245,9 +238,8 @@ async def choose_python_resolve( if root.has_field(PythonResolveField) } if not root_resolves: - # If there are no targets, we fall back to the default resolve. This is relevant, - # for example, when running `./pants repl` with no specs or directly on - # python_requirement targets. + # If there are no relevant targets, we fall back to the default resolve. This is relevant, + # for example, when running `./pants repl` with no specs or only on non-Python targets. return ChosenPythonResolve( name=python_setup.default_resolve, lockfile_path=python_setup.resolves[python_setup.default_resolve], @@ -264,19 +256,10 @@ async def choose_python_resolve( # Then, validate that all transitive deps are compatible. for tgt in transitive_targets.dependencies: - invalid_resolve_field = ( + if ( tgt.has_field(PythonResolveField) and tgt[PythonResolveField].normalized_value(python_setup) != chosen_resolve - ) - invalid_compatible_resolves_field = tgt.has_field( - PythonRequirementCompatibleResolvesField - ) and not any( - resolve == chosen_resolve - for resolve in tgt[PythonRequirementCompatibleResolvesField].normalized_value( - python_setup - ) - ) - if invalid_resolve_field or invalid_compatible_resolves_field: + ): plural = ("s", "their") if len(transitive_targets.roots) > 1 else ("", "its") raise NoCompatibleResolveException( python_setup, diff --git a/src/python/pants/backend/python/util_rules/pex_from_targets_test.py b/src/python/pants/backend/python/util_rules/pex_from_targets_test.py index 54de66c0e90..08602861b56 100644 --- a/src/python/pants/backend/python/util_rules/pex_from_targets_test.py +++ b/src/python/pants/backend/python/util_rules/pex_from_targets_test.py @@ -15,7 +15,6 @@ from pants.backend.python.subsystems.setup import PythonSetup from pants.backend.python.target_types import ( - PythonRequirementCompatibleResolvesField, PythonRequirementsField, PythonRequirementTarget, PythonResolveField, @@ -64,10 +63,7 @@ def test_no_compatible_resolve_error() -> None: python_setup = create_subsystem(PythonSetup, resolves={"a": "", "b": ""}) targets = [ PythonRequirementTarget( - { - PythonRequirementsField.alias: [], - PythonRequirementCompatibleResolvesField.alias: ["a", "b"], - }, + {PythonRequirementsField.alias: [], PythonResolveField.alias: "a"}, Address("", target_name="t1"), ), PythonSourceTarget( @@ -89,7 +85,6 @@ def test_no_compatible_resolve_error() -> None: * //:t2 b: - * //:t1 * //:t3 """ ) @@ -97,12 +92,12 @@ def test_no_compatible_resolve_error() -> None: def test_choose_compatible_resolve(rule_runner: RuleRunner) -> None: - def create_build(*, req_resolves: list[str], source_resolve: str, test_resolve: str) -> str: + def create_build(*, req_resolve: str, source_resolve: str, test_resolve: str) -> str: return dedent( f"""\ python_source(name="dep", source="dep.py", resolve="{source_resolve}") python_requirement( - name="req", requirements=[], compatible_resolves={repr(req_resolves)} + name="req", requirements=[], resolve="{req_resolve}" ) python_test( name="test", @@ -117,15 +112,8 @@ def create_build(*, req_resolves: list[str], source_resolve: str, test_resolve: rule_runner.write_files( { # Note that each of these BUILD files are entirely self-contained. - "valid/BUILD": create_build( - req_resolves=["a", "b"], source_resolve="a", test_resolve="a" - ), - "invalid_dep_resolve_field/BUILD": create_build( - req_resolves=["a"], source_resolve="a", test_resolve="b" - ), - "invalid_dep_compatible_resolves_field/BUILD": create_build( - req_resolves=["b"], source_resolve="a", test_resolve="a" - ), + "valid/BUILD": create_build(req_resolve="a", source_resolve="a", test_resolve="a"), + "invalid/BUILD": create_build(req_resolve="a", source_resolve="b", test_resolve="b"), } ) @@ -136,22 +124,20 @@ def choose_resolve(addresses: list[Address]) -> str: assert choose_resolve([Address("valid", target_name="test")]) == "a" assert choose_resolve([Address("valid", target_name="dep")]) == "a" + assert choose_resolve([Address("valid", target_name="req")]) == "a" with engine_error(NoCompatibleResolveException, contains="its dependencies are not compatible"): - choose_resolve([Address("invalid_dep_resolve_field", target_name="test")]) + choose_resolve([Address("invalid", target_name="test")]) + with engine_error(NoCompatibleResolveException, contains="its dependencies are not compatible"): + choose_resolve([Address("invalid", target_name="dep")]) + with engine_error( NoCompatibleResolveException, contains="input targets did not have a resolve" ): choose_resolve( - [ - Address("invalid_dep_resolve_field", target_name="test"), - Address("invalid_dep_resolve_field", target_name="dep"), - ] + [Address("invalid", target_name="req"), Address("invalid", target_name="dep")] ) - with engine_error(NoCompatibleResolveException): - choose_resolve([Address("invalid_dep_compatible_resolves_field", target_name="test")]) - @dataclass(frozen=True) class Project: