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

pantsbuild / pants / 20332790708

18 Dec 2025 09:48AM UTC coverage: 64.992% (-15.3%) from 80.295%
20332790708

Pull #22949

github

web-flow
Merge f730a56cd into 407284c67
Pull Request #22949: Add experimental uv resolver for Python lockfiles

54 of 97 new or added lines in 5 files covered. (55.67%)

8270 existing lines in 295 files now uncovered.

48990 of 75379 relevant lines covered (64.99%)

1.81 hits per line

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

69.35
/src/python/pants/core/util_rules/adhoc_binaries.py
1
# Copyright 2023 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
from __future__ import annotations
5✔
5

6
import os
5✔
7
import sys
5✔
8
from dataclasses import dataclass
5✔
9
from textwrap import dedent  # noqa: PNT20
5✔
10

11
from pants.core.environments.target_types import EnvironmentTarget
5✔
12
from pants.core.subsystems.python_bootstrap import PythonBootstrapSubsystem
5✔
13
from pants.core.util_rules.system_binaries import BashBinary, SystemBinariesSubsystem, TarBinary
5✔
14
from pants.engine.download_file import download_file
5✔
15
from pants.engine.fs import DownloadFile
5✔
16
from pants.engine.internals.native_engine import FileDigest
5✔
17
from pants.engine.platform import Platform
5✔
18
from pants.engine.process import Process, ProcessCacheScope, execute_process_or_raise
5✔
19
from pants.engine.rules import collect_rules, implicitly, rule
5✔
20
from pants.util.frozendict import FrozenDict
5✔
21
from pants.util.logging import LogLevel
5✔
22

23

24
@dataclass(frozen=True)
5✔
25
class PythonBuildStandaloneBinary:
5✔
26
    """A Python interpreter for use by `@rule` code as an alternative to BashBinary scripts.
27

28
    This interpreter is provided by Python Build Standalone https://gregoryszorc.com/docs/python-build-standalone/main/,
29
    which has a few caveats. Namely it doesn't play nicely with third-party sdists. Meaning Pants'
30
    scripts being run by Python Build Standalone should avoid third-party sdists.
31
    """
32

33
    _CACHE_DIRNAME = "python_build_standalone"
5✔
34
    _SYMLINK_DIRNAME = ".python-build-standalone"
5✔
35
    APPEND_ONLY_CACHES = FrozenDict({_CACHE_DIRNAME: _SYMLINK_DIRNAME})
5✔
36

37
    path: str  # The absolute path to a Python executable
5✔
38

39

40
# NB: These private types are solely so we can test the docker-path using the local
41
# environment.
42
class _PythonBuildStandaloneBinary(PythonBuildStandaloneBinary):
5✔
43
    pass
5✔
44

45

46
class _DownloadPythonBuildStandaloneBinaryRequest:
5✔
47
    pass
5✔
48

49

50
@rule(desc="Downloading Python for scripts", level=LogLevel.TRACE)
5✔
51
async def download_python_binary(
5✔
52
    _: _DownloadPythonBuildStandaloneBinaryRequest,
53
    platform: Platform,
54
    tar_binary: TarBinary,
55
    bash_binary: BashBinary,
56
    python_bootstrap: PythonBootstrapSubsystem,
57
    system_binaries_environment: SystemBinariesSubsystem.EnvironmentAware,
58
) -> _PythonBuildStandaloneBinary:
59
    url, fingerprint, bytelen = python_bootstrap.internal_python_build_standalone_info[
×
60
        platform.value
61
    ]
62

63
    filename = url.rsplit("/", 1)[-1]
×
64
    python_archive = await download_file(
×
65
        DownloadFile(
66
            url,
67
            FileDigest(
68
                fingerprint=fingerprint,
69
                serialized_bytes_length=bytelen,
70
            ),
71
        ),
72
        **implicitly(),
73
    )
74

75
    download_result = await execute_process_or_raise(
×
76
        **implicitly(
77
            Process(
78
                argv=[tar_binary.path, "-xvf", filename],
79
                input_digest=python_archive,
80
                env={"PATH": os.pathsep.join(system_binaries_environment.system_binary_paths)},
81
                description="Extract Pants' execution Python",
82
                level=LogLevel.DEBUG,
83
                output_directories=("python",),
84
            )
85
        )
86
    )
87

88
    installation_root = f"{PythonBuildStandaloneBinary._SYMLINK_DIRNAME}/{download_result.output_digest.fingerprint}"
×
89

90
    # NB: This is similar to what we do for every Python provider. We should refactor these into
91
    # some shared code to centralize the behavior.
92
    installation_script = dedent(
×
93
        f"""\
94
        if [ ! -f "{installation_root}/DONE" ]; then
95
            cp -r python "{installation_root}"
96
            touch "{installation_root}/DONE"
97
        fi
98
        echo "$(realpath "{installation_root}")/bin/python3"
99
    """
100
    )
101

102
    result = await execute_process_or_raise(
×
103
        **implicitly(
104
            Process(
105
                [bash_binary.path, "-c", installation_script],
106
                level=LogLevel.DEBUG,
107
                input_digest=download_result.output_digest,
108
                description="Install Python for Pants usage",
109
                env={"PATH": os.pathsep.join(system_binaries_environment.system_binary_paths)},
110
                append_only_caches=PythonBuildStandaloneBinary.APPEND_ONLY_CACHES,
111
                # Don't cache, we want this to always be run so that we can assume for the rest of the
112
                # session the named_cache destination for this Python is valid, as the Python ecosystem
113
                # mainly assumes absolute paths for Python interpreters.
114
                cache_scope=ProcessCacheScope.PER_SESSION,
115
            )
116
        )
117
    )
118

119
    return _PythonBuildStandaloneBinary(result.stdout.decode().splitlines()[-1].strip())
×
120

121

122
@rule
5✔
123
async def get_python_for_scripts(env_tgt: EnvironmentTarget) -> PythonBuildStandaloneBinary:
5✔
UNCOV
124
    if env_tgt.can_access_local_system_paths:
×
UNCOV
125
        return PythonBuildStandaloneBinary(sys.executable)
×
126

127
    result = await download_python_binary(
×
128
        _DownloadPythonBuildStandaloneBinaryRequest(), **implicitly()
129
    )
130

131
    return PythonBuildStandaloneBinary(result.path)
×
132

133

134
@dataclass(frozen=True)
5✔
135
class GunzipBinaryRequest:
5✔
136
    pass
5✔
137

138

139
@dataclass(frozen=True)
5✔
140
class GunzipBinary:
5✔
141
    python_binary: PythonBuildStandaloneBinary
5✔
142

143
    def extract_archive_argv(self, archive_path: str, extract_path: str) -> tuple[str, ...]:
5✔
144
        archive_name = os.path.basename(archive_path)
×
145
        dest_file_name = os.path.splitext(archive_name)[0]
×
146
        dest_path = os.path.join(extract_path, dest_file_name)
×
147
        script = dedent(
×
148
            f"""
149
            import gzip
150
            import shutil
151
            with gzip.GzipFile(filename={archive_path!r}, mode="rb") as source:
152
                with open({dest_path!r}, "wb") as dest:
153
                    shutil.copyfileobj(source, dest)
154
            """
155
        )
156
        return (self.python_binary.path, "-c", script)
×
157

158

159
@rule
5✔
160
async def find_gunzip(python_binary: PythonBuildStandaloneBinary) -> GunzipBinary:
5✔
161
    return GunzipBinary(python_binary)
×
162

163

164
@rule
5✔
165
async def find_gunzip_wrapper(_: GunzipBinaryRequest, gunzip: GunzipBinary) -> GunzipBinary:
5✔
166
    return gunzip
×
167

168

169
def rules():
5✔
170
    return collect_rules()
5✔
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