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

pantsbuild / pants / 25441711719

06 May 2026 02:31PM UTC coverage: 92.915%. Remained the same
25441711719

push

github

web-flow
use sha pin (with comment) format for generated actions (#23312)

Per the GitHub Action best practices we recently enabled at #23249, we
should pin each action to a SHA so that the reference is actually
immutable.

This will -- I hope -- knock out a large chunk of the 421 alerts we
currently get from zizmor. The next followup would then be upgrades and
harmonizing the generated and none-generated pins.

Notice: This idea was suggested by Claude while going over pinact output
and I was surprised to see that post processing the yaml wasn't too
gross.

92206 of 99237 relevant lines covered (92.91%)

4.04 hits per line

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

98.99
/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).
3
from __future__ import annotations
2✔
4

5
import re
2✔
6
from collections import defaultdict
2✔
7
from collections.abc import Iterable
2✔
8
from dataclasses import dataclass
2✔
9
from pathlib import PurePath
2✔
10
from typing import DefaultDict
2✔
11

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

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

37

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

42
    sources: CCSourceField
2✔
43
    dependencies: CCDependenciesField
2✔
44

45

46
class InferCCDependenciesRequest(InferDependenciesRequest):
2✔
47
    infer_from = CCDependencyInferenceFieldSet
2✔
48

49

50
class AllCCTargets(Targets):
2✔
51
    pass
2✔
52

53

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

58

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

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

67

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

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

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

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

90
    return CCFilesMapping(
1✔
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

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

104

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

116

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

126
    address = request.field_set.address
1✔
127
    explicitly_provided_deps, hydrated_sources = await concurrently(
1✔
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)
1✔
135
    assert len(digest_contents) == 1
1✔
136
    file_content = digest_contents[0]
1✔
137
    file_path = PurePath(file_content.path)
1✔
138

139
    includes = parse_includes(file_content.content.decode())
1✔
140

141
    result: OrderedSet[Address] = OrderedSet()
1✔
142
    for include in includes:
1✔
143
        # Skip system-path includes.
144
        if include.system_paths_only:
1✔
145
            continue
1✔
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)
1✔
149
        maybe_relative_address = cc_files_mapping.mapping_not_stripped.get(
1✔
150
            str(maybe_relative_file_path)
151
        )
152
        if maybe_relative_address:
1✔
153
            result.add(maybe_relative_address)
1✔
154
            continue
1✔
155

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

161
            if unambiguous:
1✔
162
                result.add(unambiguous)
1✔
163
            elif ambiguous:
1✔
164
                explicitly_provided_deps.maybe_warn_of_ambiguous_dependency_inference(
1✔
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)
1✔
176
                if maybe_disambiguated:
1✔
177
                    result.add(maybe_disambiguated)
1✔
178
    return InferredDependencies(sorted(result))
1✔
179

180

181
def rules() -> Iterable[Rule | UnionRule]:
2✔
182
    return (
2✔
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