• 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/package/war.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 logging
×
UNCOV
7
import textwrap
×
UNCOV
8
from collections.abc import Iterable
×
UNCOV
9
from dataclasses import dataclass
×
UNCOV
10
from pathlib import PurePath
×
11

UNCOV
12
from pants.build_graph.address import Address
×
UNCOV
13
from pants.core.goals.package import (
×
14
    BuiltPackage,
15
    BuiltPackageArtifact,
16
    OutputPathField,
17
    PackageFieldSet,
18
)
UNCOV
19
from pants.core.target_types import FileSourceField, ResourceSourceField
×
UNCOV
20
from pants.core.util_rules.source_files import SourceFilesRequest, determine_source_files
×
UNCOV
21
from pants.core.util_rules.system_binaries import BashBinary, ZipBinary
×
UNCOV
22
from pants.engine.fs import (
×
23
    EMPTY_DIGEST,
24
    AddPrefix,
25
    CreateDigest,
26
    Digest,
27
    DigestSubset,
28
    Directory,
29
    FileContent,
30
    FileEntry,
31
    MergeDigests,
32
    PathGlobs,
33
)
UNCOV
34
from pants.engine.internals.graph import (
×
35
    hydrate_sources,
36
    resolve_targets,
37
    resolve_unparsed_address_inputs,
38
)
UNCOV
39
from pants.engine.internals.selectors import concurrently
×
UNCOV
40
from pants.engine.intrinsics import (
×
41
    add_prefix,
42
    create_digest,
43
    digest_subset_to_digest,
44
    get_digest_entries,
45
    merge_digests,
46
)
UNCOV
47
from pants.engine.process import Process, execute_process_or_raise
×
UNCOV
48
from pants.engine.rules import collect_rules, implicitly, rule
×
UNCOV
49
from pants.engine.target import DependenciesRequest, HydrateSourcesRequest, SourcesField
×
UNCOV
50
from pants.engine.unions import UnionRule
×
UNCOV
51
from pants.jvm.classpath import Classpath
×
UNCOV
52
from pants.jvm.classpath import classpath as classpath_get
×
UNCOV
53
from pants.jvm.shading.rules import ShadeJarRequest, shade_jar
×
UNCOV
54
from pants.jvm.target_types import (
×
55
    JvmShadingRule,
56
    JvmWarContentField,
57
    JvmWarDependenciesField,
58
    JvmWarDescriptorAddressField,
59
    JvmWarShadingRulesField,
60
)
61

UNCOV
62
logger = logging.getLogger(__name__)
×
63

64

UNCOV
65
@dataclass(frozen=True)
×
UNCOV
66
class PackageWarFileFieldSet(PackageFieldSet):
×
UNCOV
67
    required_fields = (
×
68
        JvmWarDependenciesField,
69
        JvmWarDescriptorAddressField,
70
    )
71

UNCOV
72
    output_path: OutputPathField
×
UNCOV
73
    dependencies: JvmWarDependenciesField
×
UNCOV
74
    descriptor: JvmWarDescriptorAddressField
×
UNCOV
75
    content: JvmWarContentField
×
UNCOV
76
    shading_rules: JvmWarShadingRulesField
×
77

78

UNCOV
79
@dataclass(frozen=True)
×
UNCOV
80
class RenderWarDeploymentDescriptorRequest:
×
UNCOV
81
    descriptor: JvmWarDescriptorAddressField
×
UNCOV
82
    owning_address: Address
×
83

84

UNCOV
85
@dataclass(frozen=True)
×
UNCOV
86
class RenderedWarDeploymentDescriptor:
×
UNCOV
87
    digest: Digest
×
88

89

UNCOV
90
@dataclass(frozen=True)
×
UNCOV
91
class RenderWarContentRequest:
×
UNCOV
92
    content: JvmWarContentField
×
93

94

UNCOV
95
@dataclass(frozen=True)
×
UNCOV
96
class RenderedWarContent:
×
UNCOV
97
    digest: Digest
×
98

99

UNCOV
100
async def _apply_shading_rules_to_classpath(
×
101
    classpath: Classpath, shading_rules: Iterable[JvmShadingRule] | None
102
) -> Digest:
103
    input_digest = await merge_digests(MergeDigests(classpath.digests()))
×
104
    if not shading_rules:
×
105
        return input_digest
×
106

107
    jars_digest = await digest_subset_to_digest(DigestSubset(input_digest, PathGlobs(["**/*.jar"])))
×
108
    digest_entries = await get_digest_entries(jars_digest)
×
109
    jar_entries = [entry for entry in digest_entries if isinstance(entry, FileEntry)]
×
110
    if len(jar_entries) == 0:
×
111
        return EMPTY_DIGEST
×
112

113
    jar_digests = await concurrently(create_digest(CreateDigest([entry])) for entry in jar_entries)
×
114
    shaded_jars = await concurrently(
×
115
        shade_jar(
116
            ShadeJarRequest(path=entry.path, digest=digest, rules=shading_rules), **implicitly()
117
        )
118
        for entry, digest in zip(jar_entries, jar_digests)
119
    )
120
    return await merge_digests(MergeDigests([shaded.digest for shaded in shaded_jars]))
×
121

122

UNCOV
123
@rule
×
UNCOV
124
async def render_war_deployment_descriptor(
×
125
    request: RenderWarDeploymentDescriptorRequest,
126
) -> RenderedWarDeploymentDescriptor:
127
    descriptor_sources = await hydrate_sources(
×
128
        HydrateSourcesRequest(request.descriptor), **implicitly()
129
    )
130

131
    descriptor_sources_entries = await get_digest_entries(descriptor_sources.snapshot.digest)
×
132
    if len(descriptor_sources_entries) != 1:
×
133
        raise AssertionError(
×
134
            f"Expected `descriptor` field for {request.descriptor.address} to only refer to one file."
135
        )
136
    descriptor_entry = descriptor_sources_entries[0]
×
137
    if not isinstance(descriptor_entry, FileEntry):
×
138
        raise AssertionError(
×
139
            f"Expected `descriptor` field for {request.descriptor.address} to produce a file."
140
        )
141

142
    descriptor_digest = await create_digest(
×
143
        CreateDigest([FileEntry("__war__/WEB-INF/web.xml", descriptor_entry.file_digest)])
144
    )
145

146
    return RenderedWarDeploymentDescriptor(descriptor_digest)
×
147

148

UNCOV
149
@rule
×
UNCOV
150
async def render_war_content(request: RenderWarContentRequest) -> RenderedWarContent:
×
151
    addresses = await resolve_unparsed_address_inputs(
×
152
        request.content.to_unparsed_address_inputs(), **implicitly()
153
    )
154
    targets = await resolve_targets(**implicitly(addresses))
×
155
    sources = await determine_source_files(
×
156
        SourceFilesRequest(
157
            [tgt[SourcesField] for tgt in targets if tgt.has_field(SourcesField)],
158
            for_sources_types=(ResourceSourceField, FileSourceField),
159
            enable_codegen=True,
160
        )
161
    )
162
    digest = await add_prefix(AddPrefix(sources.snapshot.digest, "__war__"))
×
163
    return RenderedWarContent(digest)
×
164

165

UNCOV
166
@rule
×
UNCOV
167
async def package_war(
×
168
    field_set: PackageWarFileFieldSet,
169
    bash: BashBinary,
170
    zip: ZipBinary,
171
) -> BuiltPackage:
172
    classpath = await classpath_get(**implicitly(DependenciesRequest(field_set.dependencies)))
×
173
    all_jar_files_digest = await _apply_shading_rules_to_classpath(
×
174
        classpath, field_set.shading_rules.value
175
    )
176

177
    prefixed_jars_digest, content, descriptor, input_setup_digest = await concurrently(
×
178
        add_prefix(AddPrefix(all_jar_files_digest, "__war__/WEB-INF/lib")),
179
        render_war_content(RenderWarContentRequest(field_set.content)),
180
        render_war_deployment_descriptor(
181
            RenderWarDeploymentDescriptorRequest(field_set.descriptor, field_set.address)
182
        ),
183
        create_digest(
184
            CreateDigest(
185
                [
186
                    FileContent(
187
                        "make_war.sh",
188
                        textwrap.dedent(
189
                            f"""\
190
                            cd __war__
191
                            {zip.path} ../output.war -r .
192
                            """
193
                        ).encode(),
194
                        is_executable=True,
195
                    ),
196
                    Directory("__war__/WEB-INF/classes"),
197
                    Directory("__war__/WEB-INF/lib"),
198
                ]
199
            )
200
        ),
201
    )
202

203
    input_digest = await merge_digests(
×
204
        MergeDigests(
205
            [
206
                prefixed_jars_digest,
207
                descriptor.digest,
208
                content.digest,
209
                input_setup_digest,
210
            ]
211
        )
212
    )
213

214
    result = await execute_process_or_raise(
×
215
        **implicitly(
216
            Process(
217
                [bash.path, "make_war.sh"],
218
                input_digest=input_digest,
219
                output_files=("output.war",),
220
                description=f"Assemble WAR file for {field_set.address}",
221
            )
222
        )
223
    )
224

225
    output_entries = await get_digest_entries(result.output_digest)
×
226
    if len(output_entries) != 1:
×
227
        raise AssertionError("No output from war assembly step.")
×
228
    output_entry = output_entries[0]
×
229
    if not isinstance(output_entry, FileEntry):
×
230
        raise AssertionError("Unexpected digest entry")
×
231
    output_filename = PurePath(field_set.output_path.value_or_default(file_ending="war"))
×
232
    package_digest = await create_digest(
×
233
        CreateDigest([FileEntry(str(output_filename), output_entry.file_digest)])
234
    )
235
    artifact = BuiltPackageArtifact(relpath=str(output_filename))
×
236
    return BuiltPackage(digest=package_digest, artifacts=(artifact,))
×
237

238

UNCOV
239
def rules():
×
UNCOV
240
    return (
×
241
        *collect_rules(),
242
        UnionRule(PackageFieldSet, PackageWarFileFieldSet),
243
    )
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