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

pantsbuild / pants / 26080722777

19 May 2026 06:37AM UTC coverage: 52.106% (-11.5%) from 63.597%
26080722777

Pull #23250

github

web-flow
Merge 63ec06323 into 2693df832
Pull Request #23250: Feature: Add generic option to docker image

12 of 50 new or added lines in 3 files covered. (24.0%)

5382 existing lines in 201 files now uncovered.

32053 of 61515 relevant lines covered (52.11%)

1.04 hits per line

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

60.42
/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
2✔
5

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

11
from pants.core.goals.generate_lockfiles import (
2✔
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
2✔
22
from pants.engine.environment import EnvironmentName
2✔
23
from pants.engine.fs import CreateDigest, FileContent
2✔
24
from pants.engine.internals.selectors import concurrently
2✔
25
from pants.engine.intrinsics import create_digest
2✔
26
from pants.engine.rules import collect_rules, implicitly, rule
2✔
27
from pants.engine.target import AllTargets
2✔
28
from pants.engine.unions import UnionMembership, UnionRule, union
2✔
29
from pants.jvm.resolve import coursier_fetch
2✔
30
from pants.jvm.resolve.common import (
2✔
31
    ArtifactRequirement,
32
    ArtifactRequirements,
33
    GatherJvmCoordinatesRequest,
34
)
35
from pants.jvm.resolve.coursier_fetch import coursier_resolve_lockfile
2✔
36
from pants.jvm.resolve.jvm_tool import (
2✔
37
    GenerateJvmLockfileFromTool,
38
    JvmToolBase,
39
    gather_coordinates_for_jvm_lockfile,
40
)
41
from pants.jvm.resolve.lockfile_metadata import JVMLockfileMetadata
2✔
42
from pants.jvm.subsystems import JvmSubsystem
2✔
43
from pants.jvm.target_types import JvmArtifactResolveField, JvmResolveField
2✔
44
from pants.option.subsystem import _construct_subsystem
2✔
45
from pants.util.docutil import bin_name
2✔
46
from pants.util.logging import LogLevel
2✔
47
from pants.util.ordered_set import OrderedSet
2✔
48

49

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

54

55
@union(in_scope_types=[EnvironmentName])
2✔
56
@dataclass(frozen=True)
2✔
57
class ValidateJvmArtifactsForResolveRequest:
2✔
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
2✔
64
    resolve_name: str
2✔
65

66

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

72

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

79

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

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

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

103

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

107

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

111

112
@rule
2✔
113
async def determine_jvm_user_resolves(
2✔
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)
2✔
128
class _ValidateJvmArtifactsRequest:
2✔
129
    artifacts: ArtifactRequirements
2✔
130
    resolve_name: str
2✔
131

132

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

UNCOV
146
    return GenerateJvmLockfile(
×
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
2✔
155
async def setup_lockfile_request_from_tool(
2✔
156
    request: GenerateJvmLockfileFromTool,
157
) -> GenerateJvmLockfile:
UNCOV
158
    artifacts = await gather_coordinates_for_jvm_lockfile(
×
159
        GatherJvmCoordinatesRequest(request.artifact_inputs, request.artifact_option_name)
160
    )
UNCOV
161
    return GenerateJvmLockfile(
×
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(
2✔
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
    """
UNCOV
179
    if resolve in resolve_to_artifacts:
×
UNCOV
180
        return validate_jvm_artifacts_for_resolve(
×
181
            _ValidateJvmArtifactsRequest(
182
                artifacts=ArtifactRequirements(resolve_to_artifacts[resolve]),
183
                resolve_name=resolve,
184
            ),
185
            **implicitly(),
186
        )
UNCOV
187
    elif resolve in tools:
×
UNCOV
188
        tool_cls: type[JvmToolBase] = tools[resolve]
×
UNCOV
189
        tool = await _construct_subsystem(tool_cls)
×
190

UNCOV
191
        return setup_lockfile_request_from_tool(
×
192
            GenerateJvmLockfileFromTool.create(tool),
193
        )
194

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

204

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

UNCOV
220
    tools = ExportableTool.filter_for_subclasses(union_membership, JvmToolBase)
×
221

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

UNCOV
226
    jvm_lockfile_requests = await concurrently(rule_calls)
×
227

UNCOV
228
    return UserGenerateLockfiles(jvm_lockfile_requests)
×
229

230

231
def rules():
2✔
232
    return (
2✔
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