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

pantsbuild / pants / 18252174847

05 Oct 2025 01:36AM UTC coverage: 43.382% (-36.9%) from 80.261%
18252174847

push

github

web-flow
run tests on mac arm (#22717)

Just doing the minimal to pull forward the x86_64 pattern.

ref #20993

25776 of 59416 relevant lines covered (43.38%)

1.3 hits per line

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

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

4
from __future__ import annotations
3✔
5

6
import logging
3✔
7
import re
3✔
8
from collections.abc import Iterable
3✔
9

10
from pants.core.goals.run import RunRequest
3✔
11
from pants.core.util_rules.system_binaries import UnzipBinary
3✔
12
from pants.core.util_rules.system_binaries import rules as system_binaries_rules
3✔
13
from pants.engine.addresses import Addresses
3✔
14
from pants.engine.internals.graph import resolve_coarsened_targets
3✔
15
from pants.engine.internals.native_engine import Digest, MergeDigests, UnionRule
3✔
16
from pants.engine.intrinsics import digest_to_snapshot, execute_process, merge_digests
3✔
17
from pants.engine.process import Process, execute_process_or_raise
3✔
18
from pants.engine.rules import Rule, collect_rules, implicitly, rule
3✔
19
from pants.jvm.classpath import classpath as classpath_get
3✔
20
from pants.jvm.classpath import rules as classpath_rules
3✔
21
from pants.jvm.jdk_rules import (
3✔
22
    JdkEnvironment,
23
    JdkRequest,
24
    JvmProcess,
25
    jvm_process,
26
    prepare_jdk_environment,
27
)
28
from pants.jvm.jdk_rules import rules as jdk_rules
3✔
29
from pants.jvm.target_types import (
3✔
30
    NO_MAIN_CLASS,
31
    GenericJvmRunRequest,
32
    JvmArtifactFieldSet,
33
    JvmRunnableSourceFieldSet,
34
)
35
from pants.util.logging import LogLevel
3✔
36
from pants.util.memo import memoized
3✔
37

38
logger = logging.getLogger(__name__)
3✔
39

40

41
async def _find_main(
3✔
42
    unzip: UnzipBinary, jdk: JdkEnvironment, input_digest: Digest, jarfile: str
43
) -> str:
44
    # Find the `main` method, first by inspecting the manifest, and then by inspecting the index
45
    # of the included classes.
46

47
    main_from_manifest = await _find_main_by_manifest(unzip, input_digest, jarfile)
×
48

49
    if main_from_manifest:
×
50
        return main_from_manifest
×
51

52
    mains_from_javap = await _find_main_by_javap(unzip, jdk, input_digest, jarfile)
×
53

54
    if len(mains_from_javap) == 0:
×
55
        raise Exception(
×
56
            f"Could not find a `public static void main(String[])` method in `{jarfile}`"
57
        )
58
    if len(mains_from_javap) > 1:
×
59
        raise Exception(f"Found multiple classes that provide `main` methods in `{jarfile}`.")
×
60

61
    return mains_from_javap[0]
×
62

63

64
async def _find_main_by_manifest(
3✔
65
    unzip: UnzipBinary, input_digest: Digest, jarfile: str
66
) -> str | None:
67
    # jvm only allows `-cp` or `-jar` to be specified, and `-jar` takes precedence. So, even for a
68
    # JAR with a valid `Main-Class` in the manifest, we need to peek inside the manifest and
69
    # extract `main` ourself.
70
    manifest = await execute_process(
×
71
        Process(
72
            description="Get manifest destails from classpath",
73
            argv=(unzip.path, "-p", jarfile, "META-INF/MANIFEST.MF"),
74
            input_digest=input_digest,
75
        ),
76
        **implicitly(),
77
    )
78

79
    if manifest.exit_code == 11:
×
80
        # No manifest file present (occurs with e.g. first-party Java sources)
81
        return None
×
82

83
    main_class_lines = [
×
84
        r.strip()
85
        for _, is_main, r in (
86
            i.partition("Main-Class:") for i in manifest.stdout.decode().splitlines()
87
        )
88
        if is_main
89
    ]
90

91
    if not main_class_lines:
×
92
        return None
×
93
    if main_class_lines[0] == NO_MAIN_CLASS:
×
94
        return None
×
95
    return main_class_lines[0]
×
96

97

98
async def _find_main_by_javap(
3✔
99
    unzip: UnzipBinary, jdk: JdkEnvironment, input_digest: Digest, jarfile: str
100
) -> tuple[str, ...]:
101
    # Finds the `main` class by inspecting all of the classes inside the specified JAR
102
    # to find one with a JVM main method.
103

104
    first_jar_contents = await execute_process_or_raise(
×
105
        **implicitly(
106
            Process(
107
                description=f"Get class files from `{jarfile}` to find `main`",
108
                argv=(unzip.path, jarfile, "*.class", "-d", "zip_output"),
109
                input_digest=input_digest,
110
                output_directories=("zip_output",),
111
            )
112
        )
113
    )
114

115
    outputs = await digest_to_snapshot(first_jar_contents.output_digest)
×
116

117
    class_index = await execute_process_or_raise(
×
118
        **implicitly(
119
            JvmProcess(
120
                jdk=jdk,
121
                classpath_entries=[f"{jdk.java_home}/lib/tools.jar"],
122
                argv=[
123
                    "com.sun.tools.javap.Main",
124
                    *("-cp", jarfile),
125
                    *outputs.files,
126
                ],
127
                input_digest=first_jar_contents.output_digest,
128
                description=f"Index class files in `{jarfile}` to find `main`",
129
                level=LogLevel.DEBUG,
130
            )
131
        )
132
    )
133

134
    output = class_index.stdout.decode()
×
135
    p = re.compile(r"^public .*?class (.*?) .*?{(.*?)}$", flags=re.MULTILINE | re.DOTALL)
×
136
    classes: list[tuple[str, str]] = re.findall(p, output)
×
137
    mains = tuple(
×
138
        classname
139
        for classname, definition in classes
140
        if "public static void main(java.lang.String[])" in definition
141
    )
142

143
    return mains
×
144

145

146
@rule
3✔
147
async def create_run_request(
3✔
148
    request: GenericJvmRunRequest,
149
    unzip: UnzipBinary,
150
) -> RunRequest:
151
    field_set = request.field_set
×
152

153
    jdk = await prepare_jdk_environment(**implicitly(JdkRequest.from_field(field_set.jdk_version)))
×
154

155
    artifacts = await resolve_coarsened_targets(**implicitly(Addresses([field_set.address])))
×
156
    classpath = await classpath_get(artifacts, **implicitly())
×
157

158
    classpath_entries = list(classpath.args(prefix="{chroot}"))
×
159

160
    input_digest = await merge_digests(MergeDigests(classpath.digests()))
×
161

162
    # Assume that the first entry in `classpath_entries` is the artifact specified in `Addresses`?
163
    main_class = field_set.main_class.value
×
164
    if main_class is None:
×
165
        main_class = await _find_main(unzip, jdk, input_digest, classpath_entries[0])
×
166

167
    proc = await jvm_process(
×
168
        **implicitly(
169
            JvmProcess(
170
                jdk=jdk,
171
                classpath_entries=classpath_entries,
172
                argv=(main_class,),
173
                input_digest=input_digest,
174
                description=f"Run {field_set.address}",
175
                use_nailgun=False,
176
            )
177
        )
178
    )
179

180
    return _post_process_jvm_process(proc, jdk)
×
181

182

183
@memoized
3✔
184
def _jvm_source_run_request_rule(cls: type[JvmRunnableSourceFieldSet]) -> Iterable[Rule]:
3✔
185
    @rule(
3✔
186
        canonical_name_suffix=cls.__name__,
187
        _param_type_overrides={"request": cls},
188
        level=LogLevel.DEBUG,
189
    )
190
    async def jvm_source_run_request(request: JvmRunnableSourceFieldSet) -> RunRequest:
3✔
191
        return await create_run_request(GenericJvmRunRequest(request), **implicitly())
×
192

193
    return [*run_rules(), *collect_rules(locals())]
3✔
194

195

196
def jvm_run_rules(cls: type[JvmRunnableSourceFieldSet]) -> Iterable[Rule | UnionRule]:
3✔
197
    yield from _jvm_source_run_request_rule(cls)
3✔
198
    yield from cls.rules()
3✔
199

200

201
def _post_process_jvm_process(proc: Process, jdk: JdkEnvironment) -> RunRequest:
3✔
202
    # TODO(#16104) This argument re-writing code should use the native {chroot} support.
203
    # See also `jdk_rules.py` for other argument re-writing code.
204
    def prefixed(arg: str, prefixes: Iterable[str]) -> str:
×
205
        if any(arg.startswith(prefix) for prefix in prefixes):
×
206
            return f"{{chroot}}/{arg}"
×
207
        else:
208
            return arg
×
209

210
    prefixes = (jdk.bin_dir, jdk.jdk_preparation_script, jdk.java_home)
×
211
    args = [prefixed(arg, prefixes) for arg in proc.argv]
×
212

213
    env = {
×
214
        **proc.env,
215
        "PANTS_INTERNAL_ABSOLUTE_PREFIX": "{chroot}/",
216
    }
217

218
    # absolutify coursier cache envvars
219
    for key in env:
×
220
        if key.startswith("COURSIER"):
×
221
            env[key] = prefixed(env[key], (jdk.coursier.cache_dir,))
×
222

223
    return RunRequest(
×
224
        digest=proc.input_digest,
225
        args=args,
226
        extra_env=env,
227
        immutable_input_digests=proc.immutable_input_digests,
228
        append_only_caches=proc.append_only_caches,
229
    )
230

231

232
def run_rules():
3✔
233
    return [
3✔
234
        *collect_rules(),
235
        *system_binaries_rules(),
236
        *jdk_rules(),
237
        *classpath_rules(),
238
    ]
239

240

241
def rules():
3✔
242
    return [
×
243
        *run_rules(),
244
        *jvm_run_rules(JvmArtifactFieldSet),
245
    ]
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