• 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

74.19
/src/python/pants/option/global_options.py
1
# Copyright 2014 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 logging
5✔
7
import os
5✔
8
import sys
5✔
9
from dataclasses import dataclass
5✔
10
from enum import Enum
5✔
11
from pathlib import Path, PurePath
5✔
12
from typing import Type, cast
5✔
13

14
from pants.base.build_environment import is_in_container
5✔
15
from pants.base.deprecated import resolve_conflicting_options
5✔
16
from pants.base.glob_match_error_behavior import GlobMatchErrorBehavior
5✔
17
from pants.engine.fs import FileContent
5✔
18
from pants.engine.internals.native_engine import PyExecutor
5✔
19
from pants.option.bootstrap_options import BootstrapOptions
5✔
20
from pants.option.option_types import (
5✔
21
    BoolOption,
22
    EnumOption,
23
    FloatOption,
24
    IntOption,
25
    StrListOption,
26
    collect_options_info,
27
)
28
from pants.option.option_value_container import OptionValueContainer
5✔
29
from pants.option.scope import GLOBAL_SCOPE
5✔
30
from pants.option.subsystem import Subsystem
5✔
31
from pants.util.dirutil import fast_relpath_optional
5✔
32
from pants.util.docutil import doc_url
5✔
33
from pants.util.logging import LogLevel
5✔
34
from pants.util.memo import memoized_classmethod, memoized_property
5✔
35
from pants.util.ordered_set import FrozenOrderedSet, OrderedSet
5✔
36
from pants.util.strutil import Simplifier, softwrap
5✔
37

38
logger = logging.getLogger(__name__)
5✔
39

40

41
class DynamicUIRenderer(Enum):
5✔
42
    """Which renderer to use for dynamic UI."""
43

44
    indicatif_spinner = "indicatif-spinner"
5✔
45
    experimental_prodash = "experimental-prodash"
5✔
46

47

48
class KeepSandboxes(Enum):
5✔
49
    """An enum for the global option `keep_sandboxes`.
50

51
    Prefer to use this rather than requesting `GlobalOptions` for more precise invalidation.
52
    """
53

54
    always = "always"
5✔
55
    on_failure = "on_failure"
5✔
56
    never = "never"
5✔
57

58

59
# N.B. By subclassing BootstrapOptions, we inherit all of those options and are also able to extend
60
# it with non-bootstrap options too.
61
class GlobalOptions(BootstrapOptions, Subsystem):
5✔
62
    options_scope = GLOBAL_SCOPE
5✔
63
    help = "Options to control the overall behavior of Pants."
5✔
64

65
    colors = BoolOption(
5✔
66
        default=sys.stdout.isatty(),
67
        help=softwrap(
68
            """
69
            Whether Pants should use colors in output or not. This may also impact whether
70
            some tools Pants runs use color.
71

72
            When unset, this value defaults based on whether the output destination supports color.
73
            """
74
        ),
75
    )
76
    dynamic_ui = BoolOption(
5✔
77
        default=(("CI" not in os.environ) and sys.stderr.isatty()),
78
        help=softwrap(
79
            """
80
            Display a dynamically-updating console UI as Pants runs. This is true by default
81
            if Pants detects a TTY and there is no 'CI' environment variable indicating that
82
            Pants is running in a continuous integration environment.
83
            """
84
        ),
85
    )
86
    dynamic_ui_renderer = EnumOption(
5✔
87
        default=DynamicUIRenderer.indicatif_spinner,
88
        help="If `--dynamic-ui` is enabled, selects the renderer.",
89
    )
90

91
    tag = StrListOption(
5✔
92
        help=softwrap(
93
            f"""
94
            Include only targets with these tags (optional '+' prefix) or without these
95
            tags ('-' prefix). See {doc_url("docs/using-pants/advanced-target-selection")}.
96
            """
97
        ),
98
        metavar="[+-]tag1,tag2,...",
99
    )
100

101
    unmatched_build_file_globs = EnumOption(
5✔
102
        default=GlobMatchErrorBehavior.warn,
103
        help=softwrap(
104
            """
105
            What to do when files and globs specified in BUILD files, such as in the
106
            `sources` field, cannot be found.
107

108
            This usually happens when the files do not exist on your machine. It can also happen
109
            if they are ignored by the `[GLOBAL].pants_ignore` option, which causes the files to be
110
            invisible to Pants.
111
            """
112
        ),
113
        advanced=True,
114
    )
115
    unmatched_cli_globs = EnumOption(
5✔
116
        default=GlobMatchErrorBehavior.error,
117
        help=softwrap(
118
            """
119
            What to do when command line arguments, e.g. files and globs like `dir::`, cannot be
120
            found.
121

122
            This usually happens when the files do not exist on your machine. It can also happen
123
            if they are ignored by the `[GLOBAL].pants_ignore` option, which causes the files to be
124
            invisible to Pants.
125
            """
126
        ),
127
        advanced=True,
128
    )
129

130
    build_patterns = StrListOption(
5✔
131
        default=["BUILD", "BUILD.*"],
132
        help=softwrap(
133
            """
134
            The naming scheme for BUILD files, i.e. where you define targets.
135

136
            This only sets the naming scheme, not the directory paths to look for. To add
137
            ignore patterns, use the option `[GLOBAL].build_ignore`.
138

139
            You may also need to update the option `[tailor].build_file_name` so that it is
140
            compatible with this option.
141
            """
142
        ),
143
        advanced=True,
144
    )
145

146
    build_ignore = StrListOption(
5✔
147
        help=softwrap(
148
            """
149
            Path globs or literals to ignore when identifying BUILD files.
150

151
            This does not affect any other filesystem operations; use `--pants-ignore` for
152
            that instead.
153
            """
154
        ),
155
        advanced=True,
156
    )
157
    build_file_prelude_globs = StrListOption(
5✔
158
        help=softwrap(
159
            f"""
160
            Python files to evaluate and whose symbols should be exposed to all BUILD files.
161
            See {doc_url("docs/writing-plugins/macros")}.
162
            """
163
        ),
164
        advanced=True,
165
    )
166
    subproject_roots = StrListOption(
5✔
167
        help="Paths that correspond with build roots for any subproject that this project depends on.",
168
        advanced=True,
169
    )
170

171
    enable_target_origin_sources_blocks = BoolOption(
5✔
172
        default=False,
173
        help="Enable fine grained target analysis based on line numbers.",
174
        advanced=True,
175
    )
176

177
    loop = BoolOption(default=False, help="Run goals continuously as file changes are detected.")
5✔
178
    loop_max = IntOption(
5✔
179
        default=2**32,
180
        help="The maximum number of times to loop when `--loop` is specified.",
181
        advanced=True,
182
    )
183

184
    streaming_workunits_report_interval = FloatOption(
5✔
185
        default=1.0,
186
        help="Interval in seconds between when streaming workunit event receivers will be polled.",
187
        advanced=True,
188
    )
189
    streaming_workunits_level = EnumOption(
5✔
190
        default=LogLevel.DEBUG,
191
        help=softwrap(
192
            """
193
            The level of workunits that will be reported to streaming workunit event receivers.
194

195
            Workunits form a tree, and even when workunits are filtered out by this setting, the
196
            workunit tree structure will be preserved (by adjusting the parent pointers of the
197
            remaining workunits).
198
            """
199
        ),
200
        advanced=True,
201
    )
202
    streaming_workunits_complete_async = BoolOption(
5✔
203
        default=not is_in_container(),
204
        help=softwrap(
205
            """
206
            True if stats recording should be allowed to complete asynchronously when `pantsd`
207
            is enabled. When `pantsd` is disabled, stats recording is always synchronous.
208
            To reduce data loss, this flag defaults to false inside of containers, such as
209
            when run with Docker.
210
            """
211
        ),
212
        advanced=True,
213
    )
214

215
    process_cleanup = BoolOption(
5✔
216
        # Should be aligned to `keep_sandboxes`'s `default`
217
        default=True,
218
        removal_version="3.0.0.dev0",
219
        removal_hint="Use the `keep_sandboxes` option instead.",
220
        help=softwrap(
221
            """
222
            If false, Pants will not clean up local directories used as chroots for running
223
            processes. Pants will log their location so that you can inspect the chroot, and
224
            run the `__run.sh` script to recreate the process using the same argv and
225
            environment variables used by Pants. This option is useful for debugging.
226
            """
227
        ),
228
    )
229
    keep_sandboxes = EnumOption(
5✔
230
        default=KeepSandboxes.never,
231
        help=softwrap(
232
            """
233
            Controls whether Pants will clean up local directories used as chroots for running
234
            processes.
235

236
            Pants will log their location so that you can inspect the chroot, and run the
237
            `__run.sh` script to recreate the process using the same argv and environment variables
238
            used by Pants. This option is useful for debugging.
239
            """
240
        ),
241
    )
242

243
    docker_execution = BoolOption(
5✔
244
        default=True,
245
        advanced=True,
246
        help=softwrap(
247
            """
248
            If true, `docker_environment` targets can be used to run builds inside a Docker
249
            container.
250

251
            If false, anytime a `docker_environment` target is used, Pants will instead fallback to
252
            whatever the target's `fallback_environment` field is set to.
253

254
            This can be useful, for example, if you want to always use Docker locally, but disable
255
            it in CI, or vice versa.
256
            """
257
        ),
258
    )
259
    remote_execution_extra_platform_properties = StrListOption(
5✔
260
        advanced=True,
261
        help=softwrap(
262
            """
263
            Platform properties to set on remote execution requests.
264

265
            Format: `property=value`. Multiple values should be specified as multiple
266
            occurrences of this flag.
267

268
            Pants itself may add additional platform properties.
269

270
            If you are using the `remote_environment` target mechanism, set this value as a field
271
            on the target instead. This option will be ignored.
272
            """
273
        ),
274
        default=[],
275
    )
276

277
    @staticmethod
5✔
278
    def create_py_executor(bootstrap_options: OptionValueContainer) -> PyExecutor:
5✔
279
        rule_threads_max = (
×
280
            bootstrap_options.rule_threads_max
281
            if bootstrap_options.rule_threads_max
282
            else 4 * bootstrap_options.rule_threads_core
283
        )
284
        return PyExecutor(
×
285
            core_threads=bootstrap_options.rule_threads_core, max_threads=rule_threads_max
286
        )
287

288
    @staticmethod
5✔
289
    def resolve_keep_sandboxes(
5✔
290
        global_options: OptionValueContainer,
291
    ) -> KeepSandboxes:
292
        resolved_value = resolve_conflicting_options(
×
293
            old_option="process_cleanup",
294
            new_option="keep_sandboxes",
295
            old_scope="",
296
            new_scope="",
297
            old_container=global_options,
298
            new_container=global_options,
299
        )
300

301
        if isinstance(resolved_value, bool):
×
302
            # Is `process_cleanup`.
303
            return KeepSandboxes.never if resolved_value else KeepSandboxes.always
×
304
        elif isinstance(resolved_value, KeepSandboxes):
×
305
            return resolved_value
×
306
        else:
307
            raise TypeError(f"Unexpected option value for `keep_sandboxes`: {resolved_value}")
×
308

309
    @staticmethod
5✔
310
    def compute_pants_ignore(buildroot, global_options):
5✔
311
        """Computes the merged value of the `--pants-ignore` flag.
312

313
        This inherently includes the workdir and distdir locations if they are located under the
314
        buildroot.
315
        """
316
        pants_ignore = list(global_options.pants_ignore)
5✔
317

318
        def add(absolute_path, include=False):
5✔
319
            # To ensure that the path is ignored regardless of whether it is a symlink or a directory, we
320
            # strip trailing slashes (which would signal that we wanted to ignore only directories).
321
            maybe_rel_path = fast_relpath_optional(absolute_path, buildroot)
5✔
322
            if maybe_rel_path:
5✔
323
                rel_path = maybe_rel_path.rstrip(os.path.sep)
5✔
324
                prefix = "!" if include else ""
5✔
325
                pants_ignore.append(f"{prefix}/{rel_path}")
5✔
326

327
        add(global_options.pants_workdir)
5✔
328
        add(global_options.pants_distdir)
5✔
329
        add(global_options.pants_subprocessdir)
5✔
330

331
        return pants_ignore
5✔
332

333
    @staticmethod
5✔
334
    def compute_pantsd_invalidation_globs(
5✔
335
        buildroot: str, bootstrap_options: OptionValueContainer
336
    ) -> tuple[str, ...]:
337
        """Computes the merged value of the `--pantsd-invalidation-globs` option.
338

339
        Combines --pythonpath and --pants-config-files files that are in {buildroot} dir with those
340
        invalidation_globs provided by users.
341
        """
UNCOV
342
        invalidation_globs: OrderedSet[str] = OrderedSet()
×
343

344
        # Globs calculated from the sys.path and other file-like configuration need to be sanitized
345
        # to relative globs (where possible).
UNCOV
346
        potentially_absolute_globs = (
×
347
            *sys.path,
348
            *bootstrap_options.pythonpath,
349
            *bootstrap_options.pants_config_files,
350
        )
UNCOV
351
        for glob in potentially_absolute_globs:
×
352
            # NB: We use `relpath` here because these paths are untrusted, and might need to be
353
            # normalized in addition to being relativized.
UNCOV
354
            glob_relpath = (
×
355
                os.path.relpath(glob, buildroot) if os.path.isabs(glob) else os.path.normpath(glob)
356
            )
UNCOV
357
            if glob_relpath == "." or glob_relpath.startswith(".."):
×
UNCOV
358
                logger.debug(
×
359
                    f"Changes to {glob}, outside of the buildroot, will not be invalidated."
360
                )
UNCOV
361
                continue
×
362

UNCOV
363
            invalidation_globs.update([glob_relpath, glob_relpath + "/**"])
×
364

365
        # Explicitly specified globs are already relative, and are added verbatim.
UNCOV
366
        invalidation_globs.update(
×
367
            ("!*.pyc", "!__pycache__/", ".gitignore", *bootstrap_options.pantsd_invalidation_globs)
368
        )
UNCOV
369
        return tuple(invalidation_globs)
×
370

371
    @memoized_classmethod
5✔
372
    def get_options_flags(cls) -> GlobalOptionsFlags:
5✔
373
        return GlobalOptionsFlags.create(cast("Type[GlobalOptions]", cls))
×
374

375
    @memoized_property
5✔
376
    def named_caches_dir(self) -> PurePath:
5✔
UNCOV
377
        return Path(self._named_caches_dir).resolve()
×
378

379
    def output_simplifier(self) -> Simplifier:
5✔
380
        """Create a `Simplifier` instance for use on stdout and stderr that will be shown to a
381
        user."""
382
        return Simplifier(
×
383
            # it's ~never useful to show the chroot path to a user
384
            strip_chroot_path=True,
385
            strip_formatting=not self.colors,
386
        )
387

388

389
@dataclass(frozen=True)
5✔
390
class GlobalOptionsFlags:
5✔
391
    flags: FrozenOrderedSet[str]
5✔
392
    short_flags: FrozenOrderedSet[str]
5✔
393

394
    @classmethod
5✔
395
    def create(cls, GlobalOptionsType: type[GlobalOptions]) -> GlobalOptionsFlags:
5✔
396
        flags = set()
×
397
        short_flags = set()
×
398

399
        for options_info in collect_options_info(BootstrapOptions):
×
400
            for flag in options_info.args:
×
401
                flags.add(flag)
×
402
                if len(flag) == 2:
×
403
                    short_flags.add(flag)
×
404
                elif options_info.kwargs.get("type") == bool:
×
405
                    flags.add(f"--no-{flag[2:]}")
×
406

407
        return cls(FrozenOrderedSet(flags), FrozenOrderedSet(short_flags))
×
408

409

410
@dataclass(frozen=True)
5✔
411
class ProcessCleanupOption:
5✔
412
    """A wrapper around the global option `process_cleanup`.
413

414
    Prefer to use this rather than requesting `GlobalOptions` for more precise invalidation.
415
    """
416

417
    val: bool
5✔
418

419

420
@dataclass(frozen=True)
5✔
421
class NamedCachesDirOption:
5✔
422
    """A wrapper around the global option `named_caches_dir`.
423

424
    Prefer to use this rather than requesting `GlobalOptions` for more precise invalidation.
425
    """
426

427
    val: PurePath
5✔
428

429

430
def ca_certs_path_to_file_content(path: str) -> FileContent:
5✔
431
    """Set up FileContent for using the ca_certs_path locally in a process sandbox.
432

433
    This helper can be used when setting up a Process so that the certs are included in the process.
434
    Use `create_digest()`, and then include this in the `input_digest` for the Process.
435
    Typically, you will also need to configure the invoking tool to load those certs, via its argv
436
    or environment variables.
437

438
    Note that the certs are always read on the localhost, even when using Docker and remote
439
    execution. Then, those certs can be copied into the process.
440

441
    Warning: this will not detect when the contents of cert files changes, because we use
442
    `pathlib.Path.read_bytes()`. Better would be
443
    # https://github.com/pantsbuild/pants/issues/10842
444
    """
445
    return FileContent(os.path.basename(path), Path(path).read_bytes())
×
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