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

pantsbuild / pants / 24145945949

08 Apr 2026 04:14PM UTC coverage: 82.077% (-10.8%) from 92.91%
24145945949

Pull #23233

github

web-flow
Merge 089d98e3c into 9036734c9
Pull Request #23233: Introduce a LockfileFormat enum.

8 of 11 new or added lines in 4 files covered. (72.73%)

7635 existing lines in 306 files now uncovered.

63732 of 77649 relevant lines covered (82.08%)

2.96 hits per line

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

82.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
7✔
5

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

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

50

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

55

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

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

64
    artifacts: ArtifactRequirements
7✔
65
    resolve_name: str
7✔
66

67

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

73

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

80

81
@rule
7✔
82
async def wrap_jvm_lockfile_request(request: GenerateJvmLockfile) -> WrappedGenerateLockfile:
7✔
83
    return WrappedGenerateLockfile(request)
×
84

85

86
@rule(desc="Generate JVM lockfile", level=LogLevel.DEBUG)
7✔
87
async def generate_jvm_lockfile(
7✔
88
    request: GenerateJvmLockfile,
89
    generate_lockfiles_subsystem: GenerateLockfilesSubsystem,
90
) -> GenerateLockfileResult:
UNCOV
91
    resolved_lockfile = await coursier_resolve_lockfile(request.artifacts)
×
UNCOV
92
    regenerate_command = (
×
93
        generate_lockfiles_subsystem.custom_command or f"{bin_name()} generate-lockfiles"
94
    )
95

UNCOV
96
    resolved_lockfile_contents = resolved_lockfile.to_serialized()
×
UNCOV
97
    metadata = JVMLockfileMetadata.new(request.artifacts)
×
UNCOV
98
    resolved_lockfile_contents = metadata.add_header_to_lockfile(
×
99
        resolved_lockfile_contents,
100
        regenerate_command=regenerate_command,
101
        delimeter="#",
102
    )
103

UNCOV
104
    lockfile_digest = await create_digest(
×
105
        CreateDigest([FileContent(request.lockfile_dest, resolved_lockfile_contents)])
106
    )
UNCOV
107
    return GenerateLockfileResult(lockfile_digest, request.resolve_name, request.lockfile_dest)
×
108

109

110
class RequestedJVMUserResolveNames(RequestedUserResolveNames):
7✔
111
    pass
7✔
112

113

114
class KnownJVMUserResolveNamesRequest(KnownUserResolveNamesRequest):
7✔
115
    pass
7✔
116

117

118
@rule
7✔
119
async def determine_jvm_user_resolves(
7✔
120
    _: KnownJVMUserResolveNamesRequest,
121
    jvm_subsystem: JvmSubsystem,
122
    union_membership: UnionMembership,
123
) -> KnownUserResolveNames:
124
    jvm_tool_resolves = ExportableTool.filter_for_subclasses(union_membership, JvmToolBase)
×
125
    names = (*jvm_subsystem.resolves.keys(), *jvm_tool_resolves.keys())
×
126
    return KnownUserResolveNames(
×
127
        names=names,
128
        option_name=f"[{jvm_subsystem.options_scope}].resolves",
129
        requested_resolve_names_cls=RequestedJVMUserResolveNames,
130
    )
131

132

133
@dataclass(frozen=True)
7✔
134
class _ValidateJvmArtifactsRequest:
7✔
135
    artifacts: ArtifactRequirements
7✔
136
    resolve_name: str
7✔
137

138

139
@rule
7✔
140
async def validate_jvm_artifacts_for_resolve(
7✔
141
    request: _ValidateJvmArtifactsRequest,
142
    union_membership: UnionMembership,
143
    jvm_subsystem: JvmSubsystem,
144
) -> GenerateJvmLockfile:
145
    impls = union_membership.get(ValidateJvmArtifactsForResolveRequest)
1✔
146
    for impl in impls:
1✔
147
        validate_request = impl(artifacts=request.artifacts, resolve_name=request.resolve_name)
1✔
148
        _ = await _validate_jvm_artifacts_for_resolve(
1✔
149
            **implicitly({validate_request: ValidateJvmArtifactsForResolveRequest})
150
        )
151

UNCOV
152
    return GenerateJvmLockfile(
×
153
        artifacts=request.artifacts,
154
        resolve_name=request.resolve_name,
155
        lockfile_dest=jvm_subsystem.resolves[request.resolve_name],
156
        diff=False,
157
    )
158

159

160
@rule
7✔
161
async def setup_lockfile_request_from_tool(
7✔
162
    request: GenerateJvmLockfileFromTool,
163
) -> GenerateJvmLockfile:
164
    artifacts = await gather_coordinates_for_jvm_lockfile(
1✔
165
        GatherJvmCoordinatesRequest(request.artifact_inputs, request.artifact_option_name)
166
    )
167
    return GenerateJvmLockfile(
1✔
168
        artifacts=artifacts,
169
        resolve_name=request.resolve_name,
170
        lockfile_dest=(
171
            request.lockfile if request.lockfile != DEFAULT_TOOL_LOCKFILE else DEFAULT_TOOL_LOCKFILE
172
        ),
173
        diff=False,
174
    )
175

176

177
async def _plan_generate_lockfile(
7✔
178
    resolve, resolve_to_artifacts, tools
179
) -> Coroutine[Any, Any, GenerateJvmLockfile]:
180
    """Generate a JVM lockfile request for each requested resolve.
181

182
    This step also allows other backends to validate the proposed set of artifact requirements for
183
    each resolve.
184
    """
185
    if resolve in resolve_to_artifacts:
1✔
186
        return validate_jvm_artifacts_for_resolve(
1✔
187
            _ValidateJvmArtifactsRequest(
188
                artifacts=ArtifactRequirements(resolve_to_artifacts[resolve]),
189
                resolve_name=resolve,
190
            ),
191
            **implicitly(),
192
        )
193
    elif resolve in tools:
1✔
UNCOV
194
        tool_cls: type[JvmToolBase] = tools[resolve]
×
UNCOV
195
        tool = await _construct_subsystem(tool_cls)
×
196

UNCOV
197
        return setup_lockfile_request_from_tool(
×
198
            GenerateJvmLockfileFromTool.create(tool),
199
        )
200

201
    else:
202
        return validate_jvm_artifacts_for_resolve(
1✔
203
            _ValidateJvmArtifactsRequest(
204
                artifacts=ArtifactRequirements(()),
205
                resolve_name=resolve,
206
            ),
207
            **implicitly(),
208
        )
209

210

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

226
    tools = ExportableTool.filter_for_subclasses(union_membership, JvmToolBase)
1✔
227

228
    rule_calls: list[Coroutine[Any, Any, GenerateJvmLockfile]] = []
1✔
229
    for resolve in requested:
1✔
230
        rule_calls.append(await _plan_generate_lockfile(resolve, resolve_to_artifacts, tools))
1✔
231

232
    jvm_lockfile_requests = await concurrently(rule_calls)
1✔
233

UNCOV
234
    return UserGenerateLockfiles(jvm_lockfile_requests)
×
235

236

237
def rules():
7✔
238
    return (
7✔
239
        *collect_rules(),
240
        *coursier_fetch.rules(),
241
        UnionRule(GenerateLockfile, GenerateJvmLockfile),
242
        UnionRule(KnownUserResolveNamesRequest, KnownJVMUserResolveNamesRequest),
243
        UnionRule(RequestedUserResolveNames, RequestedJVMUserResolveNames),
244
    )
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