• 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/backend/python/goals/run_helper.py
1
# Copyright 2022 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
UNCOV
3
from __future__ import annotations
×
4

UNCOV
5
import dataclasses
×
UNCOV
6
import os
×
UNCOV
7
import textwrap
×
UNCOV
8
from collections.abc import Iterable
×
9

UNCOV
10
from pants.backend.python.subsystems.debugpy import DebugPy
×
UNCOV
11
from pants.backend.python.target_types import (
×
12
    ConsoleScript,
13
    Executable,
14
    PexEntryPointField,
15
    ResolvePexEntryPointRequest,
16
)
UNCOV
17
from pants.backend.python.target_types_rules import resolve_pex_entry_point
×
UNCOV
18
from pants.backend.python.util_rules.pex import (
×
19
    Pex,
20
    VenvPexRequest,
21
    create_venv_pex,
22
    find_interpreter,
23
)
UNCOV
24
from pants.backend.python.util_rules.pex_environment import PexEnvironment
×
UNCOV
25
from pants.backend.python.util_rules.pex_from_targets import (
×
26
    PexFromTargetsRequest,
27
    create_pex_from_targets,
28
)
UNCOV
29
from pants.backend.python.util_rules.python_sources import (
×
30
    PythonSourceFilesRequest,
31
    prepare_python_sources,
32
)
UNCOV
33
from pants.core.goals.run import RunDebugAdapterRequest, RunRequest
×
UNCOV
34
from pants.core.subsystems.debug_adapter import DebugAdapterSubsystem
×
UNCOV
35
from pants.engine.addresses import Address
×
UNCOV
36
from pants.engine.fs import CreateDigest, FileContent, MergeDigests
×
UNCOV
37
from pants.engine.internals.graph import transitive_targets as transitive_targets_get
×
UNCOV
38
from pants.engine.intrinsics import create_digest, merge_digests
×
UNCOV
39
from pants.engine.rules import concurrently, implicitly
×
UNCOV
40
from pants.engine.target import TransitiveTargetsRequest
×
UNCOV
41
from pants.util.frozendict import FrozenDict
×
42

43

UNCOV
44
def _in_chroot(relpath: str) -> str:
×
45
    return os.path.join("{chroot}", relpath)
×
46

47

UNCOV
48
async def _create_python_source_run_request(
×
49
    address: Address,
50
    *,
51
    entry_point_field: PexEntryPointField,
52
    pex_env: PexEnvironment,
53
    run_in_sandbox: bool,
54
    pex_path: Iterable[Pex] = (),
55
    console_script: ConsoleScript | None = None,
56
    executable: Executable | None = None,
57
) -> RunRequest:
58
    addresses = [address]
×
59
    entry_point, transitive_targets = await concurrently(
×
60
        resolve_pex_entry_point(ResolvePexEntryPointRequest(entry_point_field)),
61
        transitive_targets_get(TransitiveTargetsRequest(addresses), **implicitly()),
62
    )
63

64
    pex_filename = (
×
65
        address.generated_name.replace(".", "_") if address.generated_name else address.target_name
66
    )
67

68
    pex_request, sources = await concurrently(
×
69
        create_pex_from_targets(
70
            PexFromTargetsRequest(
71
                addresses,
72
                output_filename=f"{pex_filename}.pex",
73
                internal_only=True,
74
                include_source_files=False,
75
                include_local_dists=True,
76
                # `PEX_EXTRA_SYS_PATH` should contain this entry_point's module.
77
                main=executable or console_script or entry_point.val,
78
                additional_args=(
79
                    # N.B.: Since we cobble together the runtime environment via PEX_EXTRA_SYS_PATH
80
                    # below, it's important for any app that re-executes itself that these environment
81
                    # variables are not stripped.
82
                    "--no-strip-pex-env",
83
                ),
84
            ),
85
            **implicitly(),
86
        ),
87
        prepare_python_sources(
88
            PythonSourceFilesRequest(transitive_targets.closure, include_files=True), **implicitly()
89
        ),
90
    )
91

92
    pex_request = dataclasses.replace(pex_request, pex_path=(*pex_request.pex_path, *pex_path))
×
93

94
    if run_in_sandbox:
×
95
        # Note that a RunRequest always expects to run directly in the sandbox/workspace
96
        # root, hence working_directory=None.
97
        complete_pex_environment = pex_env.in_sandbox(working_directory=None)
×
98
    else:
99
        complete_pex_environment = pex_env.in_workspace()
×
100
    venv_pex, python = await concurrently(
×
101
        create_venv_pex(VenvPexRequest(pex_request, complete_pex_environment), **implicitly()),
102
        find_interpreter(pex_request.interpreter_constraints, **implicitly()),
103
    )
104
    input_digests = [
×
105
        venv_pex.digest,
106
        # Note regarding not-in-sandbox mode: You might think that the sources don't need to be copied
107
        # into the chroot when using inline sources. But they do, because some of them might be
108
        # codegenned, and those won't exist in the inline source tree. Rather than incurring the
109
        # complexity of figuring out here which sources were codegenned, we copy everything.
110
        # The inline source roots precede the chrooted ones in PEX_EXTRA_SYS_PATH, so the inline
111
        # sources will take precedence and their copies in the chroot will be ignored.
112
        sources.source_files.snapshot.digest,
113
    ]
114
    merged_digest = await merge_digests(MergeDigests(input_digests))
×
115

116
    chrooted_source_roots = [_in_chroot(sr) for sr in sources.source_roots]
×
117
    # The order here is important: we want the in-repo sources to take precedence over their
118
    # copies in the sandbox (see above for why those copies exist even in non-sandboxed mode).
119
    source_roots = [
×
120
        *([] if run_in_sandbox else sources.source_roots),
121
        *chrooted_source_roots,
122
    ]
123
    extra_env = {
×
124
        **complete_pex_environment.environment_dict(python=python),
125
        "PEX_EXTRA_SYS_PATH": os.pathsep.join(source_roots),
126
    }
127
    append_only_caches = (
×
128
        FrozenDict({}) if venv_pex.append_only_caches is None else venv_pex.append_only_caches
129
    )
130

131
    return RunRequest(
×
132
        digest=merged_digest,
133
        args=[_in_chroot(venv_pex.pex.argv0)],
134
        extra_env=extra_env,
135
        append_only_caches={
136
            **complete_pex_environment.append_only_caches,
137
            **append_only_caches,
138
        },
139
    )
140

141

UNCOV
142
async def _create_python_source_run_dap_request(
×
143
    regular_run_request: RunRequest,
144
    *,
145
    debugpy: DebugPy,
146
    debug_adapter: DebugAdapterSubsystem,
147
    run_in_sandbox: bool,
148
) -> RunDebugAdapterRequest:
149
    launcher_digest = await create_digest(
×
150
        CreateDigest(
151
            [
152
                FileContent(
153
                    "__debugpy_launcher.py",
154
                    textwrap.dedent(
155
                        """
156
                            import os
157
                            CHROOT = os.environ["PANTS_CHROOT"]
158

159
                            del os.environ["PEX_INTERPRETER"]
160

161
                            import debugpy._vendored.force_pydevd
162
                            from _pydevd_bundle.pydevd_process_net_command_json import PyDevJsonCommandProcessor
163
                            orig_resolve_remote_root = PyDevJsonCommandProcessor._resolve_remote_root
164

165
                            def patched_resolve_remote_root(self, local_root, remote_root):
166
                                if remote_root == ".":
167
                                    remote_root = CHROOT
168
                                return orig_resolve_remote_root(self, local_root, remote_root)
169

170
                            PyDevJsonCommandProcessor._resolve_remote_root = patched_resolve_remote_root
171

172
                            from debugpy.server import cli
173
                            cli.main()
174
                            """
175
                    ).encode("utf-8"),
176
                ),
177
            ]
178
        ),
179
    )
180

181
    merged_digest = await merge_digests(MergeDigests([regular_run_request.digest, launcher_digest]))
×
182
    extra_env = dict(regular_run_request.extra_env)
×
183
    extra_env["PEX_INTERPRETER"] = "1"
×
184
    # See https://github.com/pantsbuild/pants/issues/17540
185
    # and https://github.com/pantsbuild/pants/issues/18243
186
    # For `run --debug-adapter`, the client might send a `pathMappings`
187
    # (this is likely as VS Code likes to configure that by default) with a `remoteRoot` of ".".
188
    #
189
    # For `run`, CWD is the build root. If `run_in_sandbox` is False, everything is OK.
190
    # If `run_in_sandbox` is True, breakpoints won't be hit as CWD != sandbox root.
191
    #
192
    # We fix this by monkeypatching pydevd (the library powering debugpy) so that a remoteRoot of "."
193
    # means the sandbox root.
194
    # See https://github.com/fabioz/PyDev.Debugger/pull/243 for a better solution.
195
    extra_env["PANTS_CHROOT"] = _in_chroot("").rstrip("/") if run_in_sandbox else "."
×
196
    args = [
×
197
        *regular_run_request.args,
198
        _in_chroot("__debugpy_launcher.py"),
199
        *debugpy.get_args(debug_adapter),
200
    ]
201

202
    return RunDebugAdapterRequest(
×
203
        digest=merged_digest,
204
        args=args,
205
        extra_env=extra_env,
206
        append_only_caches=regular_run_request.append_only_caches,
207
        immutable_input_digests=regular_run_request.immutable_input_digests,
208
    )
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