• 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

95.83
/src/python/pants/jvm/goals/lockfile.py
1
# Copyright 2022 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
from __future__ import annotations
12✔
5

6
from collections import defaultdict
12✔
7
from collections.abc import Coroutine, Mapping
12✔
8
from dataclasses import dataclass
12✔
9
from typing import Any
12✔
10

11
from pants.core.goals.generate_lockfiles import (
12✔
12
    DEFAULT_TOOL_LOCKFILE,
13
    GenerateLockfile,
14
    GenerateLockfileResult,
15
    GenerateLockfilesSubsystem,
16
    KnownUserResolveNames,
17
    KnownUserResolveNamesRequest,
18
    RequestedUserResolveNames,
19
    UserGenerateLockfiles,
20
)
21
from pants.core.goals.resolves import ExportableTool
12✔
22
from pants.engine.environment import EnvironmentName
12✔
23
from pants.engine.fs import CreateDigest, FileContent
12✔
24
from pants.engine.internals.selectors import concurrently
12✔
25
from pants.engine.intrinsics import create_digest
12✔
26
from pants.engine.rules import collect_rules, implicitly, rule
12✔
27
from pants.engine.target import AllTargets
12✔
28
from pants.engine.unions import UnionMembership, UnionRule, union
12✔
29
from pants.jvm.resolve import coursier_fetch
12✔
30
from pants.jvm.resolve.common import (
12✔
31
    ArtifactRequirement,
32
    ArtifactRequirements,
33
    GatherJvmCoordinatesRequest,
34
)
35
from pants.jvm.resolve.coursier_fetch import coursier_resolve_lockfile
12✔
36
from pants.jvm.resolve.jvm_tool import (
12✔
37
    GenerateJvmLockfileFromTool,
38
    JvmToolBase,
39
    gather_coordinates_for_jvm_lockfile,
40
)
41
from pants.jvm.resolve.lockfile_metadata import JVMLockfileMetadata
12✔
42
from pants.jvm.subsystems import JvmSubsystem
12✔
43
from pants.jvm.target_types import JvmArtifactResolveField, JvmResolveField
12✔
44
from pants.option.subsystem import _construct_subsystem
12✔
45
from pants.util.docutil import bin_name
12✔
46
from pants.util.logging import LogLevel
12✔
47
from pants.util.ordered_set import OrderedSet
12✔
48

49

50
@dataclass(frozen=True)
12✔
51
class GenerateJvmLockfile(GenerateLockfile):
12✔
52
    artifacts: ArtifactRequirements
12✔
53

54

55
@union(in_scope_types=[EnvironmentName])
12✔
56
@dataclass(frozen=True)
12✔
57
class ValidateJvmArtifactsForResolveRequest:
12✔
58
    """Hook for backends to validate the artifact requirements requested for a resolve.
59

60
    The main user is the Scala backend which will ensure scala-library is present in the resolve.
61
    """
62

63
    artifacts: ArtifactRequirements
12✔
64
    resolve_name: str
12✔
65

66

67
@dataclass(frozen=True)
12✔
68
class ValidateJvmArtifactsForResolveResult:
12✔
69
    """Sentinel type that represents that a backend is satisfied with the artifacts for a JVM
70
    resolve."""
71

72

73
@rule(polymorphic=True)
12✔
74
async def _validate_jvm_artifacts_for_resolve(
12✔
75
    req: ValidateJvmArtifactsForResolveRequest, env_name: EnvironmentName
76
) -> ValidateJvmArtifactsForResolveResult:
77
    raise NotImplementedError()
×
78

79

80
@rule(desc="Generate JVM lockfile", level=LogLevel.DEBUG)
12✔
81
async def generate_jvm_lockfile(
12✔
82
    request: GenerateJvmLockfile,
83
    generate_lockfiles_subsystem: GenerateLockfilesSubsystem,
84
) -> GenerateLockfileResult:
85
    resolved_lockfile = await coursier_resolve_lockfile(request.artifacts)
1✔
86
    regenerate_command = (
1✔
87
        generate_lockfiles_subsystem.custom_command or f"{bin_name()} generate-lockfiles"
88
    )
89

90
    resolved_lockfile_contents = resolved_lockfile.to_serialized()
1✔
91
    metadata = JVMLockfileMetadata.new(request.artifacts)
1✔
92
    resolved_lockfile_contents = metadata.add_header_to_lockfile(
1✔
93
        resolved_lockfile_contents,
94
        regenerate_command=regenerate_command,
95
        delimeter="#",
96
    )
97

98
    lockfile_digest = await create_digest(
1✔
99
        CreateDigest([FileContent(request.lockfile_dest, resolved_lockfile_contents)])
100
    )
101
    return GenerateLockfileResult(lockfile_digest, request.resolve_name, request.lockfile_dest)
1✔
102

103

104
class RequestedJVMUserResolveNames(RequestedUserResolveNames):
12✔
105
    pass
12✔
106

107

108
class KnownJVMUserResolveNamesRequest(KnownUserResolveNamesRequest):
12✔
109
    pass
12✔
110

111

112
@rule
12✔
113
async def determine_jvm_user_resolves(
12✔
114
    _: KnownJVMUserResolveNamesRequest,
115
    jvm_subsystem: JvmSubsystem,
116
    union_membership: UnionMembership,
117
) -> KnownUserResolveNames:
118
    jvm_tool_resolves = ExportableTool.filter_for_subclasses(union_membership, JvmToolBase)
×
119
    names = (*jvm_subsystem.resolves.keys(), *jvm_tool_resolves.keys())
×
120
    return KnownUserResolveNames(
×
121
        names=names,
122
        option_name=f"[{jvm_subsystem.options_scope}].resolves",
123
        requested_resolve_names_cls=RequestedJVMUserResolveNames,
124
    )
125

126

127
@dataclass(frozen=True)
12✔
128
class _ValidateJvmArtifactsRequest:
12✔
129
    artifacts: ArtifactRequirements
12✔
130
    resolve_name: str
12✔
131

132

133
@rule
12✔
134
async def validate_jvm_artifacts_for_resolve(
12✔
135
    request: _ValidateJvmArtifactsRequest,
136
    union_membership: UnionMembership,
137
    jvm_subsystem: JvmSubsystem,
138
) -> GenerateJvmLockfile:
139
    impls = union_membership.get(ValidateJvmArtifactsForResolveRequest)
2✔
140
    for impl in impls:
2✔
141
        validate_request = impl(artifacts=request.artifacts, resolve_name=request.resolve_name)
1✔
142
        _ = await _validate_jvm_artifacts_for_resolve(
1✔
143
            **implicitly({validate_request: ValidateJvmArtifactsForResolveRequest})
144
        )
145

146
    return GenerateJvmLockfile(
1✔
147
        artifacts=request.artifacts,
148
        resolve_name=request.resolve_name,
149
        lockfile_dest=jvm_subsystem.resolves[request.resolve_name],
150
        diff=False,
151
    )
152

153

154
@rule
12✔
155
async def setup_lockfile_request_from_tool(
12✔
156
    request: GenerateJvmLockfileFromTool,
157
) -> GenerateJvmLockfile:
158
    artifacts = await gather_coordinates_for_jvm_lockfile(
2✔
159
        GatherJvmCoordinatesRequest(request.artifact_inputs, request.artifact_option_name)
160
    )
161
    return GenerateJvmLockfile(
2✔
162
        artifacts=artifacts,
163
        resolve_name=request.resolve_name,
164
        lockfile_dest=(
165
            request.lockfile if request.lockfile != DEFAULT_TOOL_LOCKFILE else DEFAULT_TOOL_LOCKFILE
166
        ),
167
        diff=False,
168
    )
169

170

171
async def _plan_generate_lockfile(
12✔
172
    resolve, resolve_to_artifacts, tools
173
) -> Coroutine[Any, Any, GenerateJvmLockfile]:
174
    """Generate a JVM lockfile request for each requested resolve.
175

176
    This step also allows other backends to validate the proposed set of artifact requirements for
177
    each resolve.
178
    """
179
    if resolve in resolve_to_artifacts:
2✔
180
        return validate_jvm_artifacts_for_resolve(
2✔
181
            _ValidateJvmArtifactsRequest(
182
                artifacts=ArtifactRequirements(resolve_to_artifacts[resolve]),
183
                resolve_name=resolve,
184
            ),
185
            **implicitly(),
186
        )
187
    elif resolve in tools:
2✔
188
        tool_cls: type[JvmToolBase] = tools[resolve]
1✔
189
        tool = await _construct_subsystem(tool_cls)
1✔
190

191
        return setup_lockfile_request_from_tool(
1✔
192
            GenerateJvmLockfileFromTool.create(tool),
193
        )
194

195
    else:
196
        return validate_jvm_artifacts_for_resolve(
1✔
197
            _ValidateJvmArtifactsRequest(
198
                artifacts=ArtifactRequirements(()),
199
                resolve_name=resolve,
200
            ),
201
            **implicitly(),
202
        )
203

204

205
@rule
12✔
206
async def setup_user_lockfile_requests(
12✔
207
    requested: RequestedJVMUserResolveNames,
208
    all_targets: AllTargets,
209
    jvm_subsystem: JvmSubsystem,
210
    union_membership: UnionMembership,
211
) -> UserGenerateLockfiles:
212
    resolve_to_artifacts: Mapping[str, OrderedSet[ArtifactRequirement]] = defaultdict(OrderedSet)
2✔
213
    for tgt in sorted(all_targets, key=lambda t: t.address):
2✔
214
        if not tgt.has_field(JvmArtifactResolveField):
2✔
215
            continue
1✔
216
        artifact = ArtifactRequirement.from_jvm_artifact_target(tgt)
2✔
217
        resolve = tgt[JvmResolveField].normalized_value(jvm_subsystem)
2✔
218
        resolve_to_artifacts[resolve].add(artifact)
2✔
219

220
    tools = ExportableTool.filter_for_subclasses(union_membership, JvmToolBase)
2✔
221

222
    rule_calls: list[Coroutine[Any, Any, GenerateJvmLockfile]] = []
2✔
223
    for resolve in requested:
2✔
224
        rule_calls.append(await _plan_generate_lockfile(resolve, resolve_to_artifacts, tools))
2✔
225

226
    jvm_lockfile_requests = await concurrently(rule_calls)
2✔
227

228
    return UserGenerateLockfiles(jvm_lockfile_requests)
1✔
229

230

231
def rules():
12✔
232
    return (
12✔
233
        *collect_rules(),
234
        *coursier_fetch.rules(),
235
        UnionRule(GenerateLockfile, GenerateJvmLockfile),
236
        UnionRule(KnownUserResolveNamesRequest, KnownJVMUserResolveNamesRequest),
237
        UnionRule(RequestedUserResolveNames, RequestedJVMUserResolveNames),
238
    )
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