• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pantsbuild / pants / 18812500213

26 Oct 2025 03:42AM UTC coverage: 80.284% (+0.005%) from 80.279%
18812500213

Pull #22804

github

web-flow
Merge 2a56fdb46 into 4834308dc
Pull Request #22804: test_shell_command: use correct default cache scope for a test's environment

29 of 31 new or added lines in 2 files covered. (93.55%)

1314 existing lines in 64 files now uncovered.

77900 of 97030 relevant lines covered (80.28%)

3.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

77.65
/src/python/pants/engine/internals/synthetic_targets.py
1
# Copyright 2022 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
"""Synthetic targets is a concept of injecting targets into the build graph that doesn't have a home
5
in any BUILD file.
6

7
This is achieved by declaring a union member for the `SyntheticTargetsRequest` union along
8
with a rule taking that member and returning a collection of `SyntheticAddressMap`s.
9

10
Example demonstrating how to register synthetic targets:
11

12
    from dataclasses import dataclass
13
    from pants.engine.internals.synthetic_targets import (
14
        SyntheticAddressMaps,
15
        SyntheticTargetsRequest,
16
        SyntheticTargetsSpecPaths,
17
    )
18
    from pants.engine.internals.target_adaptor import TargetAdaptor
19
    from pants.engine.rules import collect_rules, rule
20

21

22
    @dataclass(frozen=True)
23
    class SyntheticExampleTargetsRequest(SyntheticTargetsRequest):
24
        path: str = SyntheticTargetsRequest.SINGLE_REQUEST_FOR_ALL_TARGETS
25

26

27
    class SyntheticExampleTargetsPerDirectorySpecPathsRequest:
28
        pass
29

30

31
    @dataclass(frozen=True)
32
    class SyntheticExampleTargetsPerDirectoryRequest(SyntheticTargetsRequest):
33
        path: str = SyntheticTargetsRequest.REQUEST_TARGETS_PER_DIRECTORY
34

35
        # Optional: (without it, only paths with BUILD files will be consistently considered)
36
        spec_paths_request = SyntheticExampleTargetsPerDirectorySpecPathsRequest
37

38

39
    @rule
40
    async def example_synthetic_targets_per_directory_spec_paths(
41
        request: SyntheticExampleTargetsPerDirectorySpecPathsRequest,
42
    ) -> SyntheticTargetsSpecPaths:
43
        # Return all paths we have targets for.
44
        # This may involve using GlobPaths etc to discover files in the project source tree.
45
        known_paths = ["src/a/dir1", "src/a/dir2", ...]
46
        return SyntheticTargetsSpecPaths.from_paths(known_paths)
47

48

49
    @rule
50
    async def example_synthetic_targets(request: SyntheticExampleTargetsRequest) -> SyntheticAddressMaps:
51
        # Return targets for `request.path`.
52
        return SyntheticAddressMaps.for_targets_request(
53
            request,
54
            [
55
                (
56
                  "BUILD.synthetic-example",
57
                  (
58
                    TargetAdaptor("<type-alias>", "<name>", description_of_origin="...", **kwargs),
59
                    ...
60
                  ),
61
                ),
62
                ...
63
            ]
64
        )
65

66

67
    def rules():
68
        return (
69
            *collect_rules(),
70
            SyntheticExampleTargetsRequest.rules(),
71
            SyntheticExampleTargetsPerDirectoryRequest.rules(),
72
            ...
73
        )
74
"""
75

76
from __future__ import annotations
12✔
77

78
import itertools
12✔
79
import os.path
12✔
80
from collections.abc import Iterable, Iterator, Sequence
12✔
81
from dataclasses import dataclass
12✔
82
from typing import ClassVar
12✔
83

84
from pants.base.specs import GlobSpecsProtocol
12✔
85
from pants.engine.collection import Collection
12✔
86
from pants.engine.internals.mapper import AddressMap
12✔
87
from pants.engine.internals.selectors import concurrently
12✔
88
from pants.engine.internals.target_adaptor import TargetAdaptor
12✔
89
from pants.engine.rules import collect_rules, implicitly, rule
12✔
90
from pants.engine.unions import UnionMembership, UnionRule, union
12✔
91
from pants.util.frozendict import FrozenDict
12✔
92

93

94
@dataclass(frozen=True)
12✔
95
class SyntheticTargetsSpecPathsRequest:
12✔
96
    specs: tuple[GlobSpecsProtocol, ...]
12✔
97

98

99
class SyntheticTargetsSpecPaths(Collection[str]):
12✔
100
    @staticmethod
12✔
101
    def from_paths(paths: Iterable[str]) -> SyntheticTargetsSpecPaths:
12✔
102
        return SyntheticTargetsSpecPaths(sorted(set(paths)))
×
103

104
    @staticmethod
12✔
105
    def from_request(
12✔
106
        request: SyntheticTargetsSpecPathsRequest, paths: Iterable[str]
107
    ) -> SyntheticTargetsSpecPaths:
108
        return SyntheticTargetsSpecPaths.from_paths(
×
109
            filter(
110
                lambda path: any(spec.matches_target_residence_dir(path) for spec in request.specs),
111
                paths,
112
            )
113
        )
114

115

116
@union
12✔
117
class _SpecPathsRequest:
12✔
118
    """Protected union type."""
119

120

121
@rule(polymorphic=True)
12✔
122
async def _get_spec_paths(req: _SpecPathsRequest) -> SyntheticTargetsSpecPaths:
12✔
123
    raise NotImplementedError()
×
124

125

126
@union
12✔
127
@dataclass(frozen=True)
12✔
128
class SyntheticTargetsRequest:
12✔
129
    """Union members of the `SyntheticTargetsRequest` should implement a rule returning an instance
130
    of a `SyntheticAddressMaps`."""
131

132
    REQUEST_TARGETS_PER_DIRECTORY: ClassVar[str] = "*"
12✔
133
    SINGLE_REQUEST_FOR_ALL_TARGETS: ClassVar[str] = ""
12✔
134

135
    path: str = REQUEST_TARGETS_PER_DIRECTORY
12✔
136
    """
12✔
137
    The default field value is used to filter which paths to request targets for, and should be
138
    declared as appropriate by union members subclassing `SyntheticTargetsRequest`. The
139
    SINGLE_REQUEST_FOR_ALL_TARGETS will make a single request for all targets at once, while
140
    REQUEST_TARGETS_PER_DIRECTORY will request all targets for a particular path at a time. Any
141
    other value will be used as filter to only request targets for that specific directory.
142

143
    The rule processing this request should inspect `request.path` to only include targets for that
144
    directory, unless `request.path` is `SyntheticTargetsRequest.SINGLE_REQUEST_FOR_ALL_TARGETS` in
145
    which case _all_ synthetic targets should be returned.
146
    """
147

148
    spec_paths_request: ClassVar[type | None] = None
12✔
149
    """Request class for providing paths in addition to those where BUILD files are found.
12✔
150

151
    Implement a rule that takes `spec_paths_request` and returns an `SyntheticTargetsSpecPaths`.
152
    """
153

154
    @classmethod
12✔
155
    def rules(cls) -> Iterator[UnionRule]:
12✔
UNCOV
156
        yield UnionRule(SyntheticTargetsRequest, cls)
1✔
UNCOV
157
        if cls.spec_paths_request is not None:
1✔
UNCOV
158
            yield UnionRule(_SpecPathsRequest, cls.spec_paths_request)
1✔
159

160

161
class SyntheticAddressMap(AddressMap):
12✔
162
    pass
12✔
163

164

165
@dataclass(frozen=True)
12✔
166
class SyntheticAddressMapsRequest:
12✔
167
    """Request all registered synthetic targets for a given path."""
168

169
    path: str
12✔
170

171

172
class SyntheticAddressMaps(Collection[SyntheticAddressMap]):
12✔
173
    """A collection of `SyntheticAddressMap` for all synthetic target adaptors."""
174

175
    @classmethod
12✔
176
    def for_targets_request(
12✔
177
        cls,
178
        request: SyntheticTargetsRequest,
179
        synthetic_target_adaptors: Iterable[tuple[str, Iterable[TargetAdaptor]]],
180
    ) -> SyntheticAddressMaps:
181
        return cls(
×
182
            SyntheticAddressMap.create(os.path.join(request.path, filename), target_adaptors)
183
            for filename, target_adaptors in synthetic_target_adaptors
184
        )
185

186

187
@rule(polymorphic=True)
12✔
188
async def _get_synthetic_address_maps(req: SyntheticTargetsRequest) -> SyntheticAddressMaps:
12✔
189
    raise NotImplementedError()
×
190

191

192
@dataclass(frozen=True)
12✔
193
class AllSyntheticAddressMaps:
12✔
194
    """All pre-loaded SyntheticAddressMaps per directory."""
195

196
    address_maps: FrozenDict[str, SyntheticAddressMaps]
12✔
197
    path_request_types: FrozenDict[str, Sequence[type[SyntheticTargetsRequest]]]
12✔
198
    spec_paths: Sequence[str]
12✔
199

200
    @classmethod
12✔
201
    def create(
12✔
202
        cls,
203
        address_maps: Iterable[SyntheticAddressMap],
204
        requests: Iterable[SyntheticTargetsRequest],
205
        spec_paths: Iterable[str],
206
    ) -> AllSyntheticAddressMaps:
207
        def address_map_key(address_map: SyntheticAddressMap) -> str:
×
208
            return os.path.dirname(address_map.path)
×
209

210
        def requests_key(request: SyntheticAddressMapsRequest) -> str:
×
211
            return request.path
×
212

213
        return AllSyntheticAddressMaps(
×
214
            address_maps=FrozenDict(
215
                {
216
                    path: SyntheticAddressMaps(address_maps_group)
217
                    for path, address_maps_group in itertools.groupby(
218
                        sorted(address_maps, key=address_map_key), key=address_map_key
219
                    )
220
                }
221
            ),
222
            path_request_types=FrozenDict(
223
                {
224
                    path: tuple(type(request) for request in requests_group)  # type: ignore[misc]
225
                    for path, requests_group in itertools.groupby(
226
                        sorted(requests, key=requests_key),  # type: ignore[arg-type]
227
                        key=requests_key,  # type: ignore[arg-type]
228
                    )
229
                    if path != SyntheticTargetsRequest.SINGLE_REQUEST_FOR_ALL_TARGETS
230
                }
231
            ),
232
            spec_paths=tuple(sorted(spec_paths)),
233
        )
234

235
    def targets_request_types(self, path: str) -> Iterable[type[SyntheticTargetsRequest]]:
12✔
236
        yield from self.path_request_types.get(
×
237
            SyntheticTargetsRequest.REQUEST_TARGETS_PER_DIRECTORY, ()
238
        )
239
        yield from self.path_request_types.get(path, ())
×
240

241

242
@rule
12✔
243
async def get_synthetic_address_maps(
12✔
244
    request: SyntheticAddressMapsRequest,
245
    all_synthetic: AllSyntheticAddressMaps,
246
) -> SyntheticAddressMaps:
247
    per_directory_address_maps = await concurrently(
×
248
        _get_synthetic_address_maps(
249
            **implicitly({request_type(request.path): SyntheticTargetsRequest})
250
        )
251
        for request_type in all_synthetic.targets_request_types(request.path)
252
    )
253

254
    return SyntheticAddressMaps(
×
255
        itertools.chain(
256
            all_synthetic.address_maps.get(request.path, ()), *per_directory_address_maps
257
        )
258
    )
259

260

261
@rule
12✔
262
async def all_synthetic_targets(union_membership: UnionMembership) -> AllSyntheticAddressMaps:
12✔
263
    requests = [request_type() for request_type in union_membership.get(SyntheticTargetsRequest)]
×
264
    all_synthetic = await concurrently(
×
265
        _get_synthetic_address_maps(**implicitly({request: SyntheticTargetsRequest}))
266
        for request in requests
267
        if request.path == SyntheticTargetsRequest.SINGLE_REQUEST_FOR_ALL_TARGETS
268
    )
269
    all_spec_paths = await concurrently(
×
270
        _get_spec_paths(
271
            **implicitly({spec_paths_request(): _SpecPathsRequest}),
272
        )
273
        for spec_paths_request in union_membership.get(_SpecPathsRequest)
274
    )
275
    return AllSyntheticAddressMaps.create(
×
276
        address_maps=itertools.chain.from_iterable(all_synthetic),
277
        requests=requests,
278
        spec_paths=set(itertools.chain.from_iterable(all_spec_paths)),
279
    )
280

281

282
@rule
12✔
283
async def get_synthetic_targets_spec_paths(
12✔
284
    request: SyntheticTargetsSpecPathsRequest, all_synthetic: AllSyntheticAddressMaps
285
) -> SyntheticTargetsSpecPaths:
286
    """Return all known paths for synthetic targets."""
287
    return SyntheticTargetsSpecPaths.from_request(
×
288
        request, itertools.chain(all_synthetic.address_maps, all_synthetic.spec_paths)
289
    )
290

291

292
def rules():
12✔
293
    return collect_rules()
12✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc