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

pantsbuild / pants / 21552830208

31 Jan 2026 11:40PM UTC coverage: 80.277% (-0.05%) from 80.324%
21552830208

Pull #23062

github

web-flow
Merge 808a9786c into 2c4dcf9cf
Pull Request #23062: Remove support for Get

18 of 25 new or added lines in 4 files covered. (72.0%)

17119 existing lines in 541 files now uncovered.

78278 of 97510 relevant lines covered (80.28%)

3.36 hits per line

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

75.0
/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
12✔
5

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

10
from pants.core.util_rules import system_binaries
12✔
11
from pants.core.util_rules.system_binaries import UnzipBinary
12✔
12
from pants.engine.fs import Digest, MergeDigests, RemovePrefix
12✔
13
from pants.engine.intrinsics import merge_digests, remove_prefix
12✔
14
from pants.engine.process import Process, execute_process_or_raise
12✔
15
from pants.engine.rules import collect_rules, concurrently, implicitly, rule
12✔
16
from pants.engine.target import CoarsenedTargets
12✔
17
from pants.jvm.compile import (
12✔
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
12✔
25
from pants.jvm.resolve.coursier_fetch import select_coursier_resolve_for_targets
12✔
26
from pants.jvm.resolve.key import CoursierResolveKey
12✔
27
from pants.util.logging import LogLevel
12✔
28

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

31

32
@dataclass(frozen=True)
12✔
33
class Classpath:
12✔
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, ...]
12✔
48
    resolve: CoursierResolveKey
12✔
49

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

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

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

62
    def immutable_inputs(self, *, prefix: str = "") -> Iterator[tuple[str, Digest]]:
12✔
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]:
12✔
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]]:
12✔
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]:
12✔
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
12✔
82
async def classpath(
12✔
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)
12✔
111
class LooseClassfiles:
12✔
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
12✔
119

120

121
@rule
12✔
122
async def loose_classfiles(
12✔
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():
12✔
154
    return [
12✔
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

© 2026 Coveralls, Inc