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

pantsbuild / pants / 18517631058

15 Oct 2025 04:18AM UTC coverage: 69.207% (-11.1%) from 80.267%
18517631058

Pull #22745

github

web-flow
Merge 642a76ca1 into 99919310e
Pull Request #22745: [windows] Add windows support in the stdio crate.

53815 of 77759 relevant lines covered (69.21%)

2.42 hits per line

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

53.42
/src/python/pants/jvm/shading/rules.py
1
# Copyright 2022 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 os
3✔
8
from collections.abc import Iterable
3✔
9
from dataclasses import dataclass
3✔
10
from pathlib import PurePath
3✔
11

12
from pants.engine.engine_aware import EngineAwareParameter
3✔
13
from pants.engine.fs import (
3✔
14
    AddPrefix,
15
    CreateDigest,
16
    Digest,
17
    Directory,
18
    FileContent,
19
    MergeDigests,
20
    RemovePrefix,
21
)
22
from pants.engine.intrinsics import add_prefix, create_digest, merge_digests, remove_prefix
3✔
23
from pants.engine.process import execute_process_or_raise
3✔
24
from pants.engine.rules import collect_rules, concurrently, implicitly, rule
3✔
25
from pants.jvm.jdk_rules import InternalJdk, JvmProcess
3✔
26
from pants.jvm.resolve.coursier_fetch import ToolClasspathRequest, materialize_classpath_for_tool
3✔
27
from pants.jvm.resolve.jvm_tool import GenerateJvmLockfileFromTool
3✔
28
from pants.jvm.shading import jarjar
3✔
29
from pants.jvm.shading.jarjar import JarJar, MisplacedClassStrategy
3✔
30
from pants.jvm.target_types import JvmShadingRule, _shading_validate_rules
3✔
31
from pants.util.logging import LogLevel
3✔
32

33
logger = logging.getLogger(__name__)
3✔
34

35

36
@dataclass(frozen=True)
3✔
37
class ShadeJarRequest(EngineAwareParameter):
3✔
38
    path: PurePath
3✔
39
    digest: Digest
3✔
40
    rules: tuple[JvmShadingRule, ...]
3✔
41

42
    # JarJar configuration options
43
    skip_manifest: bool | None
3✔
44
    misplaced_class_strategy: MisplacedClassStrategy | None
3✔
45

46
    def __init__(
3✔
47
        self,
48
        *,
49
        path: str | PurePath,
50
        digest: Digest,
51
        rules: Iterable[JvmShadingRule] | None = None,
52
        skip_manifest: bool | None = None,
53
        misplaced_class_strategy: MisplacedClassStrategy | None = None,
54
    ) -> None:
55
        object.__setattr__(self, "path", path if isinstance(path, PurePath) else PurePath(path))
×
56
        object.__setattr__(self, "digest", digest)
×
57
        object.__setattr__(self, "rules", tuple(rules or ()))
×
58
        object.__setattr__(self, "skip_manifest", skip_manifest)
×
59
        object.__setattr__(self, "misplaced_class_strategy", misplaced_class_strategy)
×
60

61
        self.__post_init__()
×
62

63
    def __post_init__(self):
3✔
64
        validation_errors = _shading_validate_rules(self.rules)
×
65
        if validation_errors:
×
66
            raise ValueError("\n".join(["Invalid rules provided:\n", *validation_errors]))
×
67

68
    def debug_hint(self) -> str | None:
3✔
69
        return str(self.path)
×
70

71

72
@dataclass(frozen=True)
3✔
73
class ShadedJar:
3✔
74
    path: str
3✔
75
    digest: Digest
3✔
76

77

78
_JARJAR_MAIN_CLASS = "com.eed3si9n.jarjar.Main"
3✔
79
_JARJAR_RULE_CONFIG_FILENAME = "rules"
3✔
80

81

82
@rule(desc="Applies shading rules to a JAR file")
3✔
83
async def shade_jar(request: ShadeJarRequest, jdk: InternalJdk, jarjar: JarJar) -> ShadedJar:
3✔
84
    if not request.rules:
×
85
        return ShadedJar(path=str(request.path), digest=request.digest)
×
86

87
    output_prefix = "__out"
×
88
    output_filename = os.path.join(output_prefix, request.path.name)
×
89

90
    rule_config_content = "\n".join([rule.encode() for rule in request.rules]) + "\n"
×
91
    logger.debug(f"Using JarJar rule file with following contents:\n{rule_config_content}")
×
92

93
    conf_digest, output_digest = await concurrently(
×
94
        create_digest(
95
            CreateDigest(
96
                [
97
                    FileContent(
98
                        path=_JARJAR_RULE_CONFIG_FILENAME,
99
                        content=rule_config_content.encode("utf-8"),
100
                    ),
101
                ]
102
            )
103
        ),
104
        create_digest(CreateDigest([Directory(output_prefix)])),
105
    )
106

107
    tool_classpath, input_digest = await concurrently(
×
108
        materialize_classpath_for_tool(
109
            ToolClasspathRequest(lockfile=GenerateJvmLockfileFromTool.create(jarjar))
110
        ),
111
        merge_digests(MergeDigests([request.digest, output_digest])),
112
    )
113

114
    toolcp_prefix = "__toolcp"
×
115
    conf_prefix = "__conf"
×
116
    immutable_input_digests = {
×
117
        toolcp_prefix: tool_classpath.digest,
118
        conf_prefix: conf_digest,
119
    }
120

121
    def should_skip_manifest() -> bool:
×
122
        if request.skip_manifest is not None:
×
123
            return request.skip_manifest
×
124
        return jarjar.skip_manifest
×
125

126
    system_properties: dict[str, str] = {
×
127
        "verbose": str(logger.isEnabledFor(LogLevel.DEBUG.level)).lower(),
128
        "skipManifest": str(should_skip_manifest()).lower(),
129
    }
130
    misplaced_class_strategy = request.misplaced_class_strategy or jarjar.misplaced_class_strategy
×
131
    if misplaced_class_strategy:
×
132
        system_properties["misplacedClassStrategy"] = misplaced_class_strategy.value
×
133

134
    result = await execute_process_or_raise(
×
135
        **implicitly(
136
            JvmProcess(
137
                jdk=jdk,
138
                argv=[
139
                    _JARJAR_MAIN_CLASS,
140
                    "process",
141
                    os.path.join(conf_prefix, _JARJAR_RULE_CONFIG_FILENAME),
142
                    str(request.path),
143
                    output_filename,
144
                ],
145
                classpath_entries=tool_classpath.classpath_entries(toolcp_prefix),
146
                input_digest=input_digest,
147
                extra_immutable_input_digests=immutable_input_digests,
148
                extra_jvm_options=[
149
                    *jarjar.jvm_options,
150
                    *[f"-D{prop}={value}" for prop, value in system_properties.items()],
151
                ],
152
                description=f"Shading JAR {request.path}",
153
                output_directories=(output_prefix,),
154
                level=LogLevel.DEBUG,
155
            )
156
        )
157
    )
158

159
    shaded_jar_digest = await remove_prefix(RemovePrefix(result.output_digest, output_prefix))
×
160
    if request.path.parents:
×
161
        # Restore the folder structure of the original path in the output digest
162
        shaded_jar_digest = await add_prefix(AddPrefix(shaded_jar_digest, str(request.path.parent)))
×
163

164
    return ShadedJar(path=str(request.path), digest=shaded_jar_digest)
×
165

166

167
def rules():
3✔
168
    return [*collect_rules(), *jarjar.rules()]
3✔
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