• 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

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

4
from __future__ import annotations
7✔
5

6
import logging
7✔
7
from collections.abc import Iterator
7✔
8
from dataclasses import dataclass
7✔
9

10
from pants.core.util_rules import system_binaries
7✔
11
from pants.core.util_rules.system_binaries import UnzipBinary
7✔
12
from pants.engine.fs import Digest, MergeDigests, RemovePrefix
7✔
13
from pants.engine.intrinsics import merge_digests, remove_prefix
7✔
14
from pants.engine.process import Process, execute_process_or_raise
7✔
15
from pants.engine.rules import collect_rules, concurrently, implicitly, rule
7✔
16
from pants.engine.target import CoarsenedTargets
7✔
17
from pants.jvm.compile import (
7✔
18
    ClasspathEntry,
19
    ClasspathEntryRequest,
20
    ClasspathEntryRequestFactory,
21
    get_fallible_classpath_entry,
22
    required_classfiles,
23
)
24
from pants.jvm.compile import rules as jvm_compile_rules
7✔
25
from pants.jvm.resolve.coursier_fetch import select_coursier_resolve_for_targets
7✔
26
from pants.jvm.resolve.key import CoursierResolveKey
7✔
27
from pants.util.logging import LogLevel
7✔
28

29
logger = logging.getLogger(__name__)
7✔
30

31

32
@dataclass(frozen=True)
7✔
33
class Classpath:
7✔
34
    """A transitive classpath which is sufficient to launch the target(s) it was generated for.
35

36
    There are two primary ways to consume a Classpath:
37
        1. Using the `(root_)immutable_inputs` methods, which produce the argument to
38
           `Process.immutable_input_digests` and adapted CLI args for use with that argument.
39
        2. Using the `digests` and `(root_)args` methods, which can be merged to produce the
40
           argument to `Process.input_digest` and CLI args for use with a digest.
41
    The first approach should be preferred, because it allows for symlinking of inputs. If
42
    possible, the latter method should be removed when consumers have migrated.
43

44
    This classpath is guaranteed to contain only JAR files.
45
    """
46

47
    entries: tuple[ClasspathEntry, ...]
7✔
48
    resolve: CoursierResolveKey
7✔
49

50
    def args(self, *, prefix: str = "") -> Iterator[str]:
7✔
51
        """All transitive filenames for this Classpath."""
52
        return ClasspathEntry.args(ClasspathEntry.closure(self.entries), prefix=prefix)
×
53

54
    def root_args(self, *, prefix: str = "") -> Iterator[str]:
7✔
55
        """The root filenames for this Classpath."""
56
        return ClasspathEntry.args(self.entries, prefix=prefix)
×
57

58
    def digests(self) -> Iterator[Digest]:
7✔
59
        """All transitive Digests for this Classpath."""
60
        return (entry.digest for entry in ClasspathEntry.closure(self.entries))
×
61

62
    def immutable_inputs(self, *, prefix: str = "") -> Iterator[tuple[str, Digest]]:
7✔
63
        """Returns (relpath, Digest) tuples for use with `Process.immutable_input_digests`."""
64
        return ClasspathEntry.immutable_inputs(ClasspathEntry.closure(self.entries), prefix=prefix)
×
65

66
    def immutable_inputs_args(self, *, prefix: str = "") -> Iterator[str]:
7✔
67
        """Returns relative filenames for the given entries to be used as immutable_inputs."""
68
        return ClasspathEntry.immutable_inputs_args(
×
69
            ClasspathEntry.closure(self.entries), prefix=prefix
70
        )
71

72
    def root_immutable_inputs(self, *, prefix: str = "") -> Iterator[tuple[str, Digest]]:
7✔
73
        """Returns root (relpath, Digest) tuples for use with `Process.immutable_input_digests`."""
74
        return ClasspathEntry.immutable_inputs(self.entries, prefix=prefix)
×
75

76
    def root_immutable_inputs_args(self, *, prefix: str = "") -> Iterator[str]:
7✔
77
        """Returns root relative filenames for the given entries to be used as immutable_inputs."""
78
        return ClasspathEntry.immutable_inputs_args(self.entries, prefix=prefix)
×
79

80

81
@rule
7✔
82
async def classpath(
7✔
83
    coarsened_targets: CoarsenedTargets,
84
    classpath_entry_request: ClasspathEntryRequestFactory,
85
) -> Classpath:
86
    # Compute a single shared resolve for all of the roots, which will validate that they
87
    # are compatible with one another.
88
    resolve = await select_coursier_resolve_for_targets(coarsened_targets, **implicitly())
×
89

90
    # Then request classpath entries for each root.
91
    fallible_classpath_entries = await concurrently(
×
92
        get_fallible_classpath_entry(
93
            **implicitly(
94
                {
95
                    classpath_entry_request.for_targets(
96
                        component=t, resolve=resolve, root=True
97
                    ): ClasspathEntryRequest
98
                }
99
            )
100
        )
101
        for t in coarsened_targets
102
    )
103
    classpath_entries = await concurrently(
×
104
        required_classfiles(fce) for fce in fallible_classpath_entries
105
    )
106

107
    return Classpath(classpath_entries, resolve)
×
108

109

110
@dataclass(frozen=True)
7✔
111
class LooseClassfiles:
7✔
112
    """The contents of a classpath entry as loose classfiles.
113

114
    Note that `ClasspathEntry` and `Classpath` both guarantee that they contain JAR files, and so
115
    creating loose classfiles from them involves extracting their entry.
116
    """
117

118
    digest: Digest
7✔
119

120

121
@rule
7✔
122
async def loose_classfiles(
7✔
123
    classpath_entry: ClasspathEntry, unzip_binary: UnzipBinary
124
) -> LooseClassfiles:
125
    dest_dir = "dest"
×
126
    process_results = await concurrently(
×
127
        execute_process_or_raise(
128
            **implicitly(
129
                Process(
130
                    argv=[
131
                        unzip_binary.path,
132
                        "-d",
133
                        dest_dir,
134
                        filename,
135
                    ],
136
                    output_directories=(dest_dir,),
137
                    description=f"Extract {filename}",
138
                    immutable_input_digests=dict(
139
                        ClasspathEntry.immutable_inputs([classpath_entry])
140
                    ),
141
                    level=LogLevel.TRACE,
142
                )
143
            )
144
        )
145
        for filename in ClasspathEntry.immutable_inputs_args([classpath_entry])
146
    )
147

148
    merged_digest = await merge_digests(MergeDigests(pr.output_digest for pr in process_results))
×
149

150
    return LooseClassfiles(await remove_prefix(RemovePrefix(merged_digest, dest_dir)))
×
151

152

153
def rules():
7✔
154
    return [
7✔
155
        *collect_rules(),
156
        *system_binaries.rules(),
157
        *jvm_compile_rules(),
158
    ]
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