• 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/jvm/resolve/jvm_tool.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 os
×
UNCOV
6
from dataclasses import dataclass
×
UNCOV
7
from typing import ClassVar
×
8

UNCOV
9
from pants.build_graph.address import AddressInput
×
UNCOV
10
from pants.core.goals.generate_lockfiles import DEFAULT_TOOL_LOCKFILE
×
UNCOV
11
from pants.core.goals.resolves import ExportableTool
×
UNCOV
12
from pants.engine.addresses import Addresses
×
UNCOV
13
from pants.engine.internals.build_files import resolve_address
×
UNCOV
14
from pants.engine.internals.graph import resolve_targets
×
UNCOV
15
from pants.engine.internals.selectors import concurrently
×
UNCOV
16
from pants.engine.rules import collect_rules, implicitly, rule
×
UNCOV
17
from pants.jvm.resolve.common import (
×
18
    ArtifactRequirement,
19
    ArtifactRequirements,
20
    GatherJvmCoordinatesRequest,
21
)
UNCOV
22
from pants.jvm.resolve.coordinate import Coordinate
×
UNCOV
23
from pants.jvm.target_types import JvmArtifactFieldSet
×
UNCOV
24
from pants.option.option_types import StrListOption, StrOption
×
UNCOV
25
from pants.option.subsystem import Subsystem
×
UNCOV
26
from pants.util.docutil import bin_name, git_url
×
UNCOV
27
from pants.util.meta import classproperty
×
UNCOV
28
from pants.util.ordered_set import FrozenOrderedSet
×
UNCOV
29
from pants.util.strutil import softwrap
×
30

31

UNCOV
32
class JvmToolBase(Subsystem, ExportableTool):
×
33
    """Base class for subsystems that configure a set of artifact requirements for a JVM tool."""
34

35
    # Default version of the tool. (Subclasses may set.)
UNCOV
36
    default_version: ClassVar[str | None] = None
×
37

38
    # Default artifacts for the tool in GROUP:NAME format. The `--version` value will be used for the
39
    # artifact version if it has not been specified for a particular requirement. (Subclasses must set.)
UNCOV
40
    default_artifacts: ClassVar[tuple[str, ...]]
×
41

42
    # Default resource for the tool's lockfile. (Subclasses must set.)
UNCOV
43
    default_lockfile_resource: ClassVar[tuple[str, str]]
×
44

UNCOV
45
    version = StrOption(
×
46
        advanced=True,
47
        default=lambda cls: cls.default_version,
48
        help=lambda cls: softwrap(
49
            f"""
50
            Version string for the tool. This is available for substitution in the
51
            `[{cls.options_scope}].artifacts` option by including the string `{{version}}`.
52
            """
53
        ),
54
    )
UNCOV
55
    artifacts = StrListOption(
×
56
        advanced=True,
57
        default=lambda cls: list(cls.default_artifacts),
58
        help=lambda cls: softwrap(
59
            f"""
60
            Artifact requirements for this tool using specified as either the address of a `jvm_artifact`
61
            target or, alternatively, as a colon-separated Maven coordinates (e.g., `group:name:version`).
62
            For Maven coordinates, the string `{{version}}` version will be substituted with the value of the
63
            `[{cls.options_scope}].version` option.
64
            """
65
        ),
66
    )
UNCOV
67
    lockfile = StrOption(
×
68
        default=DEFAULT_TOOL_LOCKFILE,
69
        advanced=True,
70
        help=lambda cls: softwrap(
71
            f"""
72
            Path to a lockfile used for installing the tool.
73

74
            Set to the string `{DEFAULT_TOOL_LOCKFILE}` to use a lockfile provided by
75
            Pants, so long as you have not changed the `--version` option.
76
            See {cls.default_lockfile_url} for the default lockfile contents.
77

78
            To use a custom lockfile, set this option to a file path relative to the
79
            build root, then run `{bin_name()} generate-lockfiles
80
            --resolve={cls.options_scope}`.
81
            """
82
        ),
83
    )
UNCOV
84
    jvm_options = StrListOption(
×
85
        help=lambda cls: softwrap(
86
            f"""
87
            List of JVM options to pass to `{cls.options_scope}` JVM processes.
88

89
            Options set here will be added to those set in `[jvm].global_options`. Please
90
            check the documentation for the `jvm` subsystem to see what values are accepted here.
91
            """
92
        ),
93
        advanced=True,
94
    )
95

UNCOV
96
    @classproperty
×
UNCOV
97
    def default_lockfile_url(cls) -> str:
×
UNCOV
98
        return git_url(
×
99
            os.path.join(
100
                "src",
101
                "python",
102
                cls.default_lockfile_resource[0].replace(".", os.path.sep),
103
                cls.default_lockfile_resource[1],
104
            )
105
        )
106

UNCOV
107
    @classmethod
×
UNCOV
108
    def help_for_generate_lockfile_with_default_location(cls, resolve_name):
×
109
        return softwrap(
×
110
            f"""
111
            You requested to generate a lockfile for {resolve_name} because
112
            you included it in `--generate-lockfiles-resolve`, but
113
            {resolve_name} is a tool using its default lockfile.
114

115
            If you would like to generate a lockfile for {resolve_name}, please
116
            set `[{resolve_name}].lockfile` to the path where it should be
117
                    generated and run again.
118
            """
119
        )
120

UNCOV
121
    @property
×
UNCOV
122
    def artifact_inputs(self) -> tuple[str, ...]:
×
UNCOV
123
        return tuple(s.format(version=self.version) for s in self.artifacts)
×
124

125

UNCOV
126
@rule
×
UNCOV
127
async def gather_coordinates_for_jvm_lockfile(
×
128
    request: GatherJvmCoordinatesRequest,
129
) -> ArtifactRequirements:
130
    # Separate `artifact_inputs` by whether the strings parse as an `Address` or not.
131
    requirements: set[ArtifactRequirement] = set()
×
132
    candidate_address_inputs: set[AddressInput] = set()
×
133
    bad_artifact_inputs = []
×
134
    for artifact_input in request.artifact_inputs:
×
135
        # Try parsing as a `Coordinate` first since otherwise `AddressInput.parse` will try to see if the
136
        # group name is a file on disk.
137
        if 2 <= artifact_input.count(":"):
×
138
            try:
×
139
                maybe_coord = ArtifactRequirement(Coordinate.from_coord_str(artifact_input))
×
140
                requirements.add(maybe_coord)
×
141
                continue
×
142
            except Exception:
×
143
                pass
×
144

145
        try:
×
146
            address_input = AddressInput.parse(
×
147
                artifact_input, description_of_origin=f"the option `{request.option_name}`"
148
            )
149
            candidate_address_inputs.add(address_input)
×
150
        except Exception:
×
151
            bad_artifact_inputs.append(artifact_input)
×
152

153
    if bad_artifact_inputs:
×
154
        raise ValueError(
×
155
            softwrap(
156
                f"""
157
                The following values could not be parsed as an address nor as a JVM coordinate string.
158
                The problematic inputs supplied to the `{request.option_name}` option were:
159
                {", ".join(bad_artifact_inputs)}.
160
                """
161
            )
162
        )
163

164
    # Gather coordinates from the provided addresses.
165
    addresses = await concurrently(
×
166
        resolve_address(**implicitly({ai: AddressInput})) for ai in candidate_address_inputs
167
    )
168
    all_supplied_targets = await resolve_targets(**implicitly(Addresses(addresses)))
×
169
    other_targets = []
×
170
    for tgt in all_supplied_targets:
×
171
        if JvmArtifactFieldSet.is_applicable(tgt):
×
172
            requirements.add(ArtifactRequirement.from_jvm_artifact_target(tgt))
×
173
        else:
174
            other_targets.append(tgt)
×
175

176
    if other_targets:
×
177
        raise ValueError(
×
178
            softwrap(
179
                f"""
180
                The following addresses reference targets that are not `jvm_artifact` targets.
181
                Please only supply the addresses of `jvm_artifact` for the `{request.option_name}`
182
                option. The problematic addresses are: {", ".join(str(tgt.address) for tgt in other_targets)}.
183
                """
184
            )
185
        )
186

187
    return ArtifactRequirements(requirements)
×
188

189

UNCOV
190
@dataclass(frozen=True)
×
UNCOV
191
class GenerateJvmLockfileFromTool:
×
192
    """Create a `GenerateJvmLockfile` request for a JVM tool.
193

194
    We allow tools to either use coordinates or addresses to `jvm_artifact` targets for the artifact
195
    inputs. This is a convenience to parse those artifact inputs to create a standardized
196
    `GenerateJvmLockfile`.
197
    """
198

UNCOV
199
    artifact_inputs: FrozenOrderedSet[str]
×
UNCOV
200
    options_scope: str
×
UNCOV
201
    resolve_name: str
×
UNCOV
202
    lockfile: str
×
UNCOV
203
    default_lockfile_resource: tuple[str, str]
×
204

UNCOV
205
    @classmethod
×
UNCOV
206
    def create(cls, tool: JvmToolBase) -> GenerateJvmLockfileFromTool:
×
UNCOV
207
        return GenerateJvmLockfileFromTool(
×
208
            FrozenOrderedSet(tool.artifact_inputs),
209
            options_scope=tool.options_scope,
210
            resolve_name=tool.options_scope,
211
            lockfile=tool.lockfile,
212
            default_lockfile_resource=tool.default_lockfile_resource,
213
        )
214

UNCOV
215
    @property
×
UNCOV
216
    def artifact_option_name(self) -> str:
×
217
        return f"[{self.options_scope}].artifacts"
×
218

UNCOV
219
    @property
×
UNCOV
220
    def lockfile_option_name(self):
×
221
        return f"[{self.options_scope}].lockfile"
×
222

223

UNCOV
224
def rules():
×
UNCOV
225
    from pants.jvm.goals import lockfile  # TODO: Shim to avoid import cycle
×
226

UNCOV
227
    return (*collect_rules(), *lockfile.rules())
×
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