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

pantsbuild / pants / 22285099215

22 Feb 2026 08:52PM UTC coverage: 75.854% (-17.1%) from 92.936%
22285099215

Pull #23121

github

web-flow
Merge c7299df9c into ba8359840
Pull Request #23121: fix issue with optional fields in dependency validator

28 of 29 new or added lines in 2 files covered. (96.55%)

11174 existing lines in 400 files now uncovered.

53694 of 70786 relevant lines covered (75.85%)

1.88 hits per line

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

46.15
/src/python/pants/backend/project_info/dependencies.py
1
# Copyright 2019 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3
import itertools
4✔
4
import json
4✔
5
from enum import Enum
4✔
6

7
from pants.engine.addresses import Addresses
4✔
8
from pants.engine.console import Console
4✔
9
from pants.engine.goal import Goal, GoalSubsystem, LineOriented
4✔
10
from pants.engine.internals.graph import resolve_targets, resolve_unexpanded_targets
4✔
11
from pants.engine.internals.graph import transitive_targets as transitive_targets_get
4✔
12
from pants.engine.rules import collect_rules, concurrently, goal_rule, implicitly
4✔
13
from pants.engine.target import (
4✔
14
    AlwaysTraverseDeps,
15
    DependenciesRequest,
16
    Targets,
17
    TransitiveTargetsRequest,
18
)
19
from pants.engine.target import Dependencies as DependenciesField
4✔
20
from pants.option.option_types import BoolOption, EnumOption
4✔
21

22

23
class DependenciesOutputFormat(Enum):
4✔
24
    """Output format for listing dependencies.
25

26
    text: List all dependencies as a single list of targets in plain text.
27
    json: List all dependencies as a mapping `{target: [dependencies]}`.
28
    """
29

30
    text = "text"
4✔
31
    json = "json"
4✔
32

33

34
class DependenciesSubsystem(LineOriented, GoalSubsystem):
4✔
35
    name = "dependencies"
4✔
36
    help = "List the dependencies of the input files/targets."
4✔
37

38
    transitive = BoolOption(
4✔
39
        default=False,
40
        help="List all transitive dependencies. If unspecified, list direct dependencies only.",
41
    )
42
    closed = BoolOption(
4✔
43
        default=False,
44
        help="Include the input targets in the output, along with the dependencies.",
45
    )
46
    format = EnumOption(
4✔
47
        default=DependenciesOutputFormat.text,
48
        help="Output format for listing dependencies.",
49
    )
50

51

52
class Dependencies(Goal):
4✔
53
    subsystem_cls = DependenciesSubsystem
4✔
54
    environment_behavior = Goal.EnvironmentBehavior.LOCAL_ONLY
4✔
55

56

57
async def list_dependencies_as_json(
4✔
58
    addresses: Addresses, dependencies_subsystem: DependenciesSubsystem, console: Console
59
) -> None:
60
    """Get dependencies for given addresses and list them in the console in JSON."""
61
    # NB: We must preserve target generators for the roots, i.e. not replace with their
62
    # generated targets.
UNCOV
63
    target_roots = await resolve_unexpanded_targets(addresses)
×
64
    # NB: When determining dependencies, though, we replace target generators with their
65
    # generated targets.
UNCOV
66
    if dependencies_subsystem.transitive:
×
UNCOV
67
        transitive_targets_group = await concurrently(
×
68
            transitive_targets_get(
69
                TransitiveTargetsRequest(
70
                    (address,), should_traverse_deps_predicate=AlwaysTraverseDeps()
71
                ),
72
                **implicitly(),
73
            )
74
            for address in addresses
75
        )
76

UNCOV
77
        iterated_targets = []
×
UNCOV
78
        for idx, transitive_targets in enumerate(transitive_targets_group):
×
UNCOV
79
            targets_collection = {
×
80
                str(tgt.address)
81
                for tgt in (
82
                    transitive_targets.closure
83
                    if dependencies_subsystem.closed
84
                    else transitive_targets.dependencies
85
                )
86
            }
UNCOV
87
            iterated_targets.append(sorted(targets_collection))
×
88

89
    else:
UNCOV
90
        dependencies_per_target_root = await concurrently(
×
91
            resolve_targets(
92
                **implicitly(
93
                    DependenciesRequest(
94
                        tgt.get(DependenciesField),
95
                        should_traverse_deps_predicate=AlwaysTraverseDeps(),
96
                    )
97
                )
98
            )
99
            for tgt in target_roots
100
        )
101

UNCOV
102
        iterated_targets = []
×
UNCOV
103
        for idx, targets in enumerate(dependencies_per_target_root):
×
UNCOV
104
            targets_collection = {str(tgt.address) for tgt in targets}
×
UNCOV
105
            if dependencies_subsystem.closed:
×
UNCOV
106
                targets_collection.add(str(target_roots[idx].address))
×
UNCOV
107
            iterated_targets.append(sorted(targets_collection))
×
108

109
    # The assumption is that when iterating the targets and sending dependency requests
110
    # for them, the lists of dependencies are returned in the very same order.
UNCOV
111
    mapping = dict(zip([str(tgt.address) for tgt in target_roots], iterated_targets))
×
UNCOV
112
    output = json.dumps(mapping, indent=4)
×
113

UNCOV
114
    with dependencies_subsystem.line_oriented(console) as print_stdout:
×
UNCOV
115
        print_stdout(output)
×
116

117

118
async def list_dependencies_as_plain_text(
4✔
119
    addresses: Addresses, dependencies_subsystem: DependenciesSubsystem, console: Console
120
) -> None:
121
    """Get dependencies for given addresses and list them in the console as a single list."""
UNCOV
122
    if dependencies_subsystem.transitive:
×
UNCOV
123
        transitive_targets = await transitive_targets_get(
×
124
            TransitiveTargetsRequest(
125
                addresses, should_traverse_deps_predicate=AlwaysTraverseDeps()
126
            ),
127
            **implicitly(),
128
        )
UNCOV
129
        targets = Targets(transitive_targets.dependencies)
×
130
    else:
131
        # NB: We must preserve target generators for the roots, i.e. not replace with their
132
        # generated targets.
UNCOV
133
        target_roots = await resolve_unexpanded_targets(addresses)
×
134
        # NB: When determining dependencies, though, we replace target generators with their
135
        # generated targets.
UNCOV
136
        dependencies_per_target_root = await concurrently(
×
137
            resolve_targets(
138
                **implicitly(
139
                    DependenciesRequest(
140
                        tgt.get(DependenciesField),
141
                        should_traverse_deps_predicate=AlwaysTraverseDeps(),
142
                    )
143
                )
144
            )
145
            for tgt in target_roots
146
        )
UNCOV
147
        targets = Targets(itertools.chain.from_iterable(dependencies_per_target_root))
×
148

UNCOV
149
    address_strings = {addr.spec for addr in addresses} if dependencies_subsystem.closed else set()
×
UNCOV
150
    for tgt in targets:
×
UNCOV
151
        address_strings.add(tgt.address.spec)
×
152

UNCOV
153
    with dependencies_subsystem.line_oriented(console) as print_stdout:
×
UNCOV
154
        for address in sorted(address_strings):
×
UNCOV
155
            print_stdout(address)
×
156

157

158
@goal_rule
4✔
159
async def dependencies(
4✔
160
    console: Console, addresses: Addresses, dependencies_subsystem: DependenciesSubsystem
161
) -> Dependencies:
UNCOV
162
    if DependenciesOutputFormat.text == dependencies_subsystem.format:
×
UNCOV
163
        await list_dependencies_as_plain_text(
×
164
            addresses=addresses,
165
            dependencies_subsystem=dependencies_subsystem,
166
            console=console,
167
        )
168

UNCOV
169
    elif DependenciesOutputFormat.json == dependencies_subsystem.format:
×
UNCOV
170
        await list_dependencies_as_json(
×
171
            addresses=addresses,
172
            dependencies_subsystem=dependencies_subsystem,
173
            console=console,
174
        )
175

UNCOV
176
    return Dependencies(exit_code=0)
×
177

178

179
def rules():
4✔
180
    return collect_rules()
4✔
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