• 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/jvm/classpath.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 logging
×
UNCOV
7
from collections.abc import Iterator
×
UNCOV
8
from dataclasses import dataclass
×
9

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

UNCOV
29
logger = logging.getLogger(__name__)
×
30

31

UNCOV
32
@dataclass(frozen=True)
×
UNCOV
33
class Classpath:
×
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

UNCOV
47
    entries: tuple[ClasspathEntry, ...]
×
UNCOV
48
    resolve: CoursierResolveKey
×
49

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

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

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

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

UNCOV
66
    def immutable_inputs_args(self, *, prefix: str = "") -> Iterator[str]:
×
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

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

UNCOV
76
    def root_immutable_inputs_args(self, *, prefix: str = "") -> Iterator[str]:
×
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

UNCOV
81
@rule
×
UNCOV
82
async def classpath(
×
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

UNCOV
110
@dataclass(frozen=True)
×
UNCOV
111
class LooseClassfiles:
×
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

UNCOV
118
    digest: Digest
×
119

120

UNCOV
121
@rule
×
UNCOV
122
async def loose_classfiles(
×
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

UNCOV
153
def rules():
×
UNCOV
154
    return [
×
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