• 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

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

4
from __future__ import annotations
12✔
5

6
import os.path
12✔
7
from collections.abc import Iterable, Mapping
12✔
8
from dataclasses import dataclass
12✔
9
from typing import TypeVar
12✔
10

11
from pants.backend.project_info.filter_targets import FilterSubsystem
12✔
12
from pants.base.exceptions import MappingError
12✔
13
from pants.build_graph.address import Address, BuildFileAddress
12✔
14
from pants.engine.addresses import Addresses
12✔
15
from pants.engine.collection import Collection
12✔
16
from pants.engine.env_vars import EnvironmentVars
12✔
17
from pants.engine.internals.defaults import BuildFileDefaults, BuildFileDefaultsParserState
12✔
18
from pants.engine.internals.dep_rules import (
12✔
19
    BuildFileDependencyRules,
20
    BuildFileDependencyRulesParserState,
21
)
22
from pants.engine.internals.parser import BuildFilePreludeSymbols, Parser
12✔
23
from pants.engine.internals.target_adaptor import TargetAdaptor
12✔
24
from pants.engine.target import RegisteredTargetTypes, Tags, Target
12✔
25
from pants.util.filtering import TargetFilter, and_filters, create_filters
12✔
26
from pants.util.frozendict import FrozenDict
12✔
27
from pants.util.memo import memoized_property
12✔
28

29

30
class DuplicateNameError(MappingError):
12✔
31
    """Indicates more than one top-level object was found with the same name."""
32

33

34
AddressMapT = TypeVar("AddressMapT", bound="AddressMap")
12✔
35

36

37
@dataclass(frozen=True)
12✔
38
class AddressMap:
12✔
39
    """Maps target adaptors from a byte source."""
40

41
    path: str
12✔
42
    name_to_target_adaptor: FrozenDict[str, TargetAdaptor]
12✔
43

44
    @classmethod
12✔
45
    def parse(
12✔
46
        cls,
47
        filepath: str,
48
        build_file_content: str,
49
        parser: Parser,
50
        extra_symbols: BuildFilePreludeSymbols,
51
        env_vars: EnvironmentVars,
52
        is_bootstrap: bool,
53
        defaults: BuildFileDefaultsParserState,
54
        dependents_rules: BuildFileDependencyRulesParserState | None,
55
        dependencies_rules: BuildFileDependencyRulesParserState | None,
56
    ) -> AddressMap:
57
        """Parses a source for targets.
58

59
        The target adaptors are all 'thin': any targets they point to in other namespaces or even in
60
        the same namespace but from a separate source are left as unresolved pointers.
61
        """
UNCOV
62
        try:
1✔
UNCOV
63
            target_adaptors = parser.parse(
1✔
64
                filepath,
65
                build_file_content,
66
                extra_symbols,
67
                env_vars,
68
                is_bootstrap,
69
                defaults,
70
                dependents_rules,
71
                dependencies_rules,
72
            )
UNCOV
73
        except Exception as e:
1✔
UNCOV
74
            raise MappingError(f"Failed to parse ./{filepath}:\n{type(e).__name__}: {e}")
1✔
UNCOV
75
        return cls.create(filepath, target_adaptors)
1✔
76

77
    @classmethod
12✔
78
    def create(
12✔
79
        cls: type[AddressMapT], filepath: str, target_adaptors: Iterable[TargetAdaptor]
80
    ) -> AddressMapT:
UNCOV
81
        name_to_target_adaptors: dict[str, TargetAdaptor] = {}
1✔
UNCOV
82
        for target_adaptor in target_adaptors:
1✔
UNCOV
83
            name = target_adaptor.name or os.path.basename(os.path.dirname(filepath))
1✔
UNCOV
84
            if name in name_to_target_adaptors:
1✔
UNCOV
85
                duplicate = name_to_target_adaptors[name]
1✔
UNCOV
86
                raise DuplicateNameError(
1✔
87
                    f"A target already exists at {filepath!r} with name {name!r} and target type "
88
                    f"{duplicate.type_alias!r}. The {target_adaptor.type_alias!r} target "
89
                    "cannot use the same name."
90
                )
UNCOV
91
            name_to_target_adaptors[name] = target_adaptor
1✔
UNCOV
92
        return cls(filepath, FrozenDict(sorted(name_to_target_adaptors.items())))
1✔
93

94

95
class DifferingFamiliesError(MappingError):
12✔
96
    """Indicates an attempt was made to merge address maps from different families together."""
97

98

99
@dataclass(frozen=True)
12✔
100
class AddressFamily:
12✔
101
    """Represents the family of target adaptors collected from the BUILD files in one directory.
102

103
    To create an AddressFamily, use `create`.
104

105
    An address family can be composed of the target adaptors from zero or more underlying address
106
    sources. An "empty" AddressFamily is legal, and is the result when there are not build files in
107
    a particular namespace.
108

109
    :param namespace: The namespace path of this address family.
110
    :param name_to_target_adaptors: A dict mapping from name to the target adaptor.
111
    :param defaults: The default target field values, per target type, applicable for this address family.
112
    :param dependents_rules: The rules to apply on incoming dependencies to targets in this family.
113
    :param dependencies_rules: The rules to apply on the outgoing dependencies from targets in this family.
114
    """
115

116
    # The directory from which the adaptors were parsed.
117
    namespace: str
12✔
118
    name_to_target_adaptors: dict[str, tuple[str, TargetAdaptor]]
12✔
119
    defaults: BuildFileDefaults
12✔
120
    dependents_rules: BuildFileDependencyRules | None
12✔
121
    dependencies_rules: BuildFileDependencyRules | None
12✔
122

123
    @classmethod
12✔
124
    def create(
12✔
125
        cls,
126
        spec_path: str,
127
        address_maps: Iterable[AddressMap],
128
        defaults: BuildFileDefaults = BuildFileDefaults({}),
129
        dependents_rules: BuildFileDependencyRules | None = None,
130
        dependencies_rules: BuildFileDependencyRules | None = None,
131
    ) -> AddressFamily:
132
        """Creates an address family from the given set of address maps.
133

134
        :param spec_path: The directory prefix shared by all address_maps.
135
        :param address_maps: The family of maps that form this namespace.
136
        :raises: :class:`MappingError` if the given address maps do not form a family.
137
        """
UNCOV
138
        if spec_path == ".":
1✔
139
            spec_path = ""
×
UNCOV
140
        for address_map in address_maps:
1✔
UNCOV
141
            if not address_map.path.startswith(spec_path):
1✔
UNCOV
142
                raise DifferingFamiliesError(
1✔
143
                    f"Expected AddressMaps to share the same parent directory {spec_path!r}, "
144
                    f"but received: {address_map.path!r}"
145
                )
146

UNCOV
147
        name_to_target_adaptors: dict[str, tuple[str, TargetAdaptor]] = {}
1✔
UNCOV
148
        for address_map in address_maps:
1✔
UNCOV
149
            for name, target_adaptor in address_map.name_to_target_adaptor.items():
1✔
UNCOV
150
                if name in name_to_target_adaptors:
1✔
UNCOV
151
                    previous_path, _ = name_to_target_adaptors[name]
1✔
UNCOV
152
                    raise DuplicateNameError(
1✔
153
                        f"A target with name {name!r} is already defined in {previous_path!r}, but "
154
                        f"is also defined in {address_map.path!r}. Because both targets share the "
155
                        f"same namespace of {spec_path!r}, this is not allowed."
156
                    )
UNCOV
157
                name_to_target_adaptors[name] = (address_map.path, target_adaptor)
1✔
UNCOV
158
        return AddressFamily(
1✔
159
            namespace=spec_path,
160
            name_to_target_adaptors=dict(sorted(name_to_target_adaptors.items())),
161
            defaults=defaults,
162
            dependents_rules=dependents_rules,
163
            dependencies_rules=dependencies_rules,
164
        )
165

166
    @memoized_property
12✔
167
    def addresses_to_target_adaptors(self) -> Mapping[Address, TargetAdaptor]:
12✔
UNCOV
168
        return {
1✔
169
            Address(spec_path=self.namespace, target_name=name): target_adaptor
170
            for name, (_, target_adaptor) in self.name_to_target_adaptors.items()
171
        }
172

173
    @memoized_property
12✔
174
    def build_file_addresses(self) -> tuple[BuildFileAddress, ...]:
12✔
UNCOV
175
        return tuple(
1✔
176
            BuildFileAddress(
177
                rel_path=path, address=Address(spec_path=self.namespace, target_name=name)
178
            )
179
            for name, (path, _) in self.name_to_target_adaptors.items()
180
        )
181

182
    @property
12✔
183
    def target_names(self) -> tuple[str, ...]:
12✔
184
        return tuple(addr.target_name for addr in self.addresses_to_target_adaptors)
×
185

186
    def get_target_adaptor(self, address: Address) -> TargetAdaptor | None:
12✔
187
        assert address.spec_path == self.namespace
×
188
        entry = self.name_to_target_adaptors.get(address.target_name)
×
189
        if entry is None:
×
190
            return None
×
191
        _, target_adaptor = entry
×
192
        return target_adaptor
×
193

194
    def __hash__(self):
12✔
195
        return hash((self.namespace, self.defaults))
×
196

197
    def __repr__(self) -> str:
12✔
198
        return (
×
199
            f"AddressFamily(namespace={self.namespace!r}, "
200
            f"name_to_target_adaptors={sorted(self.name_to_target_adaptors.keys())})"
201
        )
202

203

204
@dataclass(frozen=True)
12✔
205
class SpecsFilter:
12✔
206
    """Filters targets with the `--tags`, `--exclude-target-regexp`, and `[filter]` subsystem
207
    options."""
208

209
    is_specified: bool
12✔
210
    filter_subsystem_filter: TargetFilter
12✔
211
    tags_filter: TargetFilter
12✔
212

213
    @classmethod
12✔
214
    def create(
12✔
215
        cls,
216
        filter_subsystem: FilterSubsystem,
217
        registered_target_types: RegisteredTargetTypes,
218
        *,
219
        tags: Iterable[str],
220
    ) -> SpecsFilter:
UNCOV
221
        def tags_outer_filter(tag: str) -> TargetFilter:
1✔
UNCOV
222
            def tags_inner_filter(tgt: Target) -> bool:
1✔
UNCOV
223
                return tag in (tgt.get(Tags).value or [])
1✔
224

UNCOV
225
            return tags_inner_filter
1✔
226

UNCOV
227
        tags_filter = and_filters(create_filters(tags, tags_outer_filter))
1✔
228

UNCOV
229
        return SpecsFilter(
1✔
230
            is_specified=bool(filter_subsystem.is_specified() or tags),
231
            filter_subsystem_filter=filter_subsystem.all_filters(registered_target_types),
232
            tags_filter=tags_filter,
233
        )
234

235
    def matches(self, target: Target) -> bool:
12✔
236
        """Check that the target matches the provided `--tag` and `--exclude-target-regexp`
237
        options."""
UNCOV
238
        return self.tags_filter(target) and self.filter_subsystem_filter(target)
1✔
239

240

241
class AddressFamilies(Collection[AddressFamily]):
12✔
242
    def addresses(self) -> Addresses:
12✔
243
        return Addresses(self._base_addresses())
×
244

245
    def _base_addresses(self) -> Iterable[Address]:
12✔
246
        for family in self:
×
247
            yield from family.addresses_to_target_adaptors
×
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