• 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/codegen/protobuf/protobuf_dependency_inference.py
1
# Copyright 2020 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

UNCOV
4
from __future__ import annotations
×
5

UNCOV
6
import re
×
UNCOV
7
from collections import defaultdict
×
UNCOV
8
from dataclasses import dataclass
×
UNCOV
9
from typing import DefaultDict
×
10

UNCOV
11
from pants.backend.codegen.protobuf.protoc import Protoc
×
UNCOV
12
from pants.backend.codegen.protobuf.target_types import (
×
13
    AllProtobufTargets,
14
    ProtobufDependenciesField,
15
    ProtobufSourceField,
16
)
UNCOV
17
from pants.core.util_rules.stripped_source_files import StrippedFileNameRequest, strip_file_name
×
UNCOV
18
from pants.engine.addresses import Address
×
UNCOV
19
from pants.engine.internals.graph import determine_explicitly_provided_dependencies, hydrate_sources
×
UNCOV
20
from pants.engine.intrinsics import get_digest_contents
×
UNCOV
21
from pants.engine.rules import collect_rules, concurrently, implicitly, rule
×
UNCOV
22
from pants.engine.target import (
×
23
    DependenciesRequest,
24
    FieldSet,
25
    HydrateSourcesRequest,
26
    InferDependenciesRequest,
27
    InferredDependencies,
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 FrozenOrderedSet, OrderedSet
×
UNCOV
33
from pants.util.strutil import softwrap
×
34

35

UNCOV
36
@dataclass(frozen=True)
×
UNCOV
37
class ProtobufMapping:
×
38
    """A mapping of stripped .proto file names to their owning file address."""
39

UNCOV
40
    mapping: FrozenDict[str, Address]
×
UNCOV
41
    ambiguous_modules: FrozenDict[str, tuple[Address, ...]]
×
42

43

UNCOV
44
@rule(desc="Creating map of Protobuf file names to Protobuf targets", level=LogLevel.DEBUG)
×
UNCOV
45
async def map_protobuf_files(protobuf_targets: AllProtobufTargets) -> ProtobufMapping:
×
46
    stripped_file_per_target = await concurrently(
×
47
        strip_file_name(StrippedFileNameRequest(tgt[ProtobufSourceField].file_path))
48
        for tgt in protobuf_targets
49
    )
50

51
    stripped_files_to_addresses: dict[str, Address] = {}
×
52
    stripped_files_with_multiple_owners: DefaultDict[str, set[Address]] = defaultdict(set)
×
53
    for tgt, stripped_file in zip(protobuf_targets, stripped_file_per_target):
×
54
        if stripped_file.value in stripped_files_to_addresses:
×
55
            stripped_files_with_multiple_owners[stripped_file.value].update(
×
56
                {stripped_files_to_addresses[stripped_file.value], tgt.address}
57
            )
58
        else:
59
            stripped_files_to_addresses[stripped_file.value] = tgt.address
×
60

61
    # Remove files with ambiguous owners.
62
    for ambiguous_stripped_f in stripped_files_with_multiple_owners:
×
63
        stripped_files_to_addresses.pop(ambiguous_stripped_f)
×
64

65
    return ProtobufMapping(
×
66
        mapping=FrozenDict(sorted(stripped_files_to_addresses.items())),
67
        ambiguous_modules=FrozenDict(
68
            (k, tuple(sorted(v))) for k, v in sorted(stripped_files_with_multiple_owners.items())
69
        ),
70
    )
71

72

73
# See https://developers.google.com/protocol-buffers/docs/reference/proto3-spec for the Proto
74
# language spec.
UNCOV
75
QUOTE_CHAR = r"(?:'|\")"
×
UNCOV
76
IMPORT_MODIFIERS = r"(?:\spublic|\sweak)?"
×
UNCOV
77
FILE_NAME = r"(.+?\.proto)"
×
78
# NB: We don't specify what a valid file name looks like to avoid accidentally breaking unicode.
UNCOV
79
IMPORT_REGEX = re.compile(rf"import\s*{IMPORT_MODIFIERS}\s*{QUOTE_CHAR}{FILE_NAME}{QUOTE_CHAR}\s*;")
×
80

81

UNCOV
82
def parse_proto_imports(file_content: str) -> FrozenOrderedSet[str]:
×
UNCOV
83
    return FrozenOrderedSet(IMPORT_REGEX.findall(file_content))
×
84

85

UNCOV
86
@dataclass(frozen=True)
×
UNCOV
87
class ProtobufDependencyInferenceFieldSet(FieldSet):
×
UNCOV
88
    required_fields = (ProtobufSourceField, ProtobufDependenciesField)
×
89

UNCOV
90
    source: ProtobufSourceField
×
UNCOV
91
    dependencies: ProtobufDependenciesField
×
92

93

UNCOV
94
class InferProtobufDependencies(InferDependenciesRequest):
×
UNCOV
95
    infer_from = ProtobufDependencyInferenceFieldSet
×
96

97

UNCOV
98
@rule(desc="Inferring Protobuf dependencies by analyzing imports")
×
UNCOV
99
async def infer_protobuf_dependencies(
×
100
    request: InferProtobufDependencies, protobuf_mapping: ProtobufMapping, protoc: Protoc
101
) -> InferredDependencies:
102
    if not protoc.dependency_inference:
×
103
        return InferredDependencies([])
×
104

105
    address = request.field_set.address
×
106
    explicitly_provided_deps, hydrated_sources = await concurrently(
×
107
        determine_explicitly_provided_dependencies(
108
            **implicitly(DependenciesRequest(request.field_set.dependencies))
109
        ),
110
        hydrate_sources(HydrateSourcesRequest(request.field_set.source), **implicitly()),
111
    )
112
    digest_contents = await get_digest_contents(hydrated_sources.snapshot.digest)
×
113
    assert len(digest_contents) == 1
×
114
    file_content = digest_contents[0]
×
115

116
    result: OrderedSet[Address] = OrderedSet()
×
117
    for import_path in parse_proto_imports(file_content.content.decode()):
×
118
        unambiguous = protobuf_mapping.mapping.get(import_path)
×
119
        ambiguous = protobuf_mapping.ambiguous_modules.get(import_path)
×
120
        if unambiguous:
×
121
            result.add(unambiguous)
×
122
        elif ambiguous:
×
123
            explicitly_provided_deps.maybe_warn_of_ambiguous_dependency_inference(
×
124
                ambiguous,
125
                address,
126
                import_reference="file",
127
                context=softwrap(
128
                    f"""
129
                    The target {address} imports `{import_path}` in the file
130
                    {file_content.path}
131
                    """
132
                ),
133
            )
134
            maybe_disambiguated = explicitly_provided_deps.disambiguated(ambiguous)
×
135
            if maybe_disambiguated:
×
136
                result.add(maybe_disambiguated)
×
137
    return InferredDependencies(sorted(result))
×
138

139

UNCOV
140
def rules():
×
UNCOV
141
    return (*collect_rules(), UnionRule(InferDependenciesRequest, InferProtobufDependencies))
×
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