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

pantsbuild / pants / 20836862798

09 Jan 2026 12:31AM UTC coverage: 43.231% (-37.0%) from 80.274%
20836862798

Pull #22992

github

web-flow
Merge 0983a6a7c into 0d471f924
Pull Request #22992: feat(engine): log elapsed time for completed workunits

26137 of 60459 relevant lines covered (43.23%)

0.86 hits per line

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

0.0
/src/python/pants/jvm/package/deploy_jar.py
1
# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
import logging
×
5
from dataclasses import dataclass
×
6
from pathlib import PurePath
×
7

8
from pants.core.goals.package import (
×
9
    BuiltPackage,
10
    BuiltPackageArtifact,
11
    OutputPathField,
12
    PackageFieldSet,
13
)
14
from pants.core.goals.run import RunFieldSet, RunInSandboxBehavior
×
15
from pants.engine.addresses import Addresses
×
16
from pants.engine.fs import EMPTY_DIGEST, AddPrefix, MergeDigests
×
17
from pants.engine.intrinsics import add_prefix, merge_digests
×
18
from pants.engine.rules import collect_rules, implicitly, rule
×
19
from pants.engine.target import Dependencies
×
20
from pants.engine.unions import UnionRule
×
21
from pants.jvm import classpath
×
22
from pants.jvm.classpath import classpath as classpath_get
×
23
from pants.jvm.compile import (
×
24
    ClasspathDependenciesRequest,
25
    ClasspathEntry,
26
    ClasspathEntryRequest,
27
    CompileResult,
28
    FallibleClasspathEntry,
29
    compile_classpath_entries,
30
)
31
from pants.jvm.jar_tool.jar_tool import JarToolRequest, run_jar_tool
×
32
from pants.jvm.jar_tool.jar_tool import rules as jar_tool_rules
×
33
from pants.jvm.shading.rules import ShadeJarRequest, shade_jar
×
34
from pants.jvm.shading.rules import rules as shaded_jar_rules
×
35
from pants.jvm.strip_jar.strip_jar import StripJarRequest, strip_jar
×
36
from pants.jvm.subsystems import JvmSubsystem
×
37
from pants.jvm.target_types import (
×
38
    DeployJarDuplicatePolicyField,
39
    DeployJarExcludeFilesField,
40
    DeployJarShadingRulesField,
41
    JvmDependenciesField,
42
    JvmJdkField,
43
    JvmMainClassNameField,
44
)
45

46
logger = logging.getLogger(__name__)
×
47

48

49
@dataclass(frozen=True)
×
50
class DeployJarFieldSet(PackageFieldSet, RunFieldSet):
×
51
    required_fields = (
×
52
        JvmMainClassNameField,
53
        JvmJdkField,
54
        Dependencies,
55
        OutputPathField,
56
    )
57
    run_in_sandbox_behavior = RunInSandboxBehavior.RUN_REQUEST_HERMETIC
×
58

59
    main_class: JvmMainClassNameField
×
60
    output_path: OutputPathField
×
61
    dependencies: JvmDependenciesField
×
62
    jdk_version: JvmJdkField
×
63
    duplicate_policy: DeployJarDuplicatePolicyField
×
64
    shading_rules: DeployJarShadingRulesField
×
65
    exclude_files: DeployJarExcludeFilesField
×
66

67

68
class DeployJarClasspathEntryRequest(ClasspathEntryRequest):
×
69
    field_sets = (DeployJarFieldSet,)
×
70
    # A `deploy_jar` can have a Classpath requested for it, but should not be used as a dependency.
71
    root_only = True
×
72

73

74
@rule
×
75
async def deploy_jar_classpath(
×
76
    request: DeployJarClasspathEntryRequest,
77
) -> FallibleClasspathEntry:
78
    if len(request.component.members) > 1:
×
79
        # If multiple DeployJar targets were coarsened into a single instance, it's because they
80
        # formed a cycle among themselves... but at a high level, they shouldn't have dependencies
81
        # on one another anyway.
82
        raise Exception(
×
83
            "`deploy_jar` targets should not depend on one another:\n"
84
            f"{request.component.bullet_list()}"
85
        )
86
    fallible_entries = await compile_classpath_entries(
×
87
        **implicitly(ClasspathDependenciesRequest(request))
88
    )
89
    classpath_entries = fallible_entries.if_all_succeeded()
×
90
    if classpath_entries is None:
×
91
        return FallibleClasspathEntry(
×
92
            description=str(request.component),
93
            result=CompileResult.DEPENDENCY_FAILED,
94
            output=None,
95
            exit_code=1,
96
        )
97
    return FallibleClasspathEntry(
×
98
        description=str(request.component),
99
        result=CompileResult.SUCCEEDED,
100
        output=ClasspathEntry(EMPTY_DIGEST, dependencies=classpath_entries),
101
        exit_code=0,
102
    )
103

104

105
@rule
×
106
async def package_deploy_jar(
×
107
    jvm: JvmSubsystem,
108
    field_set: DeployJarFieldSet,
109
) -> BuiltPackage:
110
    """
111
    Constructs a deploy ("fat") JAR file by
112
    1. Resolving/compiling a Classpath for the `root_address` target,
113
    2. Creating a deploy jar with a valid ZIP index and deduplicated entries
114
    3. (optionally) Stripping the jar of all metadata that may cause it to be non-reproducible (https://reproducible-builds.org)
115
    4. (optionally) Apply shading rules to the bytecode inside the jar file
116
    """
117

118
    if field_set.main_class.value is None:
×
119
        raise Exception("Needs a `main` argument")
×
120

121
    #
122
    # 1. Produce thin JARs containing the transitive classpath
123
    #
124

125
    classpath = await classpath_get(**implicitly(Addresses([field_set.address])))
×
126
    classpath_digest = await merge_digests(MergeDigests(classpath.digests()))
×
127

128
    #
129
    # 2. Use Pants' JAR tool to build a runnable fat JAR
130
    #
131

132
    output_filename = PurePath(field_set.output_path.value_or_default(file_ending="jar"))
×
133
    jar_digest = await run_jar_tool(
×
134
        JarToolRequest(
135
            jar_name=output_filename.name,
136
            digest=classpath_digest,
137
            main_class=field_set.main_class.value,
138
            jars=classpath.args(),
139
            policies=[
140
                (rule.pattern, rule.action)
141
                for rule in field_set.duplicate_policy.value_or_default()
142
            ],
143
            skip=[*(jvm.deploy_jar_exclude_files or []), *(field_set.exclude_files.value or [])],
144
            compress=True,
145
        ),
146
        **implicitly(),
147
    )
148

149
    #
150
    # 3. Strip the JAR from  all non-reproducible metadata if requested so
151
    #
152
    if jvm.reproducible_jars:
×
153
        jar_digest = await strip_jar(
×
154
            **implicitly(
155
                StripJarRequest(
156
                    digest=jar_digest,
157
                    filenames=(output_filename.name,),
158
                )
159
            )
160
        )
161

162
    jar_digest = await add_prefix(AddPrefix(jar_digest, str(output_filename.parent)))
×
163

164
    #
165
    # 4. Apply shading rules
166
    #
167
    if field_set.shading_rules.value:
×
168
        shaded_jar = await shade_jar(
×
169
            ShadeJarRequest(
170
                path=output_filename,
171
                digest=jar_digest,
172
                rules=field_set.shading_rules.value,
173
                skip_manifest=False,
174
            ),
175
            **implicitly(),
176
        )
177
        jar_digest = shaded_jar.digest
×
178

179
    artifact = BuiltPackageArtifact(relpath=str(output_filename))
×
180
    return BuiltPackage(digest=jar_digest, artifacts=(artifact,))
×
181

182

183
def rules():
×
184
    return [
×
185
        *collect_rules(),
186
        *classpath.rules(),
187
        *jar_tool_rules(),
188
        *shaded_jar_rules(),
189
        UnionRule(PackageFieldSet, DeployJarFieldSet),
190
        UnionRule(ClasspathEntryRequest, DeployJarClasspathEntryRequest),
191
    ]
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