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

pantsbuild / pants / 19015773527

02 Nov 2025 05:33PM UTC coverage: 17.872% (-62.4%) from 80.3%
19015773527

Pull #22816

github

web-flow
Merge a12d75757 into 6c024e162
Pull Request #22816: Update Pants internal Python to 3.14

4 of 5 new or added lines in 3 files covered. (80.0%)

28452 existing lines in 683 files now uncovered.

9831 of 55007 relevant lines covered (17.87%)

0.18 hits per line

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

0.0
/src/python/pants/bsp/util_rules/targets.py
1
# Copyright 2022 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
UNCOV
3
from __future__ import annotations
×
4

UNCOV
5
import itertools
×
UNCOV
6
import logging
×
UNCOV
7
from collections import defaultdict
×
UNCOV
8
from collections.abc import Sequence
×
UNCOV
9
from dataclasses import dataclass
×
UNCOV
10
from pathlib import Path
×
UNCOV
11
from typing import ClassVar, Generic, TypeVar
×
12

UNCOV
13
import toml
×
14

UNCOV
15
from pants.base.build_root import BuildRoot
×
UNCOV
16
from pants.base.glob_match_error_behavior import GlobMatchErrorBehavior
×
UNCOV
17
from pants.base.specs import RawSpecs, RawSpecsWithoutFileOwners
×
UNCOV
18
from pants.base.specs_parser import SpecsParser
×
UNCOV
19
from pants.bsp.goal import BSPGoal
×
UNCOV
20
from pants.bsp.protocol import BSPHandlerMapping
×
UNCOV
21
from pants.bsp.spec.base import (
×
22
    BSPData,
23
    BuildTarget,
24
    BuildTargetCapabilities,
25
    BuildTargetIdentifier,
26
    StatusCode,
27
    TaskId,
28
    Uri,
29
)
UNCOV
30
from pants.bsp.spec.targets import (
×
31
    DependencyModule,
32
    DependencyModulesItem,
33
    DependencyModulesParams,
34
    DependencyModulesResult,
35
    DependencySourcesItem,
36
    DependencySourcesParams,
37
    DependencySourcesResult,
38
    SourceItem,
39
    SourceItemKind,
40
    SourcesItem,
41
    SourcesParams,
42
    SourcesResult,
43
    WorkspaceBuildTargetsParams,
44
    WorkspaceBuildTargetsResult,
45
)
UNCOV
46
from pants.engine.environment import EnvironmentName
×
UNCOV
47
from pants.engine.fs import PathGlobs, Workspace
×
UNCOV
48
from pants.engine.internals.graph import resolve_source_paths, resolve_targets
×
UNCOV
49
from pants.engine.internals.native_engine import EMPTY_DIGEST, Digest, MergeDigests
×
UNCOV
50
from pants.engine.internals.selectors import concurrently
×
UNCOV
51
from pants.engine.intrinsics import get_digest_contents, merge_digests
×
UNCOV
52
from pants.engine.rules import _uncacheable_rule, collect_rules, implicitly, rule
×
UNCOV
53
from pants.engine.target import (
×
54
    Field,
55
    FieldDefaults,
56
    FieldSet,
57
    SourcesField,
58
    SourcesPathsRequest,
59
    Targets,
60
)
UNCOV
61
from pants.engine.unions import UnionMembership, UnionRule, union
×
UNCOV
62
from pants.source.source_root import SourceRootsRequest, get_source_roots
×
UNCOV
63
from pants.util.frozendict import FrozenDict
×
UNCOV
64
from pants.util.ordered_set import OrderedSet
×
UNCOV
65
from pants.util.strutil import bullet_list
×
66

UNCOV
67
_logger = logging.getLogger(__name__)
×
68

UNCOV
69
_FS = TypeVar("_FS", bound=FieldSet)
×
70

71

UNCOV
72
@union(in_scope_types=[EnvironmentName])
×
UNCOV
73
@dataclass(frozen=True)
×
UNCOV
74
class BSPBuildTargetsMetadataRequest(Generic[_FS]):
×
75
    """Hook to allow language backends to provide metadata for BSP build targets."""
76

UNCOV
77
    language_id: ClassVar[str]
×
UNCOV
78
    can_merge_metadata_from: ClassVar[tuple[str, ...]]
×
UNCOV
79
    field_set_type: ClassVar[type[_FS]]
×
80

UNCOV
81
    resolve_prefix: ClassVar[str]
×
UNCOV
82
    resolve_field: ClassVar[type[Field]]
×
83

UNCOV
84
    field_sets: tuple[_FS, ...]
×
85

86

UNCOV
87
@dataclass(frozen=True)
×
UNCOV
88
class BSPBuildTargetsMetadataResult:
×
89
    """Response type for a BSPBuildTargetsMetadataRequest."""
90

91
    # Metadata for the `data` field of the final `BuildTarget`.
UNCOV
92
    metadata: BSPData | None = None
×
93

94
    # Output to write into `.pants.d/bsp` for access by IDE.
UNCOV
95
    digest: Digest = EMPTY_DIGEST
×
96

97

UNCOV
98
@rule(polymorphic=True)
×
UNCOV
99
async def get_bsp_build_targets_metadata(
×
100
    req: BSPBuildTargetsMetadataRequest, env_name: EnvironmentName
101
) -> BSPBuildTargetsMetadataResult:
102
    raise NotImplementedError()
×
103

104

UNCOV
105
@dataclass(frozen=True)
×
UNCOV
106
class BSPTargetDefinition:
×
UNCOV
107
    display_name: str | None
×
UNCOV
108
    base_directory: str | None
×
UNCOV
109
    addresses: tuple[str, ...]
×
UNCOV
110
    resolve_filter: str | None
×
111

112

UNCOV
113
@dataclass(frozen=True)
×
UNCOV
114
class BSPBuildTargetInternal:
×
UNCOV
115
    name: str
×
UNCOV
116
    specs: RawSpecs
×
UNCOV
117
    definition: BSPTargetDefinition
×
118

UNCOV
119
    @property
×
UNCOV
120
    def bsp_target_id(self) -> BuildTargetIdentifier:
×
121
        return BuildTargetIdentifier(f"pants:{self.name}")
×
122

123

UNCOV
124
@dataclass(frozen=True)
×
UNCOV
125
class BSPBuildTargetSourcesInfo:
×
126
    """Source files and roots for a BSP build target.
127

128
    It is a separate class so that it is computed lazily only when called for by an RPC call.
129
    """
130

UNCOV
131
    source_files: frozenset[str]
×
UNCOV
132
    source_roots: frozenset[str]
×
133

134

UNCOV
135
@dataclass(frozen=True)
×
UNCOV
136
class BSPBuildTargets:
×
UNCOV
137
    targets_mapping: FrozenDict[str, BSPBuildTargetInternal]
×
138

139

UNCOV
140
@dataclass(frozen=True)
×
UNCOV
141
class _ParseOneBSPMappingRequest:
×
UNCOV
142
    name: str
×
UNCOV
143
    definition: BSPTargetDefinition
×
144

145

UNCOV
146
@rule
×
UNCOV
147
async def parse_one_bsp_mapping(request: _ParseOneBSPMappingRequest) -> BSPBuildTargetInternal:
×
148
    specs_parser = SpecsParser()
×
149
    specs = specs_parser.parse_specs(
×
150
        request.definition.addresses, description_of_origin=f"the BSP mapping {request.name}"
151
    ).includes
152
    return BSPBuildTargetInternal(request.name, specs, request.definition)
×
153

154

UNCOV
155
@rule
×
UNCOV
156
async def materialize_bsp_build_targets(bsp_goal: BSPGoal) -> BSPBuildTargets:
×
157
    definitions: dict[str, BSPTargetDefinition] = {}
×
158
    for config_file in bsp_goal.groups_config_files:
×
159
        config_contents = await get_digest_contents(
×
160
            **implicitly(
161
                PathGlobs(
162
                    [config_file],
163
                    glob_match_error_behavior=GlobMatchErrorBehavior.error,
164
                    description_of_origin=f"BSP config file `{config_file}`",
165
                )
166
            )
167
        )
168
        if len(config_contents) == 0:
×
169
            raise ValueError(f"BSP targets config file `{config_file}` does not exist.")
×
170
        elif len(config_contents) > 1:
×
171
            raise ValueError(
×
172
                f"BSP targets config file specified as `{config_file}` matches multiple files. "
173
                "Please do not use wildcards in config file paths."
174
            )
175

176
        config = toml.loads(config_contents[0].content.decode())
×
177

178
        groups = config.get("groups")
×
179
        if groups is None:
×
180
            raise ValueError(
×
181
                f"BSP targets config file `{config_file}` is missing the `groups` table."
182
            )
183
        if not isinstance(groups, dict):
×
184
            raise ValueError(
×
185
                f"BSP targets config file `{config_file}` contains a `groups` key that is not a TOML table."
186
            )
187

188
        for id, group in groups.items():
×
189
            if not isinstance(group, dict):
×
190
                raise ValueError(
×
191
                    f"BSP targets config file `{config_file}` contains an entry for "
192
                    "`groups` array that is not a dictionary (index={i})."
193
                )
194

195
            base_directory = group.get("base_directory")
×
196
            display_name = group.get("display_name")
×
197
            addresses = group.get("addresses", [])
×
198
            if not addresses:
×
199
                raise ValueError(
×
200
                    f"BSP targets config file `{config_file}` contains group ID `{id}` which has "
201
                    "no address specs defined via the `addresses` key. Please specify at least "
202
                    "one address spec."
203
                )
204

205
            resolve_filter = group.get("resolve")
×
206

207
            definitions[id] = BSPTargetDefinition(
×
208
                display_name=display_name,
209
                base_directory=base_directory,
210
                addresses=tuple(addresses),
211
                resolve_filter=resolve_filter,
212
            )
213

214
    bsp_internal_targets = await concurrently(
×
215
        parse_one_bsp_mapping(_ParseOneBSPMappingRequest(name, definition))
216
        for name, definition in definitions.items()
217
    )
218
    target_mapping = dict(zip(definitions.keys(), bsp_internal_targets))
×
219
    return BSPBuildTargets(FrozenDict(target_mapping))
×
220

221

UNCOV
222
@rule
×
UNCOV
223
async def resolve_bsp_build_target_identifier(
×
224
    bsp_target_id: BuildTargetIdentifier, bsp_build_targets: BSPBuildTargets
225
) -> BSPBuildTargetInternal:
226
    scheme, _, target_name = bsp_target_id.uri.partition(":")
×
227
    if scheme != "pants":
×
228
        raise ValueError(f"Unknown BSP scheme `{scheme}` for BSP target ID `{bsp_target_id}.")
×
229

230
    target_internal = bsp_build_targets.targets_mapping.get(target_name)
×
231
    if not target_internal:
×
232
        raise ValueError(f"Unknown BSP target name: {target_name}")
×
233

234
    return target_internal
×
235

236

UNCOV
237
@rule
×
UNCOV
238
async def resolve_bsp_build_target_addresses(
×
239
    bsp_target: BSPBuildTargetInternal,
240
    union_membership: UnionMembership,
241
    field_defaults: FieldDefaults,
242
) -> Targets:
243
    # NB: Using `RawSpecs` directly rather than `RawSpecsWithoutFileOwners` results in a rule graph cycle.
244
    targets = await resolve_targets(
×
245
        **implicitly(RawSpecsWithoutFileOwners.from_raw_specs(bsp_target.specs))
246
    )
247
    if bsp_target.definition.resolve_filter is None:
×
248
        return targets
×
249

250
    resolve_filter = bsp_target.definition.resolve_filter
×
251
    resolve_prefix, matched, resolve_value = resolve_filter.partition(":")
×
252
    if not resolve_prefix or not matched:
×
253
        raise ValueError(
×
254
            f"The `resolve` filter for `{bsp_target}` must have a platform or language specific "
255
            f"prefix like `$lang:$filter`, but the configured value: `{resolve_filter}` did not."
256
        )
257

258
    resolve_fields = {
×
259
        impl.resolve_field
260
        for impl in union_membership.get(BSPBuildTargetsMetadataRequest)
261
        if impl.resolve_prefix == resolve_prefix
262
    }
263

264
    return Targets(
×
265
        t
266
        for t in targets
267
        if any(
268
            t.has_field(field) and field_defaults.value_or_default(t[field]) == resolve_value
269
            for field in resolve_fields
270
        )
271
    )
272

273

UNCOV
274
@rule
×
UNCOV
275
async def resolve_bsp_build_target_source_roots(
×
276
    bsp_target: BSPBuildTargetInternal,
277
) -> BSPBuildTargetSourcesInfo:
278
    targets = await resolve_bsp_build_target_addresses(bsp_target, **implicitly())
×
279
    targets_with_sources = [tgt for tgt in targets if tgt.has_field(SourcesField)]
×
280
    sources_paths = await concurrently(
×
281
        resolve_source_paths(SourcesPathsRequest(tgt[SourcesField]), **implicitly())
282
        for tgt in targets_with_sources
283
    )
284
    merged_source_files: set[str] = set()
×
285
    for sp in sources_paths:
×
286
        merged_source_files.update(sp.files)
×
287
    source_roots_result = await get_source_roots(SourceRootsRequest.for_files(merged_source_files))
×
288
    source_root_paths = {x.path for x in source_roots_result.path_to_root.values()}
×
289
    return BSPBuildTargetSourcesInfo(
×
290
        source_files=frozenset(merged_source_files),
291
        source_roots=frozenset(source_root_paths),
292
    )
293

294

295
# -----------------------------------------------------------------------------------------------
296
# Workspace Build Targets Request
297
# See https://build-server-protocol.github.io/docs/specification.html#workspace-build-targets-request
298
# -----------------------------------------------------------------------------------------------
299

300

UNCOV
301
class WorkspaceBuildTargetsHandlerMapping(BSPHandlerMapping):
×
UNCOV
302
    method_name = "workspace/buildTargets"
×
UNCOV
303
    request_type = WorkspaceBuildTargetsParams
×
UNCOV
304
    response_type = WorkspaceBuildTargetsResult
×
305

306

UNCOV
307
@dataclass(frozen=True)
×
UNCOV
308
class GenerateOneBSPBuildTargetRequest:
×
UNCOV
309
    bsp_target: BSPBuildTargetInternal
×
310

311

UNCOV
312
@dataclass(frozen=True)
×
UNCOV
313
class GenerateOneBSPBuildTargetResult:
×
UNCOV
314
    build_target: BuildTarget
×
UNCOV
315
    digest: Digest = EMPTY_DIGEST
×
316

317

UNCOV
318
def merge_metadata(
×
319
    metadata_results_by_request_type: Sequence[
320
        tuple[type[BSPBuildTargetsMetadataRequest], BSPBuildTargetsMetadataResult]
321
    ],
322
) -> BSPData | None:
323
    if not metadata_results_by_request_type:
×
324
        return None
×
325
    if len(metadata_results_by_request_type) == 1:
×
326
        return metadata_results_by_request_type[0][1].metadata
×
327

328
    # Naive algorithm (since we only support Java and Scala backends), find the metadata request type that cannot
329
    # merge from another and use that one.
330
    if len(metadata_results_by_request_type) != 2:
×
331
        raise AssertionError(
×
332
            "BSP core rules only support naive ordering of language-backend metadata. Contact Pants developers."
333
        )
334
    if not metadata_results_by_request_type[0][0].can_merge_metadata_from:
×
335
        metadata_index = 1
×
336
    elif not metadata_results_by_request_type[1][0].can_merge_metadata_from:
×
337
        metadata_index = 0
×
338
    else:
339
        raise AssertionError(
×
340
            "BSP core rules only support naive ordering of language-backend metadata. Contact Pants developers."
341
        )
342

343
    # Pretend to merge the metadata into a single piece of metadata, but really just choose the metadata
344
    # from the selected provider.
345
    return metadata_results_by_request_type[metadata_index][1].metadata
×
346

347

UNCOV
348
@rule
×
UNCOV
349
async def generate_one_bsp_build_target_request(
×
350
    request: GenerateOneBSPBuildTargetRequest,
351
    union_membership: UnionMembership,
352
    build_root: BuildRoot,
353
) -> GenerateOneBSPBuildTargetResult:
354
    # Find all Pants targets that are part of this BSP build target.
355
    targets = await resolve_bsp_build_target_addresses(request.bsp_target, **implicitly())
×
356

357
    # Determine whether the targets are compilable.
358
    can_compile = any(
×
359
        req_type.field_set_type.is_applicable(t)  # type: ignore[misc]
360
        for req_type in union_membership[BSPCompileRequest]
361
        for t in targets
362
    )
363

364
    # Classify the targets by the language backends that claim to provide metadata for them.
365
    field_sets_by_request_type: dict[type[BSPBuildTargetsMetadataRequest], OrderedSet[FieldSet]] = (
×
366
        defaultdict(OrderedSet)
367
    )
368
    metadata_request_types: Sequence[type[BSPBuildTargetsMetadataRequest]] = union_membership.get(
×
369
        BSPBuildTargetsMetadataRequest
370
    )
371
    metadata_request_types_by_lang_id: dict[str, type[BSPBuildTargetsMetadataRequest]] = {}
×
372
    for metadata_request_type in metadata_request_types:
×
373
        previous = metadata_request_types_by_lang_id.get(metadata_request_type.language_id)
×
374
        if previous:
×
375
            raise ValueError(
×
376
                f"Multiple implementations claim to support `{metadata_request_type.language_id}`:"
377
                f"{bullet_list([previous.__name__, metadata_request_type.__name__])}"
378
                "\n"
379
                "Do you have conflicting language support backends enabled?"
380
            )
381
        metadata_request_types_by_lang_id[metadata_request_type.language_id] = metadata_request_type
×
382

383
    for tgt in targets:
×
384
        for metadata_request_type in metadata_request_types:
×
385
            field_set_type: type[FieldSet] = metadata_request_type.field_set_type
×
386
            if field_set_type.is_applicable(tgt):
×
387
                field_sets_by_request_type[metadata_request_type].add(field_set_type.create(tgt))
×
388

389
    # Request each language backend to provide metadata for the BuildTarget, and then merge it.
390
    metadata_results = await concurrently(
×
391
        get_bsp_build_targets_metadata(
392
            **implicitly(
393
                {request_type(field_sets=tuple(field_sets)): BSPBuildTargetsMetadataRequest}
394
            )
395
        )
396
        for request_type, field_sets in field_sets_by_request_type.items()
397
    )
398
    metadata = merge_metadata(list(zip(field_sets_by_request_type.keys(), metadata_results)))
×
399

400
    digest = await merge_digests(MergeDigests([r.digest for r in metadata_results]))
×
401

402
    # Determine "base directory" for this build target using source roots.
403
    # TODO: This actually has nothing to do with source roots. It should probably be computed as an ancestor
404
    # directory or else be configurable by the user. It is used as a hint in IntelliJ for where to place the
405
    # corresponding IntelliJ module.
406
    source_info = await resolve_bsp_build_target_source_roots(request.bsp_target)
×
407
    if source_info.source_roots:
×
408
        roots = [build_root.pathlib_path.joinpath(p) for p in source_info.source_roots]
×
409
    else:
410
        roots = []
×
411

412
    base_directory: Path | None = None
×
413
    if request.bsp_target.definition.base_directory:
×
414
        base_directory = build_root.pathlib_path.joinpath(
×
415
            request.bsp_target.definition.base_directory
416
        )
417
    elif roots:
×
418
        base_directory = roots[0]
×
419

420
    return GenerateOneBSPBuildTargetResult(
×
421
        build_target=BuildTarget(
422
            id=BuildTargetIdentifier(f"pants:{request.bsp_target.name}"),
423
            display_name=request.bsp_target.name,
424
            base_directory=base_directory.as_uri() if base_directory else None,
425
            tags=(),
426
            capabilities=BuildTargetCapabilities(
427
                can_compile=can_compile,
428
                can_debug=False,
429
                # TODO: See https://github.com/pantsbuild/pants/issues/15050.
430
                can_run=False,
431
                can_test=False,
432
            ),
433
            language_ids=tuple(sorted(req.language_id for req in field_sets_by_request_type)),
434
            dependencies=(),
435
            data=metadata,
436
        ),
437
        digest=digest,
438
    )
439

440

UNCOV
441
@_uncacheable_rule
×
UNCOV
442
async def bsp_workspace_build_targets(
×
443
    _: WorkspaceBuildTargetsParams,
444
    bsp_build_targets: BSPBuildTargets,
445
    workspace: Workspace,
446
) -> WorkspaceBuildTargetsResult:
447
    bsp_target_results = await concurrently(
×
448
        generate_one_bsp_build_target_request(
449
            GenerateOneBSPBuildTargetRequest(target_internal), **implicitly()
450
        )
451
        for target_internal in bsp_build_targets.targets_mapping.values()
452
    )
453
    digest = await merge_digests(MergeDigests([r.digest for r in bsp_target_results]))
×
454
    if digest != EMPTY_DIGEST:
×
455
        workspace.write_digest(digest, path_prefix=".pants.d/bsp")
×
456

457
    return WorkspaceBuildTargetsResult(
×
458
        targets=tuple(r.build_target for r in bsp_target_results),
459
    )
460

461

462
# -----------------------------------------------------------------------------------------------
463
# Build Target Sources Request
464
# See https://build-server-protocol.github.io/docs/specification.html#build-target-sources-request
465
# -----------------------------------------------------------------------------------------------
466

467

UNCOV
468
class BuildTargetSourcesHandlerMapping(BSPHandlerMapping):
×
UNCOV
469
    method_name = "buildTarget/sources"
×
UNCOV
470
    request_type = SourcesParams
×
UNCOV
471
    response_type = SourcesResult
×
472

473

UNCOV
474
@dataclass(frozen=True)
×
UNCOV
475
class MaterializeBuildTargetSourcesRequest:
×
UNCOV
476
    bsp_target_id: BuildTargetIdentifier
×
477

478

UNCOV
479
@dataclass(frozen=True)
×
UNCOV
480
class MaterializeBuildTargetSourcesResult:
×
UNCOV
481
    sources_item: SourcesItem
×
482

483

UNCOV
484
@rule
×
UNCOV
485
async def materialize_bsp_build_target_sources(
×
486
    request: MaterializeBuildTargetSourcesRequest,
487
    build_root: BuildRoot,
488
) -> MaterializeBuildTargetSourcesResult:
489
    bsp_target = await resolve_bsp_build_target_identifier(request.bsp_target_id, **implicitly())
×
490
    source_info = await resolve_bsp_build_target_source_roots(bsp_target)
×
491

492
    if source_info.source_roots:
×
493
        roots = [build_root.pathlib_path.joinpath(p) for p in source_info.source_roots]
×
494
    else:
495
        roots = [build_root.pathlib_path]
×
496

497
    sources_item = SourcesItem(
×
498
        target=request.bsp_target_id,
499
        sources=tuple(
500
            SourceItem(
501
                uri=build_root.pathlib_path.joinpath(filename).as_uri(),
502
                kind=SourceItemKind.FILE,
503
                generated=False,
504
            )
505
            for filename in sorted(source_info.source_files)
506
        ),
507
        roots=tuple(r.as_uri() for r in roots),
508
    )
509

510
    return MaterializeBuildTargetSourcesResult(sources_item)
×
511

512

UNCOV
513
@rule
×
UNCOV
514
async def bsp_build_target_sources(request: SourcesParams) -> SourcesResult:
×
515
    sources_items = await concurrently(
×
516
        materialize_bsp_build_target_sources(
517
            MaterializeBuildTargetSourcesRequest(btgt), **implicitly()
518
        )
519
        for btgt in request.targets
520
    )
521
    return SourcesResult(items=tuple(si.sources_item for si in sources_items))
×
522

523

524
# -----------------------------------------------------------------------------------------------
525
# Dependency Sources Request
526
# See https://build-server-protocol.github.io/docs/specification.html#dependency-sources-request
527
# -----------------------------------------------------------------------------------------------
528

529

UNCOV
530
class DependencySourcesHandlerMapping(BSPHandlerMapping):
×
UNCOV
531
    method_name = "buildTarget/dependencySources"
×
UNCOV
532
    request_type = DependencySourcesParams
×
UNCOV
533
    response_type = DependencySourcesResult
×
534

535

UNCOV
536
@rule
×
UNCOV
537
async def bsp_dependency_sources(request: DependencySourcesParams) -> DependencySourcesResult:
×
538
    # TODO: This is a stub.
539
    return DependencySourcesResult(
×
540
        tuple(DependencySourcesItem(target=tgt, sources=()) for tgt in request.targets)
541
    )
542

543

544
# -----------------------------------------------------------------------------------------------
545
# Dependency Modules Request
546
# See https://build-server-protocol.github.io/docs/specification.html#dependency-modules-request
547
# -----------------------------------------------------------------------------------------------
548

549

UNCOV
550
@union(in_scope_types=[EnvironmentName])
×
UNCOV
551
@dataclass(frozen=True)
×
UNCOV
552
class BSPDependencyModulesRequest(Generic[_FS]):
×
553
    """Hook to allow language backends to provide dependency modules."""
554

UNCOV
555
    field_set_type: ClassVar[type[_FS]]
×
556

UNCOV
557
    field_sets: tuple[_FS, ...]
×
558

559

UNCOV
560
@dataclass(frozen=True)
×
UNCOV
561
class BSPDependencyModulesResult:
×
UNCOV
562
    modules: tuple[DependencyModule, ...]
×
UNCOV
563
    digest: Digest = EMPTY_DIGEST
×
564

565

UNCOV
566
@rule(polymorphic=True)
×
UNCOV
567
async def get_bsp_dependency_modules(
×
568
    req: BSPDependencyModulesRequest, env_name: EnvironmentName
569
) -> BSPDependencyModulesResult:
570
    raise NotImplementedError()
×
571

572

UNCOV
573
class DependencyModulesHandlerMapping(BSPHandlerMapping):
×
UNCOV
574
    method_name = "buildTarget/dependencyModules"
×
UNCOV
575
    request_type = DependencyModulesParams
×
UNCOV
576
    response_type = DependencyModulesResult
×
577

578

UNCOV
579
@dataclass(frozen=True)
×
UNCOV
580
class ResolveOneDependencyModuleRequest:
×
UNCOV
581
    bsp_target_id: BuildTargetIdentifier
×
582

583

UNCOV
584
@dataclass(frozen=True)
×
UNCOV
585
class ResolveOneDependencyModuleResult:
×
UNCOV
586
    bsp_target_id: BuildTargetIdentifier
×
UNCOV
587
    modules: tuple[DependencyModule, ...] = ()
×
UNCOV
588
    digest: Digest = EMPTY_DIGEST
×
589

590

UNCOV
591
@rule
×
UNCOV
592
async def resolve_one_dependency_module(
×
593
    request: ResolveOneDependencyModuleRequest,
594
    union_membership: UnionMembership,
595
) -> ResolveOneDependencyModuleResult:
596
    targets = await resolve_bsp_build_target_addresses(**implicitly(request.bsp_target_id))
×
597

598
    field_sets_by_request_type: dict[type[BSPDependencyModulesRequest], list[FieldSet]] = (
×
599
        defaultdict(list)
600
    )
601
    dep_module_request_types: Sequence[type[BSPDependencyModulesRequest]] = union_membership.get(
×
602
        BSPDependencyModulesRequest
603
    )
604
    for tgt in targets:
×
605
        for dep_module_request_type in dep_module_request_types:
×
606
            field_set_type = dep_module_request_type.field_set_type
×
607
            if field_set_type.is_applicable(tgt):
×
608
                field_set = field_set_type.create(tgt)
×
609
                field_sets_by_request_type[dep_module_request_type].append(field_set)
×
610

611
    if not field_sets_by_request_type:
×
612
        return ResolveOneDependencyModuleResult(bsp_target_id=request.bsp_target_id)
×
613

614
    responses = await concurrently(
×
615
        get_bsp_dependency_modules(
616
            **implicitly(
617
                {dep_module_request_type(field_sets=tuple(field_sets)): BSPDependencyModulesRequest}
618
            )
619
        )
620
        for dep_module_request_type, field_sets in field_sets_by_request_type.items()
621
    )
622

623
    modules = set(itertools.chain.from_iterable([r.modules for r in responses]))
×
624
    digest = await merge_digests(MergeDigests([r.digest for r in responses]))
×
625

626
    return ResolveOneDependencyModuleResult(
×
627
        bsp_target_id=request.bsp_target_id,
628
        modules=tuple(modules),
629
        digest=digest,
630
    )
631

632

633
# Note: VSCode expects this endpoint to exist even if the capability bit for it is set `false`.
UNCOV
634
@_uncacheable_rule
×
UNCOV
635
async def bsp_dependency_modules(
×
636
    request: DependencyModulesParams, workspace: Workspace
637
) -> DependencyModulesResult:
638
    responses = await concurrently(
×
639
        resolve_one_dependency_module(ResolveOneDependencyModuleRequest(btgt), **implicitly())
640
        for btgt in request.targets
641
    )
642
    output_digest = await merge_digests(MergeDigests([r.digest for r in responses]))
×
643
    workspace.write_digest(output_digest, path_prefix=".pants.d/bsp")
×
644
    return DependencyModulesResult(
×
645
        tuple(DependencyModulesItem(target=r.bsp_target_id, modules=r.modules) for r in responses)
646
    )
647

648

649
# -----------------------------------------------------------------------------------------------
650
# Compile request.
651
# See https://build-server-protocol.github.io/docs/specification.html#compile-request
652
# -----------------------------------------------------------------------------------------------
653

654

UNCOV
655
@union(in_scope_types=[EnvironmentName])
×
UNCOV
656
@dataclass(frozen=True)
×
UNCOV
657
class BSPCompileRequest(Generic[_FS]):
×
658
    """Hook to allow language backends to compile targets."""
659

UNCOV
660
    field_set_type: ClassVar[type[_FS]]
×
661

UNCOV
662
    bsp_target: BSPBuildTargetInternal
×
UNCOV
663
    field_sets: tuple[_FS, ...]
×
UNCOV
664
    task_id: TaskId
×
665

666

UNCOV
667
@dataclass(frozen=True)
×
UNCOV
668
class BSPCompileResult:
×
669
    """Result of compilation of a target capable of target compilation."""
670

UNCOV
671
    status: StatusCode
×
UNCOV
672
    output_digest: Digest
×
673

674

UNCOV
675
@rule(polymorphic=True)
×
UNCOV
676
async def bsp_compile(req: BSPCompileRequest, env_name: EnvironmentName) -> BSPCompileResult:
×
677
    raise NotImplementedError()
×
678

679

680
# -----------------------------------------------------------------------------------------------
681
# Resources request.
682
# See https://build-server-protocol.github.io/docs/specification.html#resources-request
683
#
684
# NB: This method is used only for the _indexing_ of resources, and not to add them to the
685
# classpath (in the case of JVM targets). BSPCompileRequest implementations need to handle
686
# movement of resources to accessible classpath entries.
687
# -----------------------------------------------------------------------------------------------
688

689

UNCOV
690
@union(in_scope_types=[EnvironmentName])
×
UNCOV
691
@dataclass(frozen=True)
×
UNCOV
692
class BSPResourcesRequest(Generic[_FS]):
×
693
    """Hook to allow language backends to provide resources for targets."""
694

UNCOV
695
    field_set_type: ClassVar[type[_FS]]
×
696

UNCOV
697
    bsp_target: BSPBuildTargetInternal
×
UNCOV
698
    field_sets: tuple[_FS, ...]
×
699

700

UNCOV
701
@dataclass(frozen=True)
×
UNCOV
702
class BSPResourcesResult:
×
703
    """Resources for a target."""
704

UNCOV
705
    resources: tuple[Uri, ...]
×
UNCOV
706
    output_digest: Digest
×
707

708

UNCOV
709
@rule(polymorphic=True)
×
UNCOV
710
async def get_bsp_resources(
×
711
    req: BSPResourcesRequest, env_name: EnvironmentName
712
) -> BSPResourcesResult:
713
    raise NotImplementedError()
×
714

715

UNCOV
716
def rules():
×
UNCOV
717
    return (
×
718
        *collect_rules(),
719
        UnionRule(BSPHandlerMapping, WorkspaceBuildTargetsHandlerMapping),
720
        UnionRule(BSPHandlerMapping, BuildTargetSourcesHandlerMapping),
721
        UnionRule(BSPHandlerMapping, DependencySourcesHandlerMapping),
722
        UnionRule(BSPHandlerMapping, DependencyModulesHandlerMapping),
723
    )
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

© 2025 Coveralls, Inc