• 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/backend/cc/dependency_inference/rules.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 re
×
UNCOV
6
from collections import defaultdict
×
UNCOV
7
from collections.abc import Iterable
×
UNCOV
8
from dataclasses import dataclass
×
UNCOV
9
from pathlib import PurePath
×
UNCOV
10
from typing import DefaultDict
×
11

UNCOV
12
from pants.backend.cc.subsystems.cc_infer import CCInferSubsystem
×
UNCOV
13
from pants.backend.cc.target_types import CCDependenciesField, CCSourceField
×
UNCOV
14
from pants.build_graph.address import Address
×
UNCOV
15
from pants.core.util_rules import stripped_source_files
×
UNCOV
16
from pants.core.util_rules.stripped_source_files import StrippedFileNameRequest, strip_file_name
×
UNCOV
17
from pants.engine.internals.graph import determine_explicitly_provided_dependencies, hydrate_sources
×
UNCOV
18
from pants.engine.intrinsics import get_digest_contents
×
UNCOV
19
from pants.engine.rules import Rule, collect_rules, concurrently, implicitly, rule
×
UNCOV
20
from pants.engine.target import (
×
21
    AllTargets,
22
    ExplicitlyProvidedDependenciesRequest,
23
    FieldSet,
24
    HydrateSourcesRequest,
25
    InferDependenciesRequest,
26
    InferredDependencies,
27
    Targets,
28
)
UNCOV
29
from pants.engine.unions import UnionRule
×
UNCOV
30
from pants.util.frozendict import FrozenDict
×
UNCOV
31
from pants.util.logging import LogLevel
×
UNCOV
32
from pants.util.ordered_set import OrderedSet
×
UNCOV
33
from pants.util.strutil import softwrap
×
34

UNCOV
35
INCLUDE_REGEX = re.compile(r"^\s*#\s*include\s+((\".*\")|(<.*>))")
×
36

37

UNCOV
38
@dataclass(frozen=True)
×
UNCOV
39
class CCDependencyInferenceFieldSet(FieldSet):
×
UNCOV
40
    required_fields = (CCSourceField, CCDependenciesField)
×
41

UNCOV
42
    sources: CCSourceField
×
UNCOV
43
    dependencies: CCDependenciesField
×
44

45

UNCOV
46
class InferCCDependenciesRequest(InferDependenciesRequest):
×
UNCOV
47
    infer_from = CCDependencyInferenceFieldSet
×
48

49

UNCOV
50
class AllCCTargets(Targets):
×
UNCOV
51
    pass
×
52

53

UNCOV
54
@rule(desc="Find all CC targets in project", level=LogLevel.DEBUG)
×
UNCOV
55
async def find_all_cc_targets(targets: AllTargets) -> AllCCTargets:
×
56
    return AllCCTargets(tgt for tgt in targets if tgt.has_field(CCSourceField))
×
57

58

UNCOV
59
@dataclass(frozen=True)
×
UNCOV
60
class CCFilesMapping:
×
61
    """A mapping of stripped CC file names to their owning file address."""
62

UNCOV
63
    mapping: FrozenDict[str, Address]
×
UNCOV
64
    ambiguous_files: FrozenDict[str, tuple[Address, ...]]
×
UNCOV
65
    mapping_not_stripped: FrozenDict[str, Address]
×
66

67

UNCOV
68
@rule(desc="Creating map of CC file names to CC targets", level=LogLevel.DEBUG)
×
UNCOV
69
async def map_cc_files(cc_targets: AllCCTargets) -> CCFilesMapping:
×
70
    stripped_file_per_target = await concurrently(
×
71
        strip_file_name(StrippedFileNameRequest(tgt[CCSourceField].file_path)) for tgt in cc_targets
72
    )
73

74
    stripped_files_to_addresses: dict[str, Address] = {}
×
75
    stripped_files_with_multiple_owners: DefaultDict[str, set[Address]] = defaultdict(set)
×
76
    for tgt, stripped_file in zip(cc_targets, stripped_file_per_target):
×
77
        if stripped_file.value in stripped_files_to_addresses:
×
78
            stripped_files_with_multiple_owners[stripped_file.value].update(
×
79
                {stripped_files_to_addresses[stripped_file.value], tgt.address}
80
            )
81
        else:
82
            stripped_files_to_addresses[stripped_file.value] = tgt.address
×
83

84
    # Remove files with ambiguous owners.
85
    for ambiguous_stripped_f in stripped_files_with_multiple_owners:
×
86
        stripped_files_to_addresses.pop(ambiguous_stripped_f)
×
87

88
    mapping_not_stripped = {tgt[CCSourceField].file_path: tgt.address for tgt in cc_targets}
×
89

90
    return CCFilesMapping(
×
91
        mapping=FrozenDict(sorted(stripped_files_to_addresses.items())),
92
        ambiguous_files=FrozenDict(
93
            (k, tuple(sorted(v))) for k, v in sorted(stripped_files_with_multiple_owners.items())
94
        ),
95
        mapping_not_stripped=FrozenDict(mapping_not_stripped),
96
    )
97

98

UNCOV
99
@dataclass(frozen=True)
×
UNCOV
100
class CCIncludeDirective:
×
UNCOV
101
    path: str
×
UNCOV
102
    system_paths_only: bool  # True if include used `<foo.h>` instead of `"foo.h"`.
×
103

104

UNCOV
105
def parse_includes(content: str) -> frozenset[CCIncludeDirective]:
×
UNCOV
106
    includes: set[CCIncludeDirective] = set()
×
UNCOV
107
    for line in content.splitlines():
×
UNCOV
108
        m = INCLUDE_REGEX.match(line)
×
UNCOV
109
        if m:
×
UNCOV
110
            if m.group(2):
×
UNCOV
111
                includes.add(CCIncludeDirective(m.group(2)[1:-1], False))
×
UNCOV
112
            elif m.group(3):
×
UNCOV
113
                includes.add(CCIncludeDirective(m.group(3)[1:-1], True))
×
UNCOV
114
    return frozenset(includes)
×
115

116

UNCOV
117
@rule
×
UNCOV
118
async def infer_cc_source_dependencies(
×
119
    request: InferCCDependenciesRequest,
120
    cc_files_mapping: CCFilesMapping,
121
    cc_infer: CCInferSubsystem,
122
) -> InferredDependencies:
123
    if not cc_infer.includes:
×
124
        return InferredDependencies([])
×
125

126
    address = request.field_set.address
×
127
    explicitly_provided_deps, hydrated_sources = await concurrently(
×
128
        determine_explicitly_provided_dependencies(
129
            ExplicitlyProvidedDependenciesRequest(request.field_set.dependencies), **implicitly()
130
        ),
131
        hydrate_sources(HydrateSourcesRequest(request.field_set.sources), **implicitly()),
132
    )
133

134
    digest_contents = await get_digest_contents(hydrated_sources.snapshot.digest)
×
135
    assert len(digest_contents) == 1
×
136
    file_content = digest_contents[0]
×
137
    file_path = PurePath(file_content.path)
×
138

139
    includes = parse_includes(file_content.content.decode())
×
140

141
    result: OrderedSet[Address] = OrderedSet()
×
142
    for include in includes:
×
143
        # Skip system-path includes.
144
        if include.system_paths_only:
×
145
            continue
×
146

147
        # First try to resolve the include's path against the same directory where the file is.
148
        maybe_relative_file_path = file_path.parent.joinpath(include.path)
×
149
        maybe_relative_address = cc_files_mapping.mapping_not_stripped.get(
×
150
            str(maybe_relative_file_path)
151
        )
152
        if maybe_relative_address:
×
153
            result.add(maybe_relative_address)
×
154
            continue
×
155

156
        # Otherwise try source roots.
157
        if cc_infer.include_from_source_roots:
×
158
            unambiguous = cc_files_mapping.mapping.get(include.path)
×
159
            ambiguous = cc_files_mapping.ambiguous_files.get(include.path)
×
160

161
            if unambiguous:
×
162
                result.add(unambiguous)
×
163
            elif ambiguous:
×
164
                explicitly_provided_deps.maybe_warn_of_ambiguous_dependency_inference(
×
165
                    ambiguous,
166
                    address,
167
                    import_reference="file",
168
                    context=softwrap(
169
                        f"""
170
                        The target {address} includes `{include.path}` in the file
171
                        {file_content.path}
172
                        """
173
                    ),
174
                )
175
                maybe_disambiguated = explicitly_provided_deps.disambiguated(ambiguous)
×
176
                if maybe_disambiguated:
×
177
                    result.add(maybe_disambiguated)
×
178
    return InferredDependencies(sorted(result))
×
179

180

UNCOV
181
def rules() -> Iterable[Rule | UnionRule]:
×
UNCOV
182
    return (
×
183
        *collect_rules(),
184
        *stripped_source_files.rules(),
185
        UnionRule(InferDependenciesRequest, InferCCDependenciesRequest),
186
    )
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