• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In
You are now the owner of this repo.

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

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

5
import json
11✔
6
import logging
11✔
7
import os
11✔
8
from dataclasses import dataclass
11✔
9

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

43
logger = logging.getLogger(__name__)
11✔
44

45

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

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

53

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

58

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

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

66

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

71

72
class AllGoModTargets(Targets):
11✔
73
    pass
11✔
74

75

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

80

81
@rule
11✔
82
async def find_nearest_ancestor_go_mod(
11✔
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(
11✔
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(
11✔
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:
11✔
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)
11✔
110

111

112
async def _find_explict_owning_go_mod_address(
11✔
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:
2✔
120
        # If so, that is the default.
121
        if len(all_go_mod_targets) == 1:
2✔
122
            return all_go_mod_targets[0].address
2✔
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

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

178
    if target.has_field(GoModDependenciesField):
11✔
179
        return OwningGoMod(request.address)
9✔
180

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

187
    if target.has_field(GoThirdPartyPackageDependenciesField):
6✔
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()
6✔
190
        return OwningGoMod(generator_address)
6✔
191

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

201
    if target.has_field(GoOwningGoModAddressField):
2✔
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(
2✔
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)
2✔
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

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

226

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

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

237

238
@rule
11✔
239
async def determine_go_mod_info(
11✔
240
    request: GoModInfoRequest,
241
) -> GoModInfo:
242
    if isinstance(request.source, Address):
11✔
243
        wrapped_target = await resolve_target(
11✔
244
            WrappedTargetRequest(request.source, description_of_origin="<go mod info rule>"),
245
            **implicitly(),
246
        )
247
        sources_field = wrapped_target.target[GoModSourcesField]
11✔
248
    else:
249
        sources_field = request.source
10✔
250
    go_mod_path = sources_field.go_mod_path
11✔
251
    go_mod_dir = os.path.dirname(go_mod_path)
11✔
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())
11✔
255
    sources_digest = hydrated_sources.snapshot.digest
11✔
256

257
    mod_json = await fallible_to_exec_result_or_raise(
11✔
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)
11✔
268
    return GoModInfo(
11✔
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

276
def rules():
11✔
277
    return (
11✔
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