• 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/docker/util_rules/docker_binary.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
import os
×
UNCOV
8
from collections.abc import Mapping, Sequence
×
UNCOV
9
from dataclasses import dataclass
×
UNCOV
10
from typing import cast
×
11

UNCOV
12
from pants.backend.docker.subsystems.docker_options import DockerOptions
×
UNCOV
13
from pants.backend.docker.util_rules.docker_build_args import DockerBuildArgs
×
UNCOV
14
from pants.core.util_rules.system_binaries import (
×
15
    BinaryPath,
16
    BinaryPathRequest,
17
    BinaryPathTest,
18
    BinaryShims,
19
    BinaryShimsRequest,
20
    create_binary_shims,
21
    find_binary,
22
)
UNCOV
23
from pants.engine.fs import Digest
×
UNCOV
24
from pants.engine.internals.selectors import concurrently
×
UNCOV
25
from pants.engine.process import Process, ProcessCacheScope
×
UNCOV
26
from pants.engine.rules import collect_rules, implicitly, rule
×
UNCOV
27
from pants.util.logging import LogLevel
×
UNCOV
28
from pants.util.strutil import pluralize
×
29

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

32

UNCOV
33
@dataclass(frozen=True)
×
UNCOV
34
class DockerBinary(BinaryPath):
×
35
    """The `docker` binary."""
36

UNCOV
37
    extra_env: Mapping[str, str]
×
UNCOV
38
    extra_input_digests: Mapping[str, Digest] | None
×
39

UNCOV
40
    is_podman: bool
×
41

UNCOV
42
    def __init__(
×
43
        self,
44
        path: str,
45
        fingerprint: str | None = None,
46
        extra_env: Mapping[str, str] | None = None,
47
        extra_input_digests: Mapping[str, Digest] | None = None,
48
        is_podman: bool = False,
49
    ) -> None:
UNCOV
50
        object.__setattr__(self, "extra_env", {} if extra_env is None else extra_env)
×
UNCOV
51
        object.__setattr__(self, "extra_input_digests", extra_input_digests)
×
UNCOV
52
        object.__setattr__(self, "is_podman", is_podman)
×
UNCOV
53
        super().__init__(path, fingerprint)
×
54

UNCOV
55
    def _get_process_environment(self, env: Mapping[str, str]) -> Mapping[str, str]:
×
UNCOV
56
        if not self.extra_env:
×
UNCOV
57
            return env
×
58

59
        res = {**self.extra_env, **env}
×
60

61
        # Merge the PATH entries, in case they are present in both `env` and `self.extra_env`.
62
        res["PATH"] = os.pathsep.join(
×
63
            p for p in (m.get("PATH") for m in (self.extra_env, env)) if p
64
        )
65
        return res
×
66

UNCOV
67
    def build_image(
×
68
        self,
69
        tags: tuple[str, ...],
70
        digest: Digest,
71
        dockerfile: str,
72
        build_args: DockerBuildArgs,
73
        context_root: str,
74
        env: Mapping[str, str],
75
        use_buildx: bool,
76
        extra_args: tuple[str, ...] = (),
77
    ) -> Process:
UNCOV
78
        if use_buildx:
×
UNCOV
79
            build_commands = ["buildx", "build"]
×
80
        else:
UNCOV
81
            build_commands = ["build"]
×
82

UNCOV
83
        args = [self.path, *build_commands, *extra_args]
×
84

UNCOV
85
        for tag in tags:
×
UNCOV
86
            args.extend(["--tag", tag])
×
87

UNCOV
88
        for build_arg in build_args:
×
UNCOV
89
            args.extend(["--build-arg", build_arg])
×
90

UNCOV
91
        args.extend(["--file", dockerfile])
×
92

93
        # Docker context root.
UNCOV
94
        args.append(context_root)
×
95

UNCOV
96
        return Process(
×
97
            argv=tuple(args),
98
            description=(
99
                f"Building docker image {tags[0]}"
100
                + (f" +{pluralize(len(tags) - 1, 'additional tag')}." if len(tags) > 1 else "")
101
            ),
102
            env=self._get_process_environment(env),
103
            input_digest=digest,
104
            immutable_input_digests=self.extra_input_digests,
105
            # We must run the docker build commands every time, even if nothing has changed,
106
            # in case the user ran `docker image rm` outside of Pants.
107
            cache_scope=ProcessCacheScope.PER_SESSION,
108
        )
109

UNCOV
110
    def push_image(self, tag: str, env: Mapping[str, str] | None = None) -> Process:
×
UNCOV
111
        return Process(
×
112
            argv=(self.path, "push", tag),
113
            cache_scope=ProcessCacheScope.PER_SESSION,
114
            description=f"Pushing docker image {tag}",
115
            env=self._get_process_environment(env or {}),
116
            immutable_input_digests=self.extra_input_digests,
117
        )
118

UNCOV
119
    def run_image(
×
120
        self,
121
        tag: str,
122
        *,
123
        docker_run_args: tuple[str, ...] | None = None,
124
        image_args: tuple[str, ...] | None = None,
125
        env: Mapping[str, str] | None = None,
126
    ) -> Process:
UNCOV
127
        return Process(
×
128
            argv=(self.path, "run", *(docker_run_args or []), tag, *(image_args or [])),
129
            cache_scope=ProcessCacheScope.PER_SESSION,
130
            description=f"Running docker image {tag}",
131
            env=self._get_process_environment(env or {}),
132
            immutable_input_digests=self.extra_input_digests,
133
        )
134

135

UNCOV
136
async def _get_docker_tools_shims(
×
137
    *,
138
    tools: Sequence[str],
139
    optional_tools: Sequence[str],
140
    search_path: Sequence[str],
141
    rationale: str,
142
) -> BinaryShims:
UNCOV
143
    all_binary_first_paths: list[BinaryPath] = []
×
144

UNCOV
145
    if tools:
×
UNCOV
146
        tools_requests = [
×
147
            BinaryPathRequest(binary_name=binary_name, search_path=search_path)
148
            for binary_name in tools
149
        ]
150

UNCOV
151
        tools_paths = await concurrently(
×
152
            find_binary(tools_request, **implicitly()) for tools_request in tools_requests
153
        )
154

UNCOV
155
        all_binary_first_paths.extend(
×
156
            [
157
                path.first_path_or_raise(request, rationale=rationale)
158
                for request, path in zip(tools_requests, tools_paths)
159
            ]
160
        )
161

UNCOV
162
    if optional_tools:
×
UNCOV
163
        optional_tools_requests = [
×
164
            BinaryPathRequest(binary_name=binary_name, search_path=search_path)
165
            for binary_name in optional_tools
166
        ]
167

UNCOV
168
        optional_tools_paths = await concurrently(
×
169
            find_binary(optional_tools_request, **implicitly())
170
            for optional_tools_request in optional_tools_requests
171
        )
172

UNCOV
173
        all_binary_first_paths.extend(
×
174
            [
175
                cast(BinaryPath, path.first_path)  # safe since we check for non-empty paths below
176
                for path in optional_tools_paths
177
                if path.paths
178
            ]
179
        )
180

UNCOV
181
    tools_shims = await create_binary_shims(
×
182
        BinaryShimsRequest.for_paths(
183
            *all_binary_first_paths,
184
            rationale=rationale,
185
        ),
186
        **implicitly(),
187
    )
188

UNCOV
189
    return tools_shims
×
190

191

UNCOV
192
@rule(desc="Finding the `docker` binary and related tooling", level=LogLevel.DEBUG)
×
UNCOV
193
async def get_docker(
×
194
    docker_options: DockerOptions, docker_options_env_aware: DockerOptions.EnvironmentAware
195
) -> DockerBinary:
UNCOV
196
    search_path = docker_options_env_aware.executable_search_path
×
197

UNCOV
198
    first_path: BinaryPath | None = None
×
UNCOV
199
    is_podman = False
×
200

UNCOV
201
    if getattr(docker_options.options, "experimental_enable_podman", False):
×
202
        # Enable podman support with `pants.backend.experimental.docker.podman`
UNCOV
203
        request = BinaryPathRequest(
×
204
            binary_name="podman",
205
            search_path=search_path,
206
            test=BinaryPathTest(args=["-v"]),
207
        )
UNCOV
208
        paths = await find_binary(request, **implicitly())
×
UNCOV
209
        first_path = paths.first_path
×
UNCOV
210
        if first_path:
×
UNCOV
211
            is_podman = True
×
UNCOV
212
            logger.warning("podman found. Podman support is experimental.")
×
213

UNCOV
214
    if not first_path:
×
UNCOV
215
        request = BinaryPathRequest(
×
216
            binary_name="docker",
217
            search_path=search_path,
218
            test=BinaryPathTest(args=["-v"]),
219
        )
UNCOV
220
        paths = await find_binary(request, **implicitly())
×
UNCOV
221
        first_path = paths.first_path_or_raise(request, rationale="interact with the docker daemon")
×
222

UNCOV
223
    if not docker_options.tools and not docker_options.optional_tools:
×
UNCOV
224
        return DockerBinary(first_path.path, first_path.fingerprint, is_podman=is_podman)
×
225

UNCOV
226
    tools_shims = await _get_docker_tools_shims(
×
227
        tools=docker_options.tools,
228
        optional_tools=docker_options.optional_tools,
229
        search_path=search_path,
230
        rationale="use docker",
231
    )
232

UNCOV
233
    extra_env = {"PATH": tools_shims.path_component}
×
UNCOV
234
    extra_input_digests = tools_shims.immutable_input_digests
×
235

UNCOV
236
    return DockerBinary(
×
237
        first_path.path,
238
        first_path.fingerprint,
239
        extra_env=extra_env,
240
        extra_input_digests=extra_input_digests,
241
        is_podman=is_podman,
242
    )
243

244

UNCOV
245
def rules():
×
UNCOV
246
    return collect_rules()
×
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