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

pantsbuild / pants / 24055979590

06 Apr 2026 11:17PM UTC coverage: 52.37% (-40.5%) from 92.908%
24055979590

Pull #23225

github

web-flow
Merge 67474653c into 542ca048d
Pull Request #23225: Add --test-show-all-batch-targets to expose all targets in batched pytest

6 of 17 new or added lines in 2 files covered. (35.29%)

23030 existing lines in 605 files now uncovered.

31643 of 60422 relevant lines covered (52.37%)

1.05 hits per line

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

0.0
/src/python/pants/backend/project_info/paths.py
1
# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

UNCOV
4
from __future__ import annotations
×
5

UNCOV
6
import json
×
UNCOV
7
from collections import deque
×
UNCOV
8
from collections.abc import Iterable
×
UNCOV
9
from dataclasses import dataclass
×
10

UNCOV
11
from pants.base.specs import Specs
×
UNCOV
12
from pants.base.specs_parser import SpecsParser
×
UNCOV
13
from pants.engine.addresses import Address
×
UNCOV
14
from pants.engine.console import Console
×
UNCOV
15
from pants.engine.goal import Goal, GoalSubsystem, Outputting
×
UNCOV
16
from pants.engine.internals.graph import resolve_targets
×
UNCOV
17
from pants.engine.internals.graph import transitive_targets as transitive_targets_get
×
UNCOV
18
from pants.engine.rules import collect_rules, concurrently, goal_rule, implicitly, rule
×
UNCOV
19
from pants.engine.target import (
×
20
    AlwaysTraverseDeps,
21
    Dependencies,
22
    DependenciesRequest,
23
    Target,
24
    Targets,
25
    TransitiveTargetsRequest,
26
)
UNCOV
27
from pants.option.option_types import StrOption
×
28

29

UNCOV
30
class PathsSubsystem(Outputting, GoalSubsystem):
×
UNCOV
31
    name = "paths"
×
UNCOV
32
    help = (
×
33
        "List the paths between two addresses. "
34
        "Either address may represent a group of targets, e.g. `--from=src/app/main.py --to=src/library::`."
35
    )
36

UNCOV
37
    from_ = StrOption(
×
38
        default=None,
39
        help="The path starting address",
40
    )
41

UNCOV
42
    to = StrOption(
×
43
        default=None,
44
        help="The path end address",
45
    )
46

47

UNCOV
48
class PathsGoal(Goal):
×
UNCOV
49
    subsystem_cls = PathsSubsystem
×
UNCOV
50
    environment_behavior = Goal.EnvironmentBehavior.LOCAL_ONLY
×
51

52

UNCOV
53
def find_paths_breadth_first(
×
54
    adjacency_lists: dict[Address, Targets], from_target: Address, to_target: Address
55
) -> Iterable[list[Address]]:
56
    """Yields the paths between from_target to to_target if they exist.
57

58
    The paths are returned ordered by length, shortest first. If there are cycles, it checks visited
59
    edges to prevent recrossing them.
60
    """
61

UNCOV
62
    if from_target == to_target:
×
UNCOV
63
        yield [from_target]
×
UNCOV
64
        return
×
65

UNCOV
66
    visited_edges = set()
×
UNCOV
67
    to_walk_paths = deque([[from_target]])
×
68

UNCOV
69
    while len(to_walk_paths) > 0:
×
UNCOV
70
        cur_path = to_walk_paths.popleft()
×
UNCOV
71
        target = cur_path[-1]
×
72

UNCOV
73
        if len(cur_path) > 1:
×
UNCOV
74
            prev_target: Address | None = cur_path[-2]
×
75
        else:
UNCOV
76
            prev_target = None
×
UNCOV
77
        current_edge = (prev_target, target)
×
78

UNCOV
79
        if current_edge not in visited_edges:
×
UNCOV
80
            for dep in adjacency_lists.get(target, []):
×
UNCOV
81
                dep_path = cur_path + [dep.address]
×
UNCOV
82
                if dep.address == to_target:
×
UNCOV
83
                    yield dep_path
×
84
                else:
UNCOV
85
                    to_walk_paths.append(dep_path)
×
UNCOV
86
            visited_edges.add(current_edge)
×
87

88

UNCOV
89
@dataclass
×
UNCOV
90
class SpecsPaths:
×
UNCOV
91
    paths: list[list[str]]
×
92

93

UNCOV
94
@dataclass
×
UNCOV
95
class SpecsPathsCollection:
×
UNCOV
96
    spec_paths: list[SpecsPaths]
×
97

98

UNCOV
99
@dataclass(frozen=True)
×
UNCOV
100
class RootDestinationPair:
×
UNCOV
101
    root: Target
×
UNCOV
102
    destination: Target
×
103

104

UNCOV
105
@dataclass(frozen=True)
×
UNCOV
106
class RootDestinationsPair:
×
UNCOV
107
    root: Target
×
UNCOV
108
    destinations: Targets
×
109

110

UNCOV
111
@rule(desc="Get paths between root and destination.")
×
UNCOV
112
async def get_paths_between_root_and_destination(pair: RootDestinationPair) -> SpecsPaths:
×
UNCOV
113
    transitive_targets = await transitive_targets_get(
×
114
        TransitiveTargetsRequest(
115
            [pair.root.address], should_traverse_deps_predicate=AlwaysTraverseDeps()
116
        ),
117
        **implicitly(),
118
    )
119

UNCOV
120
    adjacent_targets_per_target = await concurrently(
×
121
        resolve_targets(
122
            **implicitly(
123
                DependenciesRequest(
124
                    tgt.get(Dependencies), should_traverse_deps_predicate=AlwaysTraverseDeps()
125
                )
126
            )
127
        )
128
        for tgt in transitive_targets.closure
129
    )
130

UNCOV
131
    transitive_targets_closure_addresses = (t.address for t in transitive_targets.closure)
×
UNCOV
132
    adjacency_lists = dict(zip(transitive_targets_closure_addresses, adjacent_targets_per_target))
×
133

UNCOV
134
    spec_paths = []
×
UNCOV
135
    for path in find_paths_breadth_first(
×
136
        adjacency_lists, pair.root.address, pair.destination.address
137
    ):
UNCOV
138
        spec_path = [address.spec for address in path]
×
UNCOV
139
        spec_paths.append(spec_path)
×
140

UNCOV
141
    return SpecsPaths(paths=spec_paths)
×
142

143

UNCOV
144
@rule(desc="Get paths between root and multiple destinations.")
×
UNCOV
145
async def get_paths_between_root_and_destinations(
×
146
    pair: RootDestinationsPair,
147
) -> SpecsPathsCollection:
UNCOV
148
    spec_paths = await concurrently(
×
149
        get_paths_between_root_and_destination(
150
            RootDestinationPair(destination=destination, root=pair.root)
151
        )
152
        for destination in pair.destinations
153
    )
UNCOV
154
    return SpecsPathsCollection(spec_paths=list(spec_paths))
×
155

156

UNCOV
157
@goal_rule
×
UNCOV
158
async def paths(console: Console, paths_subsystem: PathsSubsystem) -> PathsGoal:
×
UNCOV
159
    path_from = paths_subsystem.from_
×
UNCOV
160
    path_to = paths_subsystem.to
×
161

UNCOV
162
    if path_from is None:
×
UNCOV
163
        raise ValueError("Must set --from")
×
164

UNCOV
165
    if path_to is None:
×
UNCOV
166
        raise ValueError("Must set --to")
×
167

UNCOV
168
    specs_parser = SpecsParser()
×
169

UNCOV
170
    from_tgts, to_tgts = await concurrently(
×
171
        resolve_targets(
172
            **implicitly(
173
                {
174
                    specs_parser.parse_specs(
175
                        [path_from],
176
                        description_of_origin="the option `--paths-from`",
177
                    ): Specs
178
                }
179
            )
180
        ),
181
        resolve_targets(
182
            **implicitly(
183
                {
184
                    specs_parser.parse_specs(
185
                        [path_to],
186
                        description_of_origin="the option `--paths-to`",
187
                    ): Specs
188
                }
189
            )
190
        ),
191
    )
192

UNCOV
193
    all_spec_paths = []
×
UNCOV
194
    spec_paths = await concurrently(
×
195
        get_paths_between_root_and_destinations(
196
            RootDestinationsPair(root=root, destinations=to_tgts)
197
        )
198
        for root in from_tgts
199
    )
200

UNCOV
201
    for spec_path in spec_paths:
×
UNCOV
202
        for path in (p.paths for p in spec_path.spec_paths):
×
UNCOV
203
            all_spec_paths.extend(path)
×
204

UNCOV
205
    with paths_subsystem.output(console) as write_stdout:
×
UNCOV
206
        write_stdout(json.dumps(all_spec_paths, indent=2) + "\n")
×
207

UNCOV
208
    return PathsGoal(exit_code=0)
×
209

210

UNCOV
211
def rules():
×
UNCOV
212
    return collect_rules()
×
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