• 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/jar_tool/jar_tool.py
1
# Copyright 2022 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

UNCOV
4
from __future__ import annotations
×
5

UNCOV
6
import importlib.resources
×
UNCOV
7
import os
×
UNCOV
8
from collections.abc import Iterable, Mapping
×
UNCOV
9
from dataclasses import dataclass
×
UNCOV
10
from enum import Enum, unique
×
11

UNCOV
12
from pants.base.glob_match_error_behavior import GlobMatchErrorBehavior
×
UNCOV
13
from pants.core.goals.resolves import ExportableTool
×
UNCOV
14
from pants.engine.fs import (
×
15
    CreateDigest,
16
    Digest,
17
    DigestSubset,
18
    Directory,
19
    FileContent,
20
    FileEntry,
21
    MergeDigests,
22
    PathGlobs,
23
    RemovePrefix,
24
)
UNCOV
25
from pants.engine.intrinsics import (
×
26
    create_digest,
27
    digest_subset_to_digest,
28
    get_digest_entries,
29
    merge_digests,
30
    remove_prefix,
31
)
UNCOV
32
from pants.engine.process import execute_process_or_raise
×
UNCOV
33
from pants.engine.rules import collect_rules, concurrently, implicitly, rule
×
UNCOV
34
from pants.engine.unions import UnionRule
×
UNCOV
35
from pants.jvm.jdk_rules import InternalJdk, JvmProcess
×
UNCOV
36
from pants.jvm.jdk_rules import rules as jdk_rules
×
UNCOV
37
from pants.jvm.resolve.coursier_fetch import ToolClasspathRequest, materialize_classpath_for_tool
×
UNCOV
38
from pants.jvm.resolve.coursier_fetch import rules as coursier_fetch_rules
×
UNCOV
39
from pants.jvm.resolve.jvm_tool import GenerateJvmLockfileFromTool, JvmToolBase
×
UNCOV
40
from pants.jvm.resolve.jvm_tool import rules as jvm_tool_rules
×
UNCOV
41
from pants.util.frozendict import FrozenDict
×
UNCOV
42
from pants.util.logging import LogLevel
×
43

44

UNCOV
45
class JarTool(JvmToolBase):
×
UNCOV
46
    options_scope = "jar_tool"
×
UNCOV
47
    help = "The Java Archive Tool"
×
48

UNCOV
49
    default_artifacts = (
×
50
        "args4j:args4j:2.33",
51
        "com.google.code.findbugs:jsr305:3.0.2",
52
        "com.google.guava:guava:18.0",
53
    )
UNCOV
54
    default_lockfile_resource = (
×
55
        "pants.jvm.jar_tool",
56
        "jar_tool.lock",
57
    )
58

59

UNCOV
60
@unique
×
UNCOV
61
class JarDuplicateAction(Enum):
×
UNCOV
62
    SKIP = "skip"
×
UNCOV
63
    REPLACE = "replace"
×
UNCOV
64
    CONCAT = "concat"
×
UNCOV
65
    CONCAT_TEXT = "concat_text"
×
UNCOV
66
    THROW = "throw"
×
67

68

UNCOV
69
@dataclass(frozen=True)
×
UNCOV
70
class JarToolRequest:
×
UNCOV
71
    jar_name: str
×
UNCOV
72
    digest: Digest
×
UNCOV
73
    main_class: str | None
×
UNCOV
74
    classpath_entries: tuple[str, ...]
×
UNCOV
75
    manifest: str | None
×
UNCOV
76
    jars: tuple[str, ...]
×
UNCOV
77
    file_mappings: FrozenDict[str, str]
×
UNCOV
78
    default_action: JarDuplicateAction | None
×
UNCOV
79
    policies: tuple[tuple[str, JarDuplicateAction], ...]
×
UNCOV
80
    skip: tuple[str, ...]
×
UNCOV
81
    compress: bool
×
UNCOV
82
    update: bool
×
83

UNCOV
84
    def __init__(
×
85
        self,
86
        *,
87
        jar_name: str,
88
        digest: Digest,
89
        main_class: str | None = None,
90
        classpath_entries: Iterable[str] | None = None,
91
        manifest: str | None = None,
92
        jars: Iterable[str] | None = None,
93
        file_mappings: Mapping[str, str] | None = None,
94
        files: Iterable[str] | None = None,
95
        default_action: JarDuplicateAction | None = None,
96
        policies: Iterable[tuple[str, str | JarDuplicateAction]] | None = None,
97
        skip: Iterable[str] | None = None,
98
        compress: bool = False,
99
        update: bool = False,
100
    ) -> None:
UNCOV
101
        _file_mappings = {**(file_mappings or {}), **({f: f for f in (files or [])})}
×
102

UNCOV
103
        object.__setattr__(self, "jar_name", jar_name)
×
UNCOV
104
        object.__setattr__(self, "digest", digest)
×
UNCOV
105
        object.__setattr__(self, "main_class", main_class)
×
UNCOV
106
        object.__setattr__(self, "manifest", manifest)
×
UNCOV
107
        object.__setattr__(self, "classpath_entries", tuple(classpath_entries or ()))
×
UNCOV
108
        object.__setattr__(self, "jars", tuple(jars or ()))
×
UNCOV
109
        object.__setattr__(self, "file_mappings", FrozenDict(_file_mappings))
×
UNCOV
110
        object.__setattr__(self, "default_action", default_action)
×
UNCOV
111
        object.__setattr__(self, "policies", tuple(JarToolRequest.__parse_policies(policies or ())))
×
UNCOV
112
        object.__setattr__(self, "skip", tuple(skip or ()))
×
UNCOV
113
        object.__setattr__(self, "compress", compress)
×
UNCOV
114
        object.__setattr__(self, "update", update)
×
115

UNCOV
116
    @staticmethod
×
UNCOV
117
    def __parse_policies(
×
118
        policies: Iterable[tuple[str, str | JarDuplicateAction]],
119
    ) -> Iterable[tuple[str, JarDuplicateAction]]:
UNCOV
120
        return [
×
121
            (
122
                pattern,
123
                (
124
                    action
125
                    if isinstance(action, JarDuplicateAction)
126
                    else JarDuplicateAction(action.lower())
127
                ),
128
            )
129
            for (pattern, action) in policies
130
        ]
131

132

UNCOV
133
_JAR_TOOL_MAIN_CLASS = "org.pantsbuild.tools.jar.Main"
×
134

135

UNCOV
136
@dataclass(frozen=True)
×
UNCOV
137
class JarToolCompiledClassfiles:
×
UNCOV
138
    digest: Digest
×
139

140

UNCOV
141
@rule
×
UNCOV
142
async def run_jar_tool(
×
143
    request: JarToolRequest, jdk: InternalJdk, tool: JarTool, jar_tool: JarToolCompiledClassfiles
144
) -> Digest:
145
    output_prefix = "__out"
×
146
    output_jarname = os.path.join(output_prefix, request.jar_name)
×
147

148
    tool_classpath, empty_output_digest = await concurrently(
×
149
        materialize_classpath_for_tool(
150
            ToolClasspathRequest(lockfile=GenerateJvmLockfileFromTool.create(tool))
151
        ),
152
        create_digest(CreateDigest([Directory(output_prefix)])),
153
    )
154

155
    toolcp_prefix = "__toolcp"
×
156
    jartoolcp_prefix = "__jartoolcp"
×
157
    input_prefix = "__in"
×
158
    immutable_input_digests = {
×
159
        toolcp_prefix: tool_classpath.digest,
160
        jartoolcp_prefix: jar_tool.digest,
161
        input_prefix: request.digest,
162
    }
163

164
    policies = ",".join(
×
165
        f"{pattern}={action.value.upper()}" for (pattern, action) in request.policies
166
    )
167
    file_mappings = ",".join(
×
168
        f"{os.path.join(input_prefix, fs_path)}={jar_path}"
169
        for fs_path, jar_path in request.file_mappings.items()
170
    )
171

172
    tool_process = JvmProcess(
×
173
        jdk=jdk,
174
        argv=[
175
            _JAR_TOOL_MAIN_CLASS,
176
            output_jarname,
177
            *((f"-main={request.main_class}",) if request.main_class else ()),
178
            *(
179
                (f"-classpath={','.join(request.classpath_entries)}",)
180
                if request.classpath_entries
181
                else ()
182
            ),
183
            *(
184
                (f"-manifest={os.path.join(input_prefix, request.manifest)}",)
185
                if request.manifest
186
                else ()
187
            ),
188
            *(
189
                (f"-jars={','.join([os.path.join(input_prefix, jar) for jar in request.jars])}",)
190
                if request.jars
191
                else ()
192
            ),
193
            *((f"-files={file_mappings}",) if file_mappings else ()),
194
            *(
195
                (f"-default_action={request.default_action.value.upper()}",)
196
                if request.default_action
197
                else ()
198
            ),
199
            *((f"-policies={policies}",) if policies else ()),
200
            *((f"-skip={','.join(request.skip)}",) if request.skip else ()),
201
            *(("-compress",) if request.compress else ()),
202
            *(("-update",) if request.update else ()),
203
        ],
204
        classpath_entries=[*tool_classpath.classpath_entries(toolcp_prefix), jartoolcp_prefix],
205
        input_digest=empty_output_digest,
206
        extra_immutable_input_digests=immutable_input_digests,
207
        extra_nailgun_keys=immutable_input_digests.keys(),
208
        description=f"Building jar {request.jar_name}",
209
        output_directories=(output_prefix,),
210
        level=LogLevel.DEBUG,
211
    )
212

213
    result = await execute_process_or_raise(**implicitly({tool_process: JvmProcess}))
×
214
    return await remove_prefix(RemovePrefix(result.output_digest, output_prefix))
×
215

216

UNCOV
217
_JAR_TOOL_SRC_PACKAGES = ["args4j", "jar_tool_source"]
×
218

219

UNCOV
220
def _load_jar_tool_sources() -> list[FileContent]:
×
221
    parent_module = ".".join(__name__.split(".")[:-1])
×
222
    result = []
×
223
    for package in _JAR_TOOL_SRC_PACKAGES:
×
224
        # pkg_path = package.replace(".", os.path.sep)
225
        # relative_folder = os.path.join("src", pkg_path)
226
        for resource in importlib.resources.files(parent_module).joinpath(package).iterdir():
×
227
            if not resource.is_file():
×
228
                continue
×
229
            result.append(
×
230
                FileContent(
231
                    path=os.path.join(package, resource.name),
232
                    content=resource.read_bytes(),
233
                )
234
            )
235
    return result
×
236

237

238
# TODO(13879): Consolidate compilation of wrapper binaries to common rules.
UNCOV
239
@rule
×
UNCOV
240
async def build_jar_tool(jdk: InternalJdk, tool: JarTool) -> JarToolCompiledClassfiles:
×
241
    source_digest = await create_digest(CreateDigest(_load_jar_tool_sources()))
×
242

243
    dest_dir = "classfiles"
×
244
    materialized_classpath, java_subset_digest, empty_dest_dir = await concurrently(
×
245
        materialize_classpath_for_tool(
246
            ToolClasspathRequest(
247
                prefix="__toolcp", lockfile=GenerateJvmLockfileFromTool.create(tool)
248
            )
249
        ),
250
        digest_subset_to_digest(
251
            DigestSubset(
252
                source_digest,
253
                PathGlobs(
254
                    ["**/*.java"],
255
                    glob_match_error_behavior=GlobMatchErrorBehavior.error,
256
                    description_of_origin="jar tool sources",
257
                ),
258
            )
259
        ),
260
        create_digest(CreateDigest([Directory(path=dest_dir)])),
261
    )
262

263
    merged_digest, src_entries = await concurrently(
×
264
        merge_digests(MergeDigests([materialized_classpath.digest, source_digest, empty_dest_dir])),
265
        get_digest_entries(java_subset_digest),
266
    )
267

268
    compile_result = await execute_process_or_raise(
×
269
        **implicitly(
270
            JvmProcess(
271
                jdk=jdk,
272
                classpath_entries=[f"{jdk.java_home}/lib/tools.jar"],
273
                argv=[
274
                    "com.sun.tools.javac.Main",
275
                    "-cp",
276
                    ":".join(materialized_classpath.classpath_entries()),
277
                    "-d",
278
                    dest_dir,
279
                    *[entry.path for entry in src_entries if isinstance(entry, FileEntry)],
280
                ],
281
                input_digest=merged_digest,
282
                output_directories=(dest_dir,),
283
                description="Compile jar-tool sources using javac.",
284
                level=LogLevel.DEBUG,
285
                use_nailgun=False,
286
            )
287
        )
288
    )
289

290
    stripped_classfiles_digest = await remove_prefix(
×
291
        RemovePrefix(compile_result.output_digest, dest_dir)
292
    )
293
    return JarToolCompiledClassfiles(digest=stripped_classfiles_digest)
×
294

295

UNCOV
296
def rules():
×
UNCOV
297
    return [
×
298
        *collect_rules(),
299
        *coursier_fetch_rules(),
300
        *jdk_rules(),
301
        *jvm_tool_rules(),
302
        UnionRule(ExportableTool, JarTool),
303
    ]
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

© 2025 Coveralls, Inc