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

pantsbuild / pants / 18517631058

15 Oct 2025 04:18AM UTC coverage: 69.207% (-11.1%) from 80.267%
18517631058

Pull #22745

github

web-flow
Merge 642a76ca1 into 99919310e
Pull Request #22745: [windows] Add windows support in the stdio crate.

53815 of 77759 relevant lines covered (69.21%)

2.42 hits per line

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

49.49
/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
1✔
4

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

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

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

37

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

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

45

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

49

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

53

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

58

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

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

67

68
@rule(desc="Creating map of CC file names to CC targets", level=LogLevel.DEBUG)
1✔
69
async def map_cc_files(cc_targets: AllCCTargets) -> CCFilesMapping:
1✔
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

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

104

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

116

117
@rule
1✔
118
async def infer_cc_source_dependencies(
1✔
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

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

© 2025 Coveralls, Inc