• 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/go/util_rules/go_mod.py
1
# Copyright 2021 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 json
×
UNCOV
6
import logging
×
UNCOV
7
import os
×
UNCOV
8
from dataclasses import dataclass
×
9

UNCOV
10
from pants.backend.go.target_types import (
×
11
    GoBinaryMainPackageField,
12
    GoModDependenciesField,
13
    GoModSourcesField,
14
    GoModTarget,
15
    GoOwningGoModAddressField,
16
    GoPackageSourcesField,
17
    GoThirdPartyPackageDependenciesField,
18
)
UNCOV
19
from pants.backend.go.util_rules import binary
×
UNCOV
20
from pants.backend.go.util_rules.binary import (
×
21
    GoBinaryMainPackageRequest,
22
    determine_main_pkg_for_go_binary,
23
)
UNCOV
24
from pants.backend.go.util_rules.sdk import GoSdkProcess
×
UNCOV
25
from pants.base.specs import AncestorGlobSpec, RawSpecs
×
UNCOV
26
from pants.build_graph.address import Address, AddressInput
×
UNCOV
27
from pants.engine.engine_aware import EngineAwareParameter
×
UNCOV
28
from pants.engine.fs import Digest
×
UNCOV
29
from pants.engine.internals.build_files import resolve_address
×
UNCOV
30
from pants.engine.internals.graph import hydrate_sources, resolve_target, resolve_unexpanded_targets
×
UNCOV
31
from pants.engine.process import fallible_to_exec_result_or_raise
×
UNCOV
32
from pants.engine.rules import collect_rules, implicitly, rule
×
UNCOV
33
from pants.engine.target import (
×
34
    AllUnexpandedTargets,
35
    HydrateSourcesRequest,
36
    InvalidTargetException,
37
    Targets,
38
    WrappedTargetRequest,
39
)
UNCOV
40
from pants.util.docutil import bin_name
×
UNCOV
41
from pants.util.logging import LogLevel
×
42

UNCOV
43
logger = logging.getLogger(__name__)
×
44

45

UNCOV
46
@dataclass(frozen=True)
×
UNCOV
47
class OwningGoModRequest(EngineAwareParameter):
×
UNCOV
48
    address: Address
×
49

UNCOV
50
    def debug_hint(self) -> str:
×
51
        return self.address.spec
×
52

53

UNCOV
54
@dataclass(frozen=True)
×
UNCOV
55
class OwningGoMod:
×
UNCOV
56
    address: Address
×
57

58

UNCOV
59
@dataclass(frozen=True)
×
UNCOV
60
class NearestAncestorGoModRequest(EngineAwareParameter):
×
UNCOV
61
    address: Address
×
62

UNCOV
63
    def debug_hint(self) -> str | None:
×
64
        return self.address.spec
×
65

66

UNCOV
67
@dataclass(frozen=True)
×
UNCOV
68
class NearestAncestorGoModResult:
×
UNCOV
69
    address: Address
×
70

71

UNCOV
72
class AllGoModTargets(Targets):
×
UNCOV
73
    pass
×
74

75

UNCOV
76
@rule(desc="Find all `go_mod` targets in project", level=LogLevel.DEBUG)
×
UNCOV
77
async def find_all_go_mod_targets(targets: AllUnexpandedTargets) -> AllGoModTargets:
×
78
    return AllGoModTargets(tgt for tgt in targets if tgt.has_field(GoModDependenciesField))
×
79

80

UNCOV
81
@rule
×
UNCOV
82
async def find_nearest_ancestor_go_mod(
×
83
    request: NearestAncestorGoModRequest,
84
) -> NearestAncestorGoModResult:
85
    # We don't expect `go_mod` targets to be generated, so we can use UnexpandedTargets.
86
    candidate_targets = await resolve_unexpanded_targets(
×
87
        **implicitly(
88
            RawSpecs(
89
                ancestor_globs=(AncestorGlobSpec(request.address.spec_path),),
90
                description_of_origin="the `OwningGoMod` rule",
91
            )
92
        )
93
    )
94

95
    # Sort by address.spec_path in descending order so the nearest go_mod target is sorted first.
96
    go_mod_targets = sorted(
×
97
        (tgt for tgt in candidate_targets if tgt.has_field(GoModSourcesField)),
98
        key=lambda tgt: tgt.address.spec_path,
99
        reverse=True,
100
    )
101

102
    if not go_mod_targets:
×
103
        raise InvalidTargetException(
×
104
            f"The target {request.address} does not have a `go_mod` target in its BUILD file or "
105
            "any ancestor BUILD files. To fix, please make sure your project has a `go.mod` file "
106
            f"and add a `go_mod` target (you can run `{bin_name()} tailor` to do this)."
107
        )
108

109
    return NearestAncestorGoModResult(go_mod_targets[0].address)
×
110

111

UNCOV
112
async def _find_explict_owning_go_mod_address(
×
113
    address: Address,
114
    field: GoOwningGoModAddressField,
115
    alias: str,
116
    all_go_mod_targets: AllGoModTargets,
117
) -> Address:
118
    # If no value is specified, then see if there is only one `go_mod` target in this repository.
119
    if field.value is None:
×
120
        # If so, that is the default.
121
        if len(all_go_mod_targets) == 1:
×
122
            return all_go_mod_targets[0].address
×
123

124
        # Otherwise error and inform user to specify the owning `go_mod` target's address.
125
        if not all_go_mod_targets:
×
126
            raise InvalidTargetException(
×
127
                f"The `{alias}` target `{address}` requires that the address of an owning `{GoModTarget.alias}` "
128
                f"target be given via the `{GoOwningGoModAddressField.alias}` field since it is "
129
                "a dependency of a Go target. However, there are no `go_mod` targets in this repository."
130
            )
131

132
        raise InvalidTargetException(
×
133
            f"The `{alias}` target `{address}` requires that the address of an owning `{GoModTarget.alias}` "
134
            f"target be given via the `{GoOwningGoModAddressField.alias}` field since it is "
135
            "a dependency of a Go target. However, there are multiple `go_mod` targets in this repository "
136
            "which makes the choice of which `go_mod` target to use ambiguous. Please specify which of the "
137
            "following addresses to use or consider using the `parametrize` builtin to specify more than "
138
            "one of these addresses if this target will be used in multiple Go modules: "
139
            f"{', '.join([str(tgt.address) for tgt in all_go_mod_targets])}"
140
        )
141

142
    # If a value is specified, then resolve it as an address.
143
    address_input = AddressInput.parse(
×
144
        field.value,
145
        relative_to=address.spec_path,
146
        description_of_origin=f"the `{GoOwningGoModAddressField.alias}` field of target `{address}`",
147
    )
148
    candidate_go_mod_address = await resolve_address(**implicitly({address_input: AddressInput}))
×
149
    wrapped_target = await resolve_target(
×
150
        WrappedTargetRequest(
151
            candidate_go_mod_address,
152
            description_of_origin=f"the `{GoOwningGoModAddressField.alias}` field of target `{address}`",
153
        ),
154
        **implicitly(),
155
    )
156
    if not wrapped_target.target.has_field(GoModDependenciesField):
×
157
        raise InvalidTargetException(
×
158
            f"The `{alias}` target `{address}` requires that the address of an owning `{GoModTarget.alias}` "
159
            f"target be given via the `{GoOwningGoModAddressField.alias}` field since it is "
160
            f"a dependency of a Go target. However, the provided address `{field.value}` does not refer to "
161
            f"a `{GoModTarget.alias}` target. Please specify which of the following addresses to use or consider "
162
            "using the `parametrize` builtin to specify more than one of these addresses if this target will be "
163
            f"used in multiple Go modules: {', '.join([str(tgt.address) for tgt in all_go_mod_targets])}"
164
        )
165
    return candidate_go_mod_address
×
166

167

UNCOV
168
@rule
×
UNCOV
169
async def find_owning_go_mod(
×
170
    request: OwningGoModRequest, all_go_mod_targets: AllGoModTargets
171
) -> OwningGoMod:
172
    wrapped_target = await resolve_target(
×
173
        WrappedTargetRequest(request.address, description_of_origin="the `OwningGoMod` rule"),
174
        **implicitly(),
175
    )
176
    target = wrapped_target.target
×
177

178
    if target.has_field(GoModDependenciesField):
×
179
        return OwningGoMod(request.address)
×
180

181
    if target.has_field(GoPackageSourcesField):
×
182
        nearest_go_mod_result = await find_nearest_ancestor_go_mod(
×
183
            NearestAncestorGoModRequest(request.address)
184
        )
185
        return OwningGoMod(nearest_go_mod_result.address)
×
186

187
    if target.has_field(GoThirdPartyPackageDependenciesField):
×
188
        # For `go_third_party_package` targets, use the generator which is the owning `go_mod` target.
189
        generator_address = target.address.maybe_convert_to_target_generator()
×
190
        return OwningGoMod(generator_address)
×
191

192
    if target.has_field(GoBinaryMainPackageField):
×
193
        main_pkg = await determine_main_pkg_for_go_binary(
×
194
            GoBinaryMainPackageRequest(target.get(GoBinaryMainPackageField))
195
        )
196
        owning_go_mod_for_main_pkg = await find_owning_go_mod(
×
197
            OwningGoModRequest(main_pkg.address), **implicitly()
198
        )
199
        return owning_go_mod_for_main_pkg
×
200

201
    if target.has_field(GoOwningGoModAddressField):
×
202
        # Otherwise, find any explicitly defined go_mod address (e.g., for `protobuf_sources` targets).
203
        explicit_go_mod_address = await _find_explict_owning_go_mod_address(
×
204
            address=request.address,
205
            field=target.get(GoOwningGoModAddressField),
206
            alias=target.alias,
207
            all_go_mod_targets=all_go_mod_targets,
208
        )
209
        return OwningGoMod(explicit_go_mod_address)
×
210

211
    raise AssertionError(
×
212
        f"Internal error: Unable to determine how to determine the owning `{GoModTarget.alias}` target "
213
        f"for `{target.alias}` target `{target.address}`. Please file an issue at "
214
        "https://github.com/pantsbuild/pants/issues/new."
215
    )
216

217

UNCOV
218
@dataclass(frozen=True)
×
UNCOV
219
class GoModInfo:
×
220
    # Import path of the Go module, based on the `module` in `go.mod`.
UNCOV
221
    import_path: str
×
UNCOV
222
    digest: Digest
×
UNCOV
223
    mod_path: str
×
UNCOV
224
    minimum_go_version: str | None
×
225

226

UNCOV
227
@dataclass(frozen=True)
×
UNCOV
228
class GoModInfoRequest(EngineAwareParameter):
×
UNCOV
229
    source: Address | GoModSourcesField
×
230

UNCOV
231
    def debug_hint(self) -> str:
×
232
        if isinstance(self.source, Address):
×
233
            return self.source.spec
×
234
        else:
235
            return self.source.address.spec
×
236

237

UNCOV
238
@rule
×
UNCOV
239
async def determine_go_mod_info(
×
240
    request: GoModInfoRequest,
241
) -> GoModInfo:
242
    if isinstance(request.source, Address):
×
243
        wrapped_target = await resolve_target(
×
244
            WrappedTargetRequest(request.source, description_of_origin="<go mod info rule>"),
245
            **implicitly(),
246
        )
247
        sources_field = wrapped_target.target[GoModSourcesField]
×
248
    else:
249
        sources_field = request.source
×
250
    go_mod_path = sources_field.go_mod_path
×
251
    go_mod_dir = os.path.dirname(go_mod_path)
×
252

253
    # Get the `go.mod` (and `go.sum`) and strip so the file has no directory prefix.
254
    hydrated_sources = await hydrate_sources(HydrateSourcesRequest(sources_field), **implicitly())
×
255
    sources_digest = hydrated_sources.snapshot.digest
×
256

257
    mod_json = await fallible_to_exec_result_or_raise(
×
258
        **implicitly(
259
            GoSdkProcess(
260
                command=("mod", "edit", "-json"),
261
                input_digest=sources_digest,
262
                working_dir=go_mod_dir,
263
                description=f"Parse {go_mod_path}",
264
            )
265
        )
266
    )
267
    module_metadata = json.loads(mod_json.stdout)
×
268
    return GoModInfo(
×
269
        import_path=module_metadata["Module"]["Path"],
270
        digest=sources_digest,
271
        mod_path=go_mod_path,
272
        minimum_go_version=module_metadata.get("Go"),
273
    )
274

275

UNCOV
276
def rules():
×
UNCOV
277
    return (
×
278
        *collect_rules(),
279
        *binary.rules(),
280
    )
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