• 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/backend/java/compile/javac.py
1
# Copyright 2021 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 itertools
×
UNCOV
7
import logging
×
UNCOV
8
from itertools import chain
×
9

UNCOV
10
from pants.backend.java.dependency_inference.rules import (
×
11
    JavaInferredDependenciesAndExportsRequest,
12
    infer_java_dependencies_and_exports_via_source_analysis,
13
)
UNCOV
14
from pants.backend.java.dependency_inference.rules import rules as java_dep_inference_rules
×
UNCOV
15
from pants.backend.java.subsystems.javac import JavacSubsystem
×
UNCOV
16
from pants.backend.java.target_types import JavaFieldSet, JavaGeneratorFieldSet, JavaSourceField
×
UNCOV
17
from pants.core.util_rules.source_files import SourceFilesRequest, determine_source_files
×
UNCOV
18
from pants.core.util_rules.system_binaries import BashBinary, ZipBinary
×
UNCOV
19
from pants.engine.fs import EMPTY_DIGEST, CreateDigest, Directory, MergeDigests
×
UNCOV
20
from pants.engine.intrinsics import (
×
21
    create_digest,
22
    digest_to_snapshot,
23
    execute_process,
24
    merge_digests,
25
)
UNCOV
26
from pants.engine.process import Process, ProcessCacheScope, execute_process_or_raise
×
UNCOV
27
from pants.engine.rules import collect_rules, concurrently, implicitly, rule
×
UNCOV
28
from pants.engine.target import CoarsenedTarget, SourcesField
×
UNCOV
29
from pants.engine.unions import UnionRule
×
UNCOV
30
from pants.jvm.classpath import Classpath
×
UNCOV
31
from pants.jvm.compile import (
×
32
    ClasspathDependenciesRequest,
33
    ClasspathEntry,
34
    ClasspathEntryRequest,
35
    ClasspathEntryRequests,
36
    CompileResult,
37
    FallibleClasspathEntries,
38
    FallibleClasspathEntry,
39
    compile_classpath_entries,
40
)
UNCOV
41
from pants.jvm.compile import rules as jvm_compile_rules
×
UNCOV
42
from pants.jvm.jdk_rules import JdkRequest, JvmProcess, prepare_jdk_environment
×
UNCOV
43
from pants.jvm.strip_jar.strip_jar import StripJarRequest, strip_jar
×
UNCOV
44
from pants.jvm.subsystems import JvmSubsystem
×
UNCOV
45
from pants.util.logging import LogLevel
×
46

UNCOV
47
logger = logging.getLogger(__name__)
×
48

49

UNCOV
50
class CompileJavaSourceRequest(ClasspathEntryRequest):
×
UNCOV
51
    field_sets = (JavaFieldSet, JavaGeneratorFieldSet)
×
52

53

54
# TODO: This code is duplicated in the javac and BSP rules.
UNCOV
55
def compute_output_jar_filename(ctgt: CoarsenedTarget) -> str:
×
56
    return f"{ctgt.representative.address.path_safe_spec}.javac.jar"
×
57

58

UNCOV
59
@rule(desc="Compile with javac")
×
UNCOV
60
async def compile_java_source(
×
61
    bash: BashBinary,
62
    javac: JavacSubsystem,
63
    zip_binary: ZipBinary,
64
    jvm: JvmSubsystem,
65
    request: CompileJavaSourceRequest,
66
) -> FallibleClasspathEntry:
67
    # Request the component's direct dependency classpath, and additionally any prerequisite.
68
    optional_prereq_request = [*((request.prerequisite,) if request.prerequisite else ())]
×
69
    fallibles = await concurrently(
×
70
        compile_classpath_entries(ClasspathEntryRequests(optional_prereq_request)),
71
        compile_classpath_entries(**implicitly(ClasspathDependenciesRequest(request))),
72
    )
73

74
    direct_dependency_classpath_entries = FallibleClasspathEntries(
×
75
        itertools.chain(*fallibles)
76
    ).if_all_succeeded()
77

78
    if direct_dependency_classpath_entries is None:
×
79
        return FallibleClasspathEntry(
×
80
            description=str(request.component),
81
            result=CompileResult.DEPENDENCY_FAILED,
82
            output=None,
83
            exit_code=1,
84
        )
85

86
    # Capture just the `ClasspathEntry` objects that are listed as `export` types by source analysis
87
    deps_to_classpath_entries = dict(
×
88
        zip(request.component.dependencies, direct_dependency_classpath_entries or ())
89
    )
90
    # Re-request inferred dependencies to get a list of export dependency addresses
91
    inferred_dependencies = await concurrently(
×
92
        infer_java_dependencies_and_exports_via_source_analysis(
93
            JavaInferredDependenciesAndExportsRequest(tgt[JavaSourceField]), **implicitly()
94
        )
95
        for tgt in request.component.members
96
        if JavaFieldSet.is_applicable(tgt)
97
    )
98
    flat_exports = {export for i in inferred_dependencies for export in i.exports}
×
99

100
    export_classpath_entries = [
×
101
        classpath_entry
102
        for coarsened_target, classpath_entry in deps_to_classpath_entries.items()
103
        if any(m.address in flat_exports for m in coarsened_target.members)
104
    ]
105

106
    # Then collect the component's sources.
107
    component_members_with_sources = tuple(
×
108
        t for t in request.component.members if t.has_field(SourcesField)
109
    )
110
    component_members_and_source_files = zip(
×
111
        component_members_with_sources,
112
        await concurrently(
113
            determine_source_files(
114
                SourceFilesRequest(
115
                    (t.get(SourcesField),),
116
                    for_sources_types=(JavaSourceField,),
117
                    enable_codegen=True,
118
                )
119
            )
120
            for t in component_members_with_sources
121
        ),
122
    )
123
    component_members_and_java_source_files = [
×
124
        (target, sources)
125
        for target, sources in component_members_and_source_files
126
        if sources.snapshot.digest != EMPTY_DIGEST
127
    ]
128
    if not component_members_and_java_source_files:
×
129
        # Is a generator, and so exports all of its direct deps.
130
        exported_digest = await merge_digests(
×
131
            MergeDigests(cpe.digest for cpe in direct_dependency_classpath_entries)
132
        )
133
        classpath_entry = ClasspathEntry.merge(exported_digest, direct_dependency_classpath_entries)
×
134
        return FallibleClasspathEntry(
×
135
            description=str(request.component),
136
            result=CompileResult.SUCCEEDED,
137
            output=classpath_entry,
138
            exit_code=0,
139
        )
140

141
    dest_dir = "classfiles"
×
142
    dest_dir_digest, jdk = await concurrently(
×
143
        create_digest(CreateDigest([Directory(dest_dir)])),
144
        prepare_jdk_environment(**implicitly(JdkRequest.from_target(request.component))),
145
    )
146
    merged_digest = await merge_digests(
×
147
        MergeDigests(
148
            (
149
                dest_dir_digest,
150
                *(
151
                    sources.snapshot.digest
152
                    for _, sources in component_members_and_java_source_files
153
                ),
154
            )
155
        )
156
    )
157

158
    usercp = "__cp"
×
159
    user_classpath = Classpath(direct_dependency_classpath_entries, request.resolve)
×
160
    classpath_arg = ":".join(user_classpath.root_immutable_inputs_args(prefix=usercp))
×
161
    immutable_input_digests = dict(user_classpath.root_immutable_inputs(prefix=usercp))
×
162

163
    # Compile.
164
    compile_result = await execute_process(
×
165
        **implicitly(
166
            JvmProcess(
167
                jdk=jdk,
168
                classpath_entries=[f"{jdk.java_home}/lib/tools.jar"],
169
                argv=[
170
                    "com.sun.tools.javac.Main",
171
                    *(("-cp", classpath_arg) if classpath_arg else ()),
172
                    *javac.args,
173
                    "-d",
174
                    dest_dir,
175
                    *sorted(
176
                        chain.from_iterable(
177
                            sources.snapshot.files
178
                            for _, sources in component_members_and_java_source_files
179
                        )
180
                    ),
181
                ],
182
                input_digest=merged_digest,
183
                extra_immutable_input_digests=immutable_input_digests,
184
                output_directories=(dest_dir,),
185
                description=f"Compile {request.component} with javac",
186
                level=LogLevel.DEBUG,
187
            )
188
        )
189
    )
190
    if compile_result.exit_code != 0:
×
191
        return FallibleClasspathEntry.from_fallible_process_result(
×
192
            str(request.component),
193
            compile_result,
194
            None,
195
        )
196

197
    # Jar.
198
    # NB: We jar up the outputs in a separate process because the nailgun runner cannot support
199
    # invoking via a `bash` wrapper (since the trailing portion of the command is executed by
200
    # the nailgun server). We might be able to resolve this in the future via a Javac wrapper shim.
201
    output_snapshot = await digest_to_snapshot(compile_result.output_digest)
×
202
    output_file = compute_output_jar_filename(request.component)
×
203
    output_files: tuple[str, ...] = (output_file,)
×
204
    if output_snapshot.files:
×
205
        jar_result = await execute_process_or_raise(
×
206
            **implicitly(
207
                Process(
208
                    argv=[
209
                        bash.path,
210
                        "-c",
211
                        " ".join(
212
                            ["cd", dest_dir, ";", zip_binary.path, "-r", f"../{output_file}", "."]
213
                        ),
214
                    ],
215
                    input_digest=compile_result.output_digest,
216
                    output_files=output_files,
217
                    description=f"Capture outputs of {request.component} for javac",
218
                    level=LogLevel.TRACE,
219
                    cache_scope=ProcessCacheScope.LOCAL_SUCCESSFUL,
220
                )
221
            )
222
        )
223
        jar_output_digest = jar_result.output_digest
×
224
    else:
225
        # If there was no output, then do not create a jar file. This may occur, for example, when compiling
226
        # a `package-info.java` in a single partition.
227
        output_files = ()
×
228
        jar_output_digest = EMPTY_DIGEST
×
229

230
    if jvm.reproducible_jars:
×
231
        jar_output_digest = await strip_jar(
×
232
            **implicitly(StripJarRequest(digest=jar_output_digest, filenames=output_files))
233
        )
234
    output_classpath = ClasspathEntry(
×
235
        jar_output_digest, output_files, direct_dependency_classpath_entries
236
    )
237

238
    if export_classpath_entries:
×
239
        merged_export_digest = await merge_digests(
×
240
            MergeDigests((output_classpath.digest, *(i.digest for i in export_classpath_entries)))
241
        )
242
        merged_classpath = ClasspathEntry.merge(
×
243
            merged_export_digest, (output_classpath, *export_classpath_entries)
244
        )
245
        output_classpath = merged_classpath
×
246

247
    return FallibleClasspathEntry.from_fallible_process_result(
×
248
        str(request.component),
249
        compile_result,
250
        output_classpath,
251
    )
252

253

UNCOV
254
def rules():
×
UNCOV
255
    return [
×
256
        *collect_rules(),
257
        *java_dep_inference_rules(),
258
        *jvm_compile_rules(),
259
        UnionRule(ClasspathEntryRequest, CompileJavaSourceRequest),
260
    ]
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