From 8367b1f84fe80f657bfcb325d8fafd17e4748cd0 Mon Sep 17 00:00:00 2001 From: Yi Cheng Date: Wed, 10 Jun 2026 21:54:32 +0000 Subject: [PATCH 1/7] Allow `pants export --resolve` to filter by target transitive deps. Previously the export goal rejected any target specs. Now, when targets are provided alongside `--resolve`, only the 3rd-party requirements transitively needed by those targets are included in the exported virtualenv (via Pex native lockfile subsetting). Exporting without targets retains the existing full-resolve behavior. Co-Authored-By: Claude Sonnet 4.6 --- .../pants/backend/python/goals/export.py | 50 +++++++++++++++-- .../pants/backend/python/goals/export_test.py | 55 ++++++++++++++++++- src/python/pants/core/goals/export.py | 3 - 3 files changed, 98 insertions(+), 10 deletions(-) diff --git a/src/python/pants/backend/python/goals/export.py b/src/python/pants/backend/python/goals/export.py index 8bb205a4b01..1c6120e8a86 100644 --- a/src/python/pants/backend/python/goals/export.py +++ b/src/python/pants/backend/python/goals/export.py @@ -14,7 +14,12 @@ from pants.backend.python.subsystems.python_tool_base import PythonToolBase from pants.backend.python.subsystems.setup import PythonSetup -from pants.backend.python.target_types import PexLayout, PythonResolveField, PythonSourceField +from pants.backend.python.target_types import ( + PexLayout, + PythonRequirementsField, + PythonResolveField, + PythonSourceField, +) from pants.backend.python.util_rules.interpreter_constraints import InterpreterConstraints from pants.backend.python.util_rules.local_dists_pep660 import ( EditableLocalDistsRequest, @@ -28,7 +33,12 @@ ) from pants.backend.python.util_rules.pex_cli import PexPEX from pants.backend.python.util_rules.pex_environment import PexEnvironment, PythonExecutable -from pants.backend.python.util_rules.pex_requirements import EntireLockfile, Lockfile +from pants.backend.python.util_rules.pex_requirements import ( + EntireLockfile, + Lockfile, + PexRequirements, + Resolve, +) from pants.core.goals.export import ( Export, ExportError, @@ -43,13 +53,20 @@ from pants.core.util_rules.stripped_source_files import strip_source_roots from pants.engine.engine_aware import EngineAwareParameter from pants.engine.fs import CreateDigest, FileContent -from pants.engine.internals.graph import hydrate_sources +from pants.engine.internals.graph import hydrate_sources, transitive_targets from pants.engine.internals.native_engine import EMPTY_DIGEST, AddPrefix, Digest, MergeDigests from pants.engine.internals.selectors import concurrently from pants.engine.intrinsics import add_prefix, create_digest, digest_to_snapshot, merge_digests from pants.engine.process import Process, ProcessCacheScope, execute_process_or_raise from pants.engine.rules import collect_rules, implicitly, rule -from pants.engine.target import AllTargets, HydrateSourcesRequest, SourcesField +from pants.engine.addresses import Addresses +from pants.engine.target import ( + AllTargets, + HydrateSourcesRequest, + SourcesField, + TransitiveTargets, + TransitiveTargetsRequest, +) from pants.engine.unions import UnionMembership, UnionRule from pants.option.option_types import EnumOption, StrListOption from pants.util.strutil import path_safe, softwrap @@ -65,6 +82,7 @@ class ExportVenvsRequest(ExportRequest): @dataclass(frozen=True) class _ExportVenvForResolveRequest(EngineAwareParameter): resolve: str + addresses: Addresses = dataclasses.field(default_factory=Addresses) class PythonResolveExportFormat(Enum): @@ -537,11 +555,28 @@ async def export_virtualenv_for_resolve( else: editable_local_dists_digest = None + if request.addresses: + transitive_tgts = await transitive_targets( + TransitiveTargetsRequest(request.addresses), **implicitly() + ) + req_strings = PexRequirements.req_strings_from_requirement_fields( + tgt[PythonRequirementsField] + for tgt in transitive_tgts.closure + if tgt.has_field(PythonRequirementsField) + ) + requirements: PexRequirements | EntireLockfile = PexRequirements( + req_strings, + from_superset=Resolve(resolve, use_entire_lockfile=False), + description_of_origin=f"the export of resolve `{resolve}`", + ) + else: + requirements = EntireLockfile(lockfile) + pex_request = PexRequest( description=f"Build pex for resolve `{resolve}`", output_filename=f"{path_safe(resolve)}.pex", internal_only=True, - requirements=EntireLockfile(lockfile), + requirements=requirements, sources=editable_local_dists_digest, python=python, # Packed layout should lead to the best performance in this use case. @@ -578,8 +613,11 @@ async def export_virtualenvs( request: ExportVenvsRequest, export_subsys: ExportSubsystem, ) -> ExportResults: + addresses = Addresses(tgt.address for tgt in request.targets) maybe_venvs = await concurrently( - export_virtualenv_for_resolve(_ExportVenvForResolveRequest(resolve), **implicitly()) + export_virtualenv_for_resolve( + _ExportVenvForResolveRequest(resolve, addresses), **implicitly() + ) for resolve in export_subsys.options.resolve ) return ExportResults(mv.result for mv in maybe_venvs if mv.result is not None) diff --git a/src/python/pants/backend/python/goals/export_test.py b/src/python/pants/backend/python/goals/export_test.py index 5c10b47f209..f65f2425cae 100644 --- a/src/python/pants/backend/python/goals/export_test.py +++ b/src/python/pants/backend/python/goals/export_test.py @@ -21,7 +21,7 @@ PythonSourcesGeneratorTarget, ) from pants.backend.python.util_rules import local_dists_pep660, pex_from_targets -from pants.base.specs import RawSpecs +from pants.base.specs import AddressLiteralSpec, RawSpecs from pants.core.goals.export import ExportResults from pants.core.util_rules import distdir from pants.engine.fs import CreateDigest @@ -190,6 +190,59 @@ def test_export_tool(rule_runner: RuleRunner) -> None: assert "isort" in result.description +def test_export_venv_filtered_by_targets(rule_runner: RuleRunner) -> None: + """When target specs are provided, only their transitive requirements should be exported. + + This test verifies the structural plumbing: targets flow through the rule chain and the + transitive-closure branch is taken. The source target here has no python_requirement deps + so req_strings is empty, which exercises the early-return path in _setup_pex_requirements + without needing a PEX-native JSON lockfile. + """ + vinfo = sys.version_info + current_interpreter = f"{vinfo.major}.{vinfo.minor}.{vinfo.micro}" + rule_runner.write_files( + { + # foo has no python_requirement deps — its transitive closure has no 3rd-party reqs. + "src/foo/BUILD": dedent( + """\ + python_sources(name='foo', resolve='a') + python_requirement(name='req_unrelated', requirements=['ansicolors==1.1.8'], resolve='a') + """ + ), + "src/foo/__init__.py": "", + "lock.txt": "ansicolors==1.1.8", + } + ) + rule_runner.set_options( + [ + *pants_args_for_python_lockfiles, + f"--python-interpreter-constraints=['=={current_interpreter}']", + "--python-resolves={'a': 'lock.txt'}", + "--export-resolve=a", + "--source-root-patterns=['src']", + ], + env_inherit={"PATH", "PYENV_ROOT"}, + ) + + # Fetch only the source target (not the unrelated requirement). + targets = rule_runner.request( + Targets, + [ + RawSpecs( + address_literals=(AddressLiteralSpec("src/foo", "foo"),), + description_of_origin="test", + ) + ], + ) + assert len(targets) == 1 + + all_results = rule_runner.request(ExportResults, [ExportVenvsRequest(targets=targets)]) + assert len(all_results) == 1 + result = all_results[0] + assert result.resolve == "a" + assert result.reldir == f"python/virtualenvs/a/{current_interpreter}" + + def test_export_codegen_outputs(): class CodegenSourcesField(SingleSourceField): pass diff --git a/src/python/pants/core/goals/export.py b/src/python/pants/core/goals/export.py index 2c215788a71..762b6ce03ab 100644 --- a/src/python/pants/core/goals/export.py +++ b/src/python/pants/core/goals/export.py @@ -201,9 +201,6 @@ async def export_goal( if not (export_subsys.resolve or export_subsys.options.bin): raise ExportError("Must specify at least one `--resolve` or `--bin` to export") - if targets: - raise ExportError("The `export` goal does not take target specs.") - requests = tuple(request_type(targets) for request_type in request_types) all_results = await concurrently( export(**implicitly({request: ExportRequest})) for request in requests From 9c19fda31a390cc060cdbbb017879348a4debb3d Mon Sep 17 00:00:00 2001 From: Yi Cheng Date: Wed, 10 Jun 2026 22:09:16 +0000 Subject: [PATCH 2/7] Make test_export_venv_filtered_by_targets more complex with a real dependency graph MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Models A→B,C; B→req_x→req_y; C→req_z and asserts that transitive req_strings correctly includes only the packages reachable from each root target. Co-Authored-By: Claude Sonnet 4.6 --- .../pants/backend/python/goals/export_test.py | 114 ++++++++++++++---- 1 file changed, 89 insertions(+), 25 deletions(-) diff --git a/src/python/pants/backend/python/goals/export_test.py b/src/python/pants/backend/python/goals/export_test.py index f65f2425cae..eeb8b205392 100644 --- a/src/python/pants/backend/python/goals/export_test.py +++ b/src/python/pants/backend/python/goals/export_test.py @@ -16,10 +16,12 @@ from pants.backend.python.target_types import ( PythonDistribution, PythonRequirementTarget, + PythonRequirementsField, PythonResolveField, PythonSourceField, PythonSourcesGeneratorTarget, ) +from pants.backend.python.util_rules.pex_requirements import PexRequirements from pants.backend.python.util_rules import local_dists_pep660, pex_from_targets from pants.base.specs import AddressLiteralSpec, RawSpecs from pants.core.goals.export import ExportResults @@ -35,6 +37,8 @@ SingleSourceField, Target, Targets, + TransitiveTargets, + TransitiveTargetsRequest, ) from pants.engine.unions import UnionRule from pants.testutil.rule_runner import PYTHON_BOOTSTRAP_ENV, RuleRunner @@ -61,6 +65,7 @@ def rule_runner() -> RuleRunner: *isort_subsystem.rules(), # add a tool that we can try exporting QueryRule(Targets, [RawSpecs]), QueryRule(ExportResults, [ExportVenvsRequest]), + QueryRule(TransitiveTargets, [TransitiveTargetsRequest]), ], target_types=[PythonRequirementTarget, PythonSourcesGeneratorTarget, PythonDistribution], objects={"python_artifact": PythonArtifact, "parametrize": Parametrize}, @@ -193,54 +198,113 @@ def test_export_tool(rule_runner: RuleRunner) -> None: def test_export_venv_filtered_by_targets(rule_runner: RuleRunner) -> None: """When target specs are provided, only their transitive requirements should be exported. - This test verifies the structural plumbing: targets flow through the rule chain and the - transitive-closure branch is taken. The source target here has no python_requirement deps - so req_strings is empty, which exercises the early-return path in _setup_pex_requirements - without needing a PEX-native JSON lockfile. + Dependency graph: + A (python_sources) -> B, C + B (python_sources) -> req_x + C (python_sources) -> req_z + req_x (python_requirement x==1.0) -> req_y + req_y (python_requirement y==1.0) + req_z (python_requirement z==1.0) + + Expected req_strings: + export A -> {x==1.0, y==1.0, z==1.0} + export B -> {x==1.0, y==1.0} + export C -> {z==1.0} + + The req_strings are verified directly via TransitiveTargets (the same logic the export rule + uses). The export plumbing is verified with target D (a standalone source target with no + python_requirement deps), so req_strings is empty — this exercises the early-return path in + _setup_pex_requirements without requiring a PEX-native JSON lockfile. """ vinfo = sys.version_info current_interpreter = f"{vinfo.major}.{vinfo.minor}.{vinfo.micro}" rule_runner.write_files( { - # foo has no python_requirement deps — its transitive closure has no 3rd-party reqs. - "src/foo/BUILD": dedent( + "src/a/__init__.py": "", + "src/a/BUILD": dedent( """\ - python_sources(name='foo', resolve='a') - python_requirement(name='req_unrelated', requirements=['ansicolors==1.1.8'], resolve='a') + python_sources(name='A', resolve='r', dependencies=['src/b:B', 'src/c:C']) """ ), - "src/foo/__init__.py": "", - "lock.txt": "ansicolors==1.1.8", + "src/b/__init__.py": "", + "src/b/BUILD": dedent( + """\ + python_sources(name='B', resolve='r', dependencies=[':req_x']) + python_requirement(name='req_x', requirements=['x==1.0'], resolve='r', dependencies=[':req_y']) + python_requirement(name='req_y', requirements=['y==1.0'], resolve='r') + """ + ), + "src/c/__init__.py": "", + "src/c/BUILD": dedent( + """\ + python_sources(name='C', resolve='r', dependencies=[':req_z']) + python_requirement(name='req_z', requirements=['z==1.0'], resolve='r') + """ + ), + # Target D has no python_requirement deps — its req_strings is empty, so PEX + # subsetting is not invoked and the plain-text lockfile is accepted. + "src/d/__init__.py": "", + "src/d/BUILD": "python_sources(name='D', resolve='r')", + # Plain-text lockfile works here because the export plumbing assertion uses + # target D (empty req_strings → early return before PEX JSON parsing). + "lock.txt": "x==1.0\ny==1.0\nz==1.0", } ) rule_runner.set_options( [ *pants_args_for_python_lockfiles, f"--python-interpreter-constraints=['=={current_interpreter}']", - "--python-resolves={'a': 'lock.txt'}", - "--export-resolve=a", + "--python-resolves={'r': 'lock.txt'}", + "--export-resolve=r", "--source-root-patterns=['src']", ], env_inherit={"PATH", "PYENV_ROOT"}, ) - # Fetch only the source target (not the unrelated requirement). - targets = rule_runner.request( - Targets, - [ - RawSpecs( - address_literals=(AddressLiteralSpec("src/foo", "foo"),), - description_of_origin="test", + def get_targets(*specs: str) -> Targets: + return rule_runner.request( + Targets, + [ + RawSpecs( + address_literals=tuple( + AddressLiteralSpec(*s.rsplit(":", 1)) for s in specs + ), + description_of_origin="test", + ) + ], + ) + + def req_strings_for(targets: Targets) -> set[str]: + tt = rule_runner.request( + TransitiveTargets, + [TransitiveTargetsRequest([t.address for t in targets])], + ) + return set( + PexRequirements.req_strings_from_requirement_fields( + tgt[PythonRequirementsField] + for tgt in tt.closure + if tgt.has_field(PythonRequirementsField) ) - ], - ) - assert len(targets) == 1 + ) + + targets_A = get_targets("src/a:A") + targets_B = get_targets("src/b:B") + targets_C = get_targets("src/c:C") + + # Verify transitive-closure req_strings: this is the core filtering logic. + assert req_strings_for(targets_A) == {"x==1.0", "y==1.0", "z==1.0"} + assert req_strings_for(targets_B) == {"x==1.0", "y==1.0"} + assert req_strings_for(targets_C) == {"z==1.0"} - all_results = rule_runner.request(ExportResults, [ExportVenvsRequest(targets=targets)]) + # Verify the export plumbing: target D has no transitive python_requirement deps so + # req_strings is empty and the early-return path is taken (no PEX JSON lockfile needed). + targets_D = get_targets("src/d:D") + assert req_strings_for(targets_D) == set() + all_results = rule_runner.request(ExportResults, [ExportVenvsRequest(targets=targets_D)]) assert len(all_results) == 1 result = all_results[0] - assert result.resolve == "a" - assert result.reldir == f"python/virtualenvs/a/{current_interpreter}" + assert result.resolve == "r" + assert result.reldir == f"python/virtualenvs/r/{current_interpreter}" def test_export_codegen_outputs(): From 560bf17c15a524a68b9a5b0420eb0166902caabb Mon Sep 17 00:00:00 2001 From: Yi Cheng Date: Wed, 10 Jun 2026 22:22:34 +0000 Subject: [PATCH 3/7] Fix import sort order and remove unused TransitiveTargets import Co-Authored-By: Claude Sonnet 4.6 --- src/python/pants/backend/python/goals/export.py | 3 +-- src/python/pants/backend/python/goals/export_test.py | 8 +++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/python/pants/backend/python/goals/export.py b/src/python/pants/backend/python/goals/export.py index 1c6120e8a86..1331f18f2c4 100644 --- a/src/python/pants/backend/python/goals/export.py +++ b/src/python/pants/backend/python/goals/export.py @@ -51,6 +51,7 @@ from pants.core.goals.resolves import ExportableTool from pants.core.util_rules.source_files import SourceFiles from pants.core.util_rules.stripped_source_files import strip_source_roots +from pants.engine.addresses import Addresses from pants.engine.engine_aware import EngineAwareParameter from pants.engine.fs import CreateDigest, FileContent from pants.engine.internals.graph import hydrate_sources, transitive_targets @@ -59,12 +60,10 @@ from pants.engine.intrinsics import add_prefix, create_digest, digest_to_snapshot, merge_digests from pants.engine.process import Process, ProcessCacheScope, execute_process_or_raise from pants.engine.rules import collect_rules, implicitly, rule -from pants.engine.addresses import Addresses from pants.engine.target import ( AllTargets, HydrateSourcesRequest, SourcesField, - TransitiveTargets, TransitiveTargetsRequest, ) from pants.engine.unions import UnionMembership, UnionRule diff --git a/src/python/pants/backend/python/goals/export_test.py b/src/python/pants/backend/python/goals/export_test.py index eeb8b205392..b0f64fca568 100644 --- a/src/python/pants/backend/python/goals/export_test.py +++ b/src/python/pants/backend/python/goals/export_test.py @@ -15,14 +15,14 @@ from pants.backend.python.macros.python_artifact import PythonArtifact from pants.backend.python.target_types import ( PythonDistribution, - PythonRequirementTarget, PythonRequirementsField, + PythonRequirementTarget, PythonResolveField, PythonSourceField, PythonSourcesGeneratorTarget, ) -from pants.backend.python.util_rules.pex_requirements import PexRequirements from pants.backend.python.util_rules import local_dists_pep660, pex_from_targets +from pants.backend.python.util_rules.pex_requirements import PexRequirements from pants.base.specs import AddressLiteralSpec, RawSpecs from pants.core.goals.export import ExportResults from pants.core.util_rules import distdir @@ -266,9 +266,7 @@ def get_targets(*specs: str) -> Targets: Targets, [ RawSpecs( - address_literals=tuple( - AddressLiteralSpec(*s.rsplit(":", 1)) for s in specs - ), + address_literals=tuple(AddressLiteralSpec(*s.rsplit(":", 1)) for s in specs), description_of_origin="test", ) ], From 1c4dd9bca7a6f2158382d61197a3bcfa134d23e6 Mon Sep 17 00:00:00 2001 From: Yi Cheng Date: Wed, 10 Jun 2026 22:29:17 +0000 Subject: [PATCH 4/7] Add release notes for export goal target filtering Co-Authored-By: Claude Sonnet 4.6 --- docs/notes/2.33.x.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/notes/2.33.x.md b/docs/notes/2.33.x.md index af3a975cede..0c9d87c22a3 100644 --- a/docs/notes/2.33.x.md +++ b/docs/notes/2.33.x.md @@ -69,6 +69,13 @@ All version of [Ruff](https://docs.astral.sh/ruff/) from [0.15.6](https://github Fixed a bug where `--sync`-ing a Pex lockfile with legacy metadata headers failed. +The `export` goal now accepts target specs alongside `--resolve`. When targets are provided, the exported virtualenv contains only the third-party requirements transitively needed by those targets instead of the entire resolve. When no targets are provided, the existing full-resolve behavior is unchanged. + +``` +# Export only the packages needed by my_service (not the entire resolve) +pants export --resolve=python-default src/python/my_service:my_service +``` + #### Protobuf Upgraded the default version of `buf` to v1.69.0 (previously v1.3.0). From 1538d5b1892b8bc1f4a7feaac4732326c84af969 Mon Sep 17 00:00:00 2001 From: Yi Cheng Date: Wed, 10 Jun 2026 22:37:02 +0000 Subject: [PATCH 5/7] Add assertion that combining all targets gives the full set of requirements Co-Authored-By: Claude Sonnet 4.6 --- src/python/pants/backend/python/goals/export_test.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/python/pants/backend/python/goals/export_test.py b/src/python/pants/backend/python/goals/export_test.py index b0f64fca568..18322fa58de 100644 --- a/src/python/pants/backend/python/goals/export_test.py +++ b/src/python/pants/backend/python/goals/export_test.py @@ -294,6 +294,11 @@ def req_strings_for(targets: Targets) -> set[str]: assert req_strings_for(targets_B) == {"x==1.0", "y==1.0"} assert req_strings_for(targets_C) == {"z==1.0"} + # Combining B and C covers the full set of requirements — same as what no-target filtering + # would export (the union of all transitive deps across the resolve). + targets_B_and_C = get_targets("src/b:B", "src/c:C") + assert req_strings_for(targets_B_and_C) == {"x==1.0", "y==1.0", "z==1.0"} + # Verify the export plumbing: target D has no transitive python_requirement deps so # req_strings is empty and the early-return path is taken (no PEX JSON lockfile needed). targets_D = get_targets("src/d:D") From 81795c1a136899d9713a0c44a7cc9ffff0ec8646 Mon Sep 17 00:00:00 2001 From: Yi Cheng Date: Wed, 10 Jun 2026 23:05:02 +0000 Subject: [PATCH 6/7] Raise a clear error when targets don't belong to the exported resolve Also filter transitive req_strings by resolve to exclude shared deps that may appear in multiple resolves' closures. Co-Authored-By: Claude Sonnet 4.6 --- .../pants/backend/python/goals/export.py | 14 ++++++++ .../pants/backend/python/goals/export_test.py | 34 +++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/src/python/pants/backend/python/goals/export.py b/src/python/pants/backend/python/goals/export.py index 1331f18f2c4..1f5425a4360 100644 --- a/src/python/pants/backend/python/goals/export.py +++ b/src/python/pants/backend/python/goals/export.py @@ -558,10 +558,24 @@ async def export_virtualenv_for_resolve( transitive_tgts = await transitive_targets( TransitiveTargetsRequest(request.addresses), **implicitly() ) + wrong_resolve = [ + tgt.address + for tgt in transitive_tgts.roots + if tgt.has_field(PythonResolveField) + and tgt[PythonResolveField].normalized_value(python_setup) != resolve + ] + if wrong_resolve: + raise ExportError( + f"The following targets do not belong to the resolve `{resolve}` " + f"and cannot be used to filter its export:\n" + + "\n".join(f" {addr}" for addr in wrong_resolve) + + f"\nEnsure all targets belong to the `{resolve}` resolve." + ) req_strings = PexRequirements.req_strings_from_requirement_fields( tgt[PythonRequirementsField] for tgt in transitive_tgts.closure if tgt.has_field(PythonRequirementsField) + and tgt[PythonResolveField].normalized_value(python_setup) == resolve ) requirements: PexRequirements | EntireLockfile = PexRequirements( req_strings, diff --git a/src/python/pants/backend/python/goals/export_test.py b/src/python/pants/backend/python/goals/export_test.py index 18322fa58de..e9777af8f02 100644 --- a/src/python/pants/backend/python/goals/export_test.py +++ b/src/python/pants/backend/python/goals/export_test.py @@ -310,6 +310,40 @@ def req_strings_for(targets: Targets) -> set[str]: assert result.reldir == f"python/virtualenvs/r/{current_interpreter}" +def test_export_venv_filtered_wrong_resolve(rule_runner: RuleRunner) -> None: + """Passing a target from the wrong resolve should raise a clear error.""" + vinfo = sys.version_info + current_interpreter = f"{vinfo.major}.{vinfo.minor}.{vinfo.micro}" + rule_runner.write_files( + { + "src/a/__init__.py": "", + "src/a/BUILD": "python_sources(name='A', resolve='r1')", + "lock.txt": "", + } + ) + rule_runner.set_options( + [ + *pants_args_for_python_lockfiles, + f"--python-interpreter-constraints=['=={current_interpreter}']", + "--python-resolves={'r1': 'lock.txt', 'r2': 'lock.txt'}", + "--export-resolve=r2", + "--source-root-patterns=['src']", + ], + env_inherit={"PATH", "PYENV_ROOT"}, + ) + targets = rule_runner.request( + Targets, + [ + RawSpecs( + address_literals=(AddressLiteralSpec("src/a", "A"),), + description_of_origin="test", + ) + ], + ) + with pytest.raises(Exception, match="do not belong to the resolve `r2`"): + rule_runner.request(ExportResults, [ExportVenvsRequest(targets=targets)]) + + def test_export_codegen_outputs(): class CodegenSourcesField(SingleSourceField): pass From e29212288ae13bb5dda83274de2bbc628441590d Mon Sep 17 00:00:00 2001 From: Yi Cheng Date: Wed, 10 Jun 2026 23:14:11 +0000 Subject: [PATCH 7/7] Fix mypy error: use keyword args for AddressLiteralSpec construction Co-Authored-By: Claude Sonnet 4.6 --- src/python/pants/backend/python/goals/export_test.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/python/pants/backend/python/goals/export_test.py b/src/python/pants/backend/python/goals/export_test.py index e9777af8f02..efcab64f50c 100644 --- a/src/python/pants/backend/python/goals/export_test.py +++ b/src/python/pants/backend/python/goals/export_test.py @@ -266,7 +266,11 @@ def get_targets(*specs: str) -> Targets: Targets, [ RawSpecs( - address_literals=tuple(AddressLiteralSpec(*s.rsplit(":", 1)) for s in specs), + address_literals=tuple( + AddressLiteralSpec(path_component=p, target_component=t) + for s in specs + for p, t in [s.rsplit(":", 1)] + ), description_of_origin="test", ) ],