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

pantsbuild / pants / 23068140808

13 Mar 2026 07:56PM UTC coverage: 52.677% (-40.3%) from 92.932%
23068140808

Pull #23170

github

web-flow
Merge e8ca01cfa into f07276df6
Pull Request #23170: Debug reapi test cache misses

31687 of 60153 relevant lines covered (52.68%)

1.05 hits per line

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

70.28
/src/python/pants/option/bootstrap_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
2✔
5

6
import enum
2✔
7
import logging
2✔
8
import os
2✔
9
import re
2✔
10
import sys
2✔
11
import tempfile
2✔
12
from collections.abc import Callable
2✔
13
from dataclasses import dataclass
2✔
14
from datetime import datetime, timedelta
2✔
15
from enum import Enum
2✔
16
from pathlib import Path
2✔
17
from typing import Any, TypeVar, assert_never, cast
2✔
18

19
from pants.base.build_environment import (
2✔
20
    get_buildroot,
21
    get_default_pants_config_file,
22
    get_pants_cachedir,
23
    pants_version,
24
)
25
from pants.base.exceptions import BuildConfigurationError
2✔
26
from pants.base.glob_match_error_behavior import GlobMatchErrorBehavior
2✔
27
from pants.engine.env_vars import CompleteEnvironmentVars
2✔
28
from pants.option.custom_types import memory_size
2✔
29
from pants.option.errors import OptionsError
2✔
30
from pants.option.option_types import (
2✔
31
    BoolOption,
32
    DictOption,
33
    DirOption,
34
    EnumOption,
35
    FloatOption,
36
    IntOption,
37
    MemorySizeOption,
38
    StrListOption,
39
    StrOption,
40
)
41
from pants.option.option_value_container import OptionValueContainer
2✔
42
from pants.option.options import Options
2✔
43
from pants.util.docutil import bin_name, doc_url
2✔
44
from pants.util.logging import LogLevel
2✔
45
from pants.util.osutil import CPU_COUNT
2✔
46
from pants.util.strutil import fmt_memory_size, softwrap
2✔
47
from pants.version import VERSION
2✔
48

49
logger = logging.getLogger(__name__)
2✔
50

51

52
# The time that leases are acquired for in the local store. Configured on the Python side
53
# in order to ease interaction with the StoreGCService, which needs to be aware of its value.
54
LOCAL_STORE_LEASE_TIME_SECS = 2 * 60 * 60
2✔
55

56

57
MEGABYTES = 1_000_000
2✔
58
GIGABYTES = 1_000 * MEGABYTES
2✔
59

60

61
_G = TypeVar("_G", bound="_GlobMatchErrorBehaviorOptionBase")
2✔
62

63

64
class RemoteProvider(Enum):
2✔
65
    """Which remote provider to use."""
66

67
    reapi = "reapi"
2✔
68
    experimental_file = "experimental-file"
2✔
69
    experimental_github_actions_cache = "experimental-github-actions-cache"
2✔
70

71
    def _supports_execution(self) -> bool:
2✔
72
        return self is RemoteProvider.reapi
×
73

74
    def _supported_schemes(self) -> list[str]:
2✔
75
        if self is RemoteProvider.reapi:
2✔
76
            return ["grpc", "grpcs"]
2✔
77
        elif self is RemoteProvider.experimental_file:
2✔
78
            return ["file"]
2✔
79
        elif self is RemoteProvider.experimental_github_actions_cache:
2✔
80
            return ["http", "https"]
2✔
81

82
        assert_never(self)
×
83

84
    def _matches_scheme(self, addr: str) -> bool:
2✔
85
        return any(addr.startswith(f"{scheme}://") for scheme in self._supported_schemes())
×
86

87
    def _human_readable_schemes(self) -> str:
2✔
88
        return ", ".join(f"`{s}://`" for s in sorted(self._supported_schemes()))
2✔
89

90
    def validate_address(self, addr: str, address_source: str, provider_source: str) -> None:
2✔
91
        if self._matches_scheme(addr):
×
92
            # All good! The scheme matches this provider.
93
            return
×
94

95
        other_providers = [
×
96
            provider for provider in RemoteProvider if provider._matches_scheme(addr)
97
        ]
98
        if other_providers:
×
99
            rendered = ", ".join(f"`{p.value}`" for p in other_providers)
×
100
            provider_did_you_mean = (
×
101
                f"to use a provider that does support this scheme ({rendered}) or "
102
            )
103
        else:
104
            provider_did_you_mean = ""
×
105

106
        schemes_did_you_mean = (
×
107
            f"to use a scheme that is supported by this provider ({self._human_readable_schemes()})"
108
        )
109

110
        raise OptionsError(
×
111
            softwrap(
112
                f"""
113
                Value `{addr}` from {address_source} is invalid: it doesn't have a scheme that is
114
                supported by provider `{self.value}` from {provider_source}.
115

116
                Did you mean {provider_did_you_mean}{schemes_did_you_mean}?
117
                """
118
            )
119
        )
120

121
    def validate_execution_supported(self, provider_source: str, execution_implied_by: str) -> None:
2✔
122
        if self._supports_execution():
×
123
            # All good! Execution is supported by this provider.
124
            return
×
125

126
        supported_execution_providers = ", ".join(
×
127
            f"`{provider.value}`" for provider in RemoteProvider if provider._supports_execution()
128
        )
129
        raise OptionsError(
×
130
            softwrap(
131
                f"""
132
                Value `{self.value}` from {provider_source} is invalid: it does not support remote
133
                execution, but remote execution is required due to {execution_implied_by}.
134

135
                Either disable remote execution, or use a provider that does support remote
136
                execution: {supported_execution_providers}
137
                """
138
            )
139
        )
140

141
    @staticmethod
2✔
142
    def provider_help() -> Callable[[object], str]:
2✔
143
        def provider_list_item(p: RemoteProvider) -> str:
2✔
144
            if p is RemoteProvider.reapi:
2✔
145
                description = "a server using the Remote Execution API (https://github.com/bazelbuild/remote-apis)"
2✔
146
            elif p is RemoteProvider.experimental_github_actions_cache:
2✔
147
                description = "the GitHub Actions caching service"
2✔
148
            elif p is RemoteProvider.experimental_file:
2✔
149
                description = "a directory mapped on the current machine"
2✔
150
            else:
151
                assert_never(p)
×
152

153
            return f"- `{p.value}`: {description} (supported schemes for URIs: {p._human_readable_schemes()})"
2✔
154

155
        def renderer(_: object) -> str:
2✔
156
            list_items = "\n\n".join(provider_list_item(p) for p in RemoteProvider)
2✔
157
            return softwrap(
2✔
158
                f"""
159
                The type of provider to use, if using a remote cache and/or remote execution, See
160
                {doc_url("docs/using-pants/remote-caching-and-execution")} for details.
161

162
                Each provider supports different `remote_store_address` and (optional)
163
                `remote_execution_address` URIs.
164

165
                Supported values:
166

167
                {list_items}
168
                """
169
            )
170

171
        return renderer
2✔
172

173

174
@dataclass(frozen=True)
2✔
175
class _GlobMatchErrorBehaviorOptionBase:
2✔
176
    """This class exists to have dedicated types per global option of the `GlobMatchErrorBehavior`
177
    so we can extract the relevant option in a rule to limit the scope of downstream rules to avoid
178
    depending on the entire global options data."""
179

180
    error_behavior: GlobMatchErrorBehavior
2✔
181

182
    @classmethod
2✔
183
    def ignore(cls: type[_G]) -> _G:
2✔
184
        return cls(GlobMatchErrorBehavior.ignore)
×
185

186
    @classmethod
2✔
187
    def warn(cls: type[_G]) -> _G:
2✔
188
        return cls(GlobMatchErrorBehavior.warn)
×
189

190
    @classmethod
2✔
191
    def error(cls: type[_G]) -> _G:
2✔
192
        return cls(GlobMatchErrorBehavior.error)
2✔
193

194

195
class UnmatchedBuildFileGlobs(_GlobMatchErrorBehaviorOptionBase):
2✔
196
    """What to do when globs do not match in BUILD files."""
197

198

199
class UnmatchedCliGlobs(_GlobMatchErrorBehaviorOptionBase):
2✔
200
    """What to do when globs do not match in CLI args."""
201

202

203
class OwnersNotFoundBehavior(_GlobMatchErrorBehaviorOptionBase):
2✔
204
    """What to do when a file argument cannot be mapped to an owning target."""
205

206

207
@enum.unique
2✔
208
class RemoteCacheWarningsBehavior(Enum):
2✔
209
    ignore = "ignore"
2✔
210
    first_only = "first_only"
2✔
211
    backoff = "backoff"
2✔
212
    always = "always"
2✔
213

214

215
@enum.unique
2✔
216
class CacheContentBehavior(Enum):
2✔
217
    fetch = "fetch"
2✔
218
    validate = "validate"
2✔
219
    defer = "defer"
2✔
220

221

222
@enum.unique
2✔
223
class AuthPluginState(Enum):
2✔
224
    OK = "ok"
2✔
225
    UNAVAILABLE = "unavailable"
2✔
226

227

228
@dataclass(frozen=True)
2✔
229
class AuthPluginResult:
2✔
230
    """The return type for a function specified via `[GLOBAL].remote_auth_plugin`.
231

232
    The returned `store_headers` and `execution_headers` will replace whatever headers Pants would
233
    have used normally, e.g. what is set with `[GLOBAL].remote_store_headers`. This allows you to control
234
    the merge strategy if your plugin sets conflicting headers. Usually, you will want to preserve
235
    the `initial_store_headers` and `initial_execution_headers` passed to the plugin.
236

237
    If set, the returned `instance_name` will override `[GLOBAL].remote_instance_name`,
238
    `store_address` will override `[GLOBAL].remote_store_address`, and `execution_address` will
239
    override ``[GLOBAL].remote_execution_address``. The addresses are interpreted and validated in
240
    the same manner as the corresponding option.
241
    """
242

243
    state: AuthPluginState
2✔
244
    store_headers: dict[str, str]
2✔
245
    execution_headers: dict[str, str]
2✔
246
    provider: RemoteProvider = RemoteProvider.reapi
2✔
247
    store_address: str | None = None
2✔
248
    execution_address: str | None = None
2✔
249
    instance_name: str | None = None
2✔
250
    expiration: datetime | None = None
2✔
251
    plugin_name: str | None = None
2✔
252

253
    def __post_init__(self) -> None:
2✔
254
        name = self.plugin_name or ""
×
255
        plugin_context = f"in `AuthPluginResult` returned from `[GLOBAL].remote_auth_plugin` {name}"
×
256

257
        if self.store_address:
×
258
            self.provider.validate_address(
×
259
                self.store_address,
260
                address_source=f"`store_address` {plugin_context}",
261
                provider_source="`provider` in same result",
262
            )
263

264
        if self.execution_address:
×
265
            self.provider.validate_execution_supported(
×
266
                provider_source=f"`provider` {plugin_context}",
267
                execution_implied_by="`execution_address` in same result",
268
            )
269
            self.provider.validate_address(
×
270
                self.execution_address,
271
                address_source=f"`execution_address` {plugin_context}",
272
                provider_source="`provider` in same result",
273
            )
274

275
    @property
2✔
276
    def is_available(self) -> bool:
2✔
277
        return self.state == AuthPluginState.OK
×
278

279

280
@dataclass(frozen=True)
2✔
281
class DynamicRemoteOptions:
2✔
282
    """Options related to remote execution of processes which are computed dynamically."""
283

284
    provider: RemoteProvider
2✔
285
    execution: bool
2✔
286
    cache_read: bool
2✔
287
    cache_write: bool
2✔
288
    instance_name: str | None
2✔
289
    store_address: str | None
2✔
290
    execution_address: str | None
2✔
291
    store_headers: dict[str, str]
2✔
292
    execution_headers: dict[str, str]
2✔
293
    parallelism: int
2✔
294
    store_rpc_concurrency: int
2✔
295
    store_batch_load_enabled: bool
2✔
296
    cache_rpc_concurrency: int
2✔
297
    execution_rpc_concurrency: int
2✔
298

299
    def _validate_store_addr(self) -> None:
2✔
300
        if self.store_address:
2✔
301
            return
×
302
        if self.cache_read:
2✔
303
            raise OptionsError(
×
304
                softwrap(
305
                    """
306
                    The `[GLOBAL].remote_cache_read` option requires also setting the
307
                    `[GLOBAL].remote_store_address` option in order to work properly.
308
                    """
309
                )
310
            )
311
        if self.cache_write:
2✔
312
            raise OptionsError(
×
313
                softwrap(
314
                    """
315
                    The `[GLOBAL].remote_cache_write` option requires also setting the
316
                    `[GLOBAL].remote_store_address` option in order to work properly.
317
                    """
318
                )
319
            )
320

321
    def _validate_exec_addr(self) -> None:
2✔
322
        if not self.execution:
2✔
323
            return
2✔
324
        if not self.execution_address:
×
325
            raise OptionsError(
×
326
                softwrap(
327
                    """
328
                    The `[GLOBAL].remote_execution` option requires also setting the
329
                    `[GLOBAL].remote_execution_address` option in order to work properly.
330
                    """
331
                )
332
            )
333
        if not self.store_address:
×
334
            raise OptionsError(
×
335
                softwrap(
336
                    """
337
                    The `[GLOBAL].remote_execution_address` option requires also setting the
338
                    `[GLOBAL].remote_store_address` option. Often these have the same value.
339
                    """
340
                )
341
            )
342

343
    def __post_init__(self) -> None:
2✔
344
        self._validate_store_addr()
2✔
345
        self._validate_exec_addr()
2✔
346

347
    @classmethod
2✔
348
    def disabled(cls) -> DynamicRemoteOptions:
2✔
349
        return cls(
2✔
350
            provider=DEFAULT_EXECUTION_OPTIONS.remote_provider,
351
            execution=False,
352
            cache_read=False,
353
            cache_write=False,
354
            instance_name=None,
355
            store_address=None,
356
            execution_address=None,
357
            store_headers={},
358
            execution_headers={},
359
            parallelism=DEFAULT_EXECUTION_OPTIONS.process_execution_remote_parallelism,
360
            store_rpc_concurrency=DEFAULT_EXECUTION_OPTIONS.remote_store_rpc_concurrency,
361
            store_batch_load_enabled=DEFAULT_EXECUTION_OPTIONS.remote_store_batch_load_enabled,
362
            cache_rpc_concurrency=DEFAULT_EXECUTION_OPTIONS.remote_cache_rpc_concurrency,
363
            execution_rpc_concurrency=DEFAULT_EXECUTION_OPTIONS.remote_execution_rpc_concurrency,
364
        )
365

366
    @classmethod
2✔
367
    def _use_oauth_token(cls, bootstrap_options: OptionValueContainer) -> DynamicRemoteOptions:
2✔
368
        oauth_token = bootstrap_options.remote_oauth_bearer_token
×
369

370
        if set(oauth_token).intersection({"\n", "\r"}):
×
371
            raise OptionsError(
×
372
                "OAuth bearer token from `remote_oauth_bearer_token` option must not contain multiple lines."
373
            )
374

375
        token_header = {"authorization": f"Bearer {oauth_token}"}
×
376
        provider = cast(RemoteProvider, bootstrap_options.remote_provider)
×
377
        execution = cast(bool, bootstrap_options.remote_execution)
×
378
        cache_read = cast(bool, bootstrap_options.remote_cache_read)
×
379
        cache_write = cast(bool, bootstrap_options.remote_cache_write)
×
380
        store_address = cast("str | None", bootstrap_options.remote_store_address)
×
381
        execution_address = cast("str | None", bootstrap_options.remote_execution_address)
×
382
        instance_name = cast("str | None", bootstrap_options.remote_instance_name)
×
383
        execution_headers = cast("dict[str, str]", bootstrap_options.remote_execution_headers)
×
384
        store_headers = cast("dict[str, str]", bootstrap_options.remote_store_headers)
×
385
        parallelism = cast(int, bootstrap_options.process_execution_remote_parallelism)
×
386
        store_rpc_concurrency = cast(int, bootstrap_options.remote_store_rpc_concurrency)
×
387
        store_batch_load_enabled = cast(bool, bootstrap_options.remote_store_batch_load_enabled)
×
388
        cache_rpc_concurrency = cast(int, bootstrap_options.remote_cache_rpc_concurrency)
×
389
        execution_rpc_concurrency = cast(int, bootstrap_options.remote_execution_rpc_concurrency)
×
390
        execution_headers.update(token_header)
×
391
        store_headers.update(token_header)
×
392
        return cls(
×
393
            provider=provider,
394
            execution=execution,
395
            cache_read=cache_read,
396
            cache_write=cache_write,
397
            instance_name=instance_name,
398
            store_address=cls._normalize_address(store_address),
399
            execution_address=cls._normalize_address(execution_address),
400
            store_headers=store_headers,
401
            execution_headers=execution_headers,
402
            parallelism=parallelism,
403
            store_rpc_concurrency=store_rpc_concurrency,
404
            store_batch_load_enabled=store_batch_load_enabled,
405
            cache_rpc_concurrency=cache_rpc_concurrency,
406
            execution_rpc_concurrency=execution_rpc_concurrency,
407
        )
408

409
    @classmethod
2✔
410
    def from_options(
2✔
411
        cls,
412
        full_options: Options,
413
        env: CompleteEnvironmentVars,
414
        prior_result: AuthPluginResult | None = None,
415
        remote_auth_plugin_func: Callable | None = None,
416
    ) -> tuple[DynamicRemoteOptions, AuthPluginResult | None]:
417
        global_options = full_options.for_global_scope()
2✔
418
        execution = cast(bool, global_options.remote_execution)
2✔
419
        cache_read = cast(bool, global_options.remote_cache_read)
2✔
420
        cache_write = cast(bool, global_options.remote_cache_write)
2✔
421
        if not (execution or cache_read or cache_write):
2✔
422
            return cls.disabled(), None
2✔
423

424
        sources = {
×
425
            str(remote_auth_plugin_func): bool(remote_auth_plugin_func),
426
            "[GLOBAL].remote_oauth_bearer_token": bool(global_options.remote_oauth_bearer_token),
427
        }
428
        enabled_sources = [name for name, enabled in sources.items() if enabled]
×
429
        if len(enabled_sources) > 1:
×
430
            rendered = ", ".join(f"`{name}`" for name in enabled_sources)
×
431
            raise OptionsError(
×
432
                softwrap(
433
                    f"""
434
                    Multiple options are set that provide auth information: {rendered}.
435
                    This is not supported. Only one of those should be set.
436
                    """
437
                )
438
            )
439
        if global_options.remote_oauth_bearer_token:
×
440
            return cls._use_oauth_token(global_options), None
×
441
        if remote_auth_plugin_func is not None:
×
442
            return cls._use_auth_plugin(
×
443
                global_options,
444
                full_options=full_options,
445
                env=env,
446
                prior_result=prior_result,
447
                remote_auth_plugin_func=remote_auth_plugin_func,
448
            )
449
        return cls._use_no_auth(global_options), None
×
450

451
    @classmethod
2✔
452
    def _use_no_auth(cls, bootstrap_options: OptionValueContainer) -> DynamicRemoteOptions:
2✔
453
        provider = cast(RemoteProvider, bootstrap_options.remote_provider)
×
454
        execution = cast(bool, bootstrap_options.remote_execution)
×
455
        cache_read = cast(bool, bootstrap_options.remote_cache_read)
×
456
        cache_write = cast(bool, bootstrap_options.remote_cache_write)
×
457
        store_address = cast("str | None", bootstrap_options.remote_store_address)
×
458
        execution_address = cast("str | None", bootstrap_options.remote_execution_address)
×
459
        instance_name = cast("str | None", bootstrap_options.remote_instance_name)
×
460
        execution_headers = cast("dict[str, str]", bootstrap_options.remote_execution_headers)
×
461
        store_headers = cast("dict[str, str]", bootstrap_options.remote_store_headers)
×
462
        parallelism = cast(int, bootstrap_options.process_execution_remote_parallelism)
×
463
        store_rpc_concurrency = cast(int, bootstrap_options.remote_store_rpc_concurrency)
×
464
        store_batch_load_enabled = cast(bool, bootstrap_options.remote_store_batch_load_enabled)
×
465
        cache_rpc_concurrency = cast(int, bootstrap_options.remote_cache_rpc_concurrency)
×
466
        execution_rpc_concurrency = cast(int, bootstrap_options.remote_execution_rpc_concurrency)
×
467
        return cls(
×
468
            provider=provider,
469
            execution=execution,
470
            cache_read=cache_read,
471
            cache_write=cache_write,
472
            instance_name=instance_name,
473
            store_address=cls._normalize_address(store_address),
474
            execution_address=cls._normalize_address(execution_address),
475
            store_headers=store_headers,
476
            execution_headers=execution_headers,
477
            parallelism=parallelism,
478
            store_rpc_concurrency=store_rpc_concurrency,
479
            store_batch_load_enabled=store_batch_load_enabled,
480
            cache_rpc_concurrency=cache_rpc_concurrency,
481
            execution_rpc_concurrency=execution_rpc_concurrency,
482
        )
483

484
    @classmethod
2✔
485
    def _use_auth_plugin(
2✔
486
        cls,
487
        bootstrap_options: OptionValueContainer,
488
        full_options: Options,
489
        env: CompleteEnvironmentVars,
490
        prior_result: AuthPluginResult | None,
491
        remote_auth_plugin_func: Callable,
492
    ) -> tuple[DynamicRemoteOptions, AuthPluginResult | None]:
493
        provider = cast(RemoteProvider, bootstrap_options.remote_provider)
×
494
        execution = cast(bool, bootstrap_options.remote_execution)
×
495
        cache_read = cast(bool, bootstrap_options.remote_cache_read)
×
496
        cache_write = cast(bool, bootstrap_options.remote_cache_write)
×
497
        store_address = cast("str | None", bootstrap_options.remote_store_address)
×
498
        execution_address = cast("str | None", bootstrap_options.remote_execution_address)
×
499
        instance_name = cast("str | None", bootstrap_options.remote_instance_name)
×
500
        execution_headers = cast("dict[str, str]", bootstrap_options.remote_execution_headers)
×
501
        store_headers = cast("dict[str, str]", bootstrap_options.remote_store_headers)
×
502
        parallelism = cast(int, bootstrap_options.process_execution_remote_parallelism)
×
503
        store_rpc_concurrency = cast(int, bootstrap_options.remote_store_rpc_concurrency)
×
504
        store_batch_load_enabled = cast(bool, bootstrap_options.remote_store_batch_load_enabled)
×
505
        cache_rpc_concurrency = cast(int, bootstrap_options.remote_cache_rpc_concurrency)
×
506
        execution_rpc_concurrency = cast(int, bootstrap_options.remote_execution_rpc_concurrency)
×
507
        auth_plugin_result = cast(
×
508
            AuthPluginResult,
509
            remote_auth_plugin_func(
510
                initial_execution_headers=execution_headers,
511
                initial_store_headers=store_headers,
512
                options=full_options,
513
                env=dict(env),
514
                prior_result=prior_result,
515
            ),
516
        )
517
        plugin_name = (
×
518
            auth_plugin_result.plugin_name
519
            or f"{remote_auth_plugin_func.__module__}.{remote_auth_plugin_func.__name__}"
520
        )
521
        if not auth_plugin_result.is_available:
×
522
            # NB: This is debug because we expect plugins to log more informative messages.
523
            logger.debug(
×
524
                f"Disabling remote caching and remote execution because authentication was not available via the plugin {plugin_name} (from `[GLOBAL].remote_auth_plugin`)."
525
            )
526
            return cls.disabled(), None
×
527

528
        logger.debug(
×
529
            f"Remote auth plugin `{plugin_name}` succeeded. Remote caching/execution will be attempted."
530
        )
531
        provider = auth_plugin_result.provider
×
532
        execution_headers = auth_plugin_result.execution_headers
×
533
        store_headers = auth_plugin_result.store_headers
×
534
        plugin_provided_opt_log = "Setting `[GLOBAL].remote_{opt}` is not needed and will be ignored since it is provided by the auth plugin: {plugin_name}."
×
535
        if auth_plugin_result.instance_name is not None:
×
536
            if instance_name is not None:
×
537
                logger.warning(
×
538
                    plugin_provided_opt_log.format(opt="instance_name", plugin_name=plugin_name)
539
                )
540
            instance_name = auth_plugin_result.instance_name
×
541
        if auth_plugin_result.store_address is not None:
×
542
            if store_address is not None:
×
543
                logger.warning(
×
544
                    plugin_provided_opt_log.format(opt="store_address", plugin_name=plugin_name)
545
                )
546
            store_address = auth_plugin_result.store_address
×
547
        if auth_plugin_result.execution_address is not None:
×
548
            if execution_address is not None:
×
549
                logger.warning(
×
550
                    plugin_provided_opt_log.format(opt="execution_address", plugin_name=plugin_name)
551
                )
552
            execution_address = auth_plugin_result.execution_address
×
553

554
        opts = cls(
×
555
            provider=provider,
556
            execution=execution,
557
            cache_read=cache_read,
558
            cache_write=cache_write,
559
            instance_name=instance_name,
560
            store_address=cls._normalize_address(store_address),
561
            execution_address=cls._normalize_address(execution_address),
562
            store_headers=store_headers,
563
            execution_headers=execution_headers,
564
            parallelism=parallelism,
565
            store_rpc_concurrency=store_rpc_concurrency,
566
            store_batch_load_enabled=store_batch_load_enabled,
567
            cache_rpc_concurrency=cache_rpc_concurrency,
568
            execution_rpc_concurrency=execution_rpc_concurrency,
569
        )
570
        return opts, auth_plugin_result
×
571

572
    @classmethod
2✔
573
    def _normalize_address(cls, address: str | None) -> str | None:
2✔
574
        # NB: Tonic expects the schemes `http` and `https`, even though they are gRPC requests.
575
        # We validate that users set `grpc` and `grpcs` in the options system / plugin for clarity,
576
        # but then normalize to `http`/`https`.
577
        # TODO: move this logic into the actual remote providers
578
        return re.sub(r"^grpc", "http", address) if address else None
×
579

580

581
@dataclass(frozen=True)
2✔
582
class ExecutionOptions:
2✔
583
    """A collection of all options related to (remote) execution of processes.
584

585
    TODO: These options should move to a Subsystem once we add support for "bootstrap" Subsystems (ie,
586
    allowing Subsystems to be consumed before the Scheduler has been created).
587
    """
588

589
    remote_provider: RemoteProvider
2✔
590

591
    remote_execution: bool
2✔
592
    remote_cache_read: bool
2✔
593
    remote_cache_write: bool
2✔
594

595
    remote_instance_name: str | None
2✔
596
    remote_ca_certs_path: str | None
2✔
597
    remote_client_certs_path: str | None
2✔
598
    remote_client_key_path: str | None
2✔
599

600
    use_sandboxer: bool
2✔
601
    local_cache: bool
2✔
602
    process_execution_local_parallelism: int
2✔
603
    process_execution_local_enable_nailgun: bool
2✔
604
    process_execution_remote_parallelism: int
2✔
605
    process_execution_cache_namespace: str | None
2✔
606
    process_execution_graceful_shutdown_timeout: int
2✔
607
    cache_content_behavior: CacheContentBehavior
2✔
608

609
    process_total_child_memory_usage: int | None
2✔
610
    process_per_child_memory_usage: int
2✔
611

612
    remote_store_address: str | None
2✔
613
    remote_store_headers: dict[str, str]
2✔
614
    remote_store_chunk_bytes: Any
2✔
615
    remote_store_rpc_retries: int
2✔
616
    remote_store_rpc_concurrency: int
2✔
617
    remote_store_batch_api_size_limit: int
2✔
618
    remote_store_batch_load_enabled: bool
2✔
619
    remote_store_rpc_timeout_millis: int
2✔
620

621
    remote_cache_warnings: RemoteCacheWarningsBehavior
2✔
622
    remote_cache_rpc_concurrency: int
2✔
623
    remote_cache_rpc_timeout_millis: int
2✔
624

625
    remote_execution_address: str | None
2✔
626
    remote_execution_headers: dict[str, str]
2✔
627
    remote_execution_overall_deadline_secs: int
2✔
628
    remote_execution_rpc_concurrency: int
2✔
629

630
    remote_execution_append_only_caches_base_path: str | None
2✔
631

632
    @classmethod
2✔
633
    def from_options(
2✔
634
        cls,
635
        bootstrap_options: OptionValueContainer,
636
        dynamic_remote_options: DynamicRemoteOptions,
637
    ) -> ExecutionOptions:
638
        return cls(
2✔
639
            remote_provider=dynamic_remote_options.provider,
640
            # Remote execution strategy.
641
            remote_execution=dynamic_remote_options.execution,
642
            remote_cache_read=dynamic_remote_options.cache_read,
643
            remote_cache_write=dynamic_remote_options.cache_write,
644
            # General remote setup.
645
            remote_instance_name=dynamic_remote_options.instance_name,
646
            remote_ca_certs_path=bootstrap_options.remote_ca_certs_path,
647
            remote_client_certs_path=bootstrap_options.remote_client_certs_path,
648
            remote_client_key_path=bootstrap_options.remote_client_key_path,
649
            # Process execution setup.
650
            use_sandboxer=bootstrap_options.sandboxer,
651
            local_cache=bootstrap_options.local_cache,
652
            process_execution_local_parallelism=bootstrap_options.process_execution_local_parallelism,
653
            process_execution_remote_parallelism=dynamic_remote_options.parallelism,
654
            process_execution_cache_namespace=bootstrap_options.process_execution_cache_namespace,
655
            process_execution_graceful_shutdown_timeout=bootstrap_options.process_execution_graceful_shutdown_timeout,
656
            process_execution_local_enable_nailgun=bootstrap_options.process_execution_local_enable_nailgun,
657
            cache_content_behavior=bootstrap_options.cache_content_behavior,
658
            process_total_child_memory_usage=bootstrap_options.process_total_child_memory_usage,
659
            process_per_child_memory_usage=bootstrap_options.process_per_child_memory_usage,
660
            # Remote store setup.
661
            remote_store_address=dynamic_remote_options.store_address,
662
            remote_store_headers=cls.with_user_agent(dynamic_remote_options.store_headers),
663
            remote_store_chunk_bytes=bootstrap_options.remote_store_chunk_bytes,
664
            remote_store_rpc_retries=bootstrap_options.remote_store_rpc_retries,
665
            remote_store_rpc_concurrency=dynamic_remote_options.store_rpc_concurrency,
666
            remote_store_batch_api_size_limit=bootstrap_options.remote_store_batch_api_size_limit,
667
            remote_store_batch_load_enabled=bootstrap_options.remote_store_batch_load_enabled,
668
            remote_store_rpc_timeout_millis=bootstrap_options.remote_store_rpc_timeout_millis,
669
            # Remote cache setup.
670
            remote_cache_warnings=bootstrap_options.remote_cache_warnings,
671
            remote_cache_rpc_concurrency=dynamic_remote_options.cache_rpc_concurrency,
672
            remote_cache_rpc_timeout_millis=bootstrap_options.remote_cache_rpc_timeout_millis,
673
            # Remote execution setup.
674
            remote_execution_address=dynamic_remote_options.execution_address,
675
            remote_execution_headers=cls.with_user_agent(dynamic_remote_options.execution_headers),
676
            remote_execution_overall_deadline_secs=bootstrap_options.remote_execution_overall_deadline_secs,
677
            remote_execution_rpc_concurrency=dynamic_remote_options.execution_rpc_concurrency,
678
            remote_execution_append_only_caches_base_path=bootstrap_options.remote_execution_append_only_caches_base_path,
679
        )
680

681
    @classmethod
2✔
682
    def with_user_agent(cls, headers: dict[str, str]) -> dict[str, str]:
2✔
683
        has_user_agent = any(k.lower() == "user-agent" for k in headers.keys())
2✔
684
        if has_user_agent:
2✔
685
            return headers
×
686
        return {"user-agent": f"pants/{VERSION}"} | headers
2✔
687

688

689
@dataclass(frozen=True)
2✔
690
class LocalStoreOptions:
2✔
691
    """A collection of all options related to the local store.
692

693
    TODO: These options should move to a Subsystem once we add support for "bootstrap" Subsystems (ie,
694
    allowing Subsystems to be consumed before the Scheduler has been created).
695
    """
696

697
    store_dir: str = os.path.join(get_pants_cachedir(), "lmdb_store")
2✔
698
    processes_max_size_bytes: int = 16 * GIGABYTES
2✔
699
    files_max_size_bytes: int = 256 * GIGABYTES
2✔
700
    directories_max_size_bytes: int = 16 * GIGABYTES
2✔
701
    shard_count: int = 16
2✔
702

703
    def target_total_size_bytes(self) -> int:
2✔
704
        """Returns the target total size of all of the stores.
705

706
        The `max_size` values are caps on the total size of each store: the "target" size
707
        is the size that garbage collection will attempt to shrink the stores to each time
708
        it runs.
709

710
        NB: This value is not currently configurable, but that could be desirable in the future.
711
        """
712
        max_total_size_bytes = (
×
713
            self.processes_max_size_bytes
714
            + self.files_max_size_bytes
715
            + self.directories_max_size_bytes
716
        )
717
        return max_total_size_bytes // 10
×
718

719
    @classmethod
2✔
720
    def from_options(cls, options: OptionValueContainer) -> LocalStoreOptions:
2✔
721
        return cls(
2✔
722
            store_dir=str(Path(options.local_store_dir).resolve()),
723
            processes_max_size_bytes=options.local_store_processes_max_size_bytes,
724
            files_max_size_bytes=options.local_store_files_max_size_bytes,
725
            directories_max_size_bytes=options.local_store_directories_max_size_bytes,
726
            shard_count=options.local_store_shard_count,
727
        )
728

729

730
_PER_CHILD_MEMORY_USAGE = "512MiB"
2✔
731

732

733
DEFAULT_EXECUTION_OPTIONS = ExecutionOptions(
2✔
734
    remote_provider=RemoteProvider.reapi,
735
    # Remote execution strategy.
736
    remote_execution=False,
737
    remote_cache_read=False,
738
    remote_cache_write=False,
739
    # General remote setup.
740
    remote_instance_name=None,
741
    remote_ca_certs_path=None,
742
    remote_client_certs_path=None,
743
    remote_client_key_path=None,
744
    # Process execution setup.
745
    process_total_child_memory_usage=None,
746
    process_per_child_memory_usage=memory_size(_PER_CHILD_MEMORY_USAGE),
747
    process_execution_local_parallelism=CPU_COUNT,
748
    process_execution_remote_parallelism=128,
749
    process_execution_cache_namespace=None,
750
    use_sandboxer=False,
751
    local_cache=True,
752
    cache_content_behavior=CacheContentBehavior.fetch,
753
    process_execution_local_enable_nailgun=True,
754
    process_execution_graceful_shutdown_timeout=3,
755
    # Remote store setup.
756
    remote_store_address=None,
757
    remote_store_headers={},
758
    remote_store_chunk_bytes=1024 * 1024,
759
    remote_store_rpc_retries=2,
760
    remote_store_rpc_concurrency=128,
761
    remote_store_batch_api_size_limit=4194304,
762
    remote_store_rpc_timeout_millis=30000,
763
    remote_store_batch_load_enabled=False,
764
    # Remote cache setup.
765
    remote_cache_warnings=RemoteCacheWarningsBehavior.backoff,
766
    remote_cache_rpc_concurrency=128,
767
    remote_cache_rpc_timeout_millis=1500,
768
    # Remote execution setup.
769
    remote_execution_address=None,
770
    remote_execution_headers={},
771
    remote_execution_overall_deadline_secs=60 * 60,  # one hour
772
    remote_execution_rpc_concurrency=128,
773
    remote_execution_append_only_caches_base_path=None,
774
)
775

776
DEFAULT_LOCAL_STORE_OPTIONS = LocalStoreOptions()
2✔
777

778

779
class LogLevelOption(EnumOption[LogLevel, LogLevel]):
2✔
780
    """The `--level` option.
781

782
    This is a dedicated class because it's the only option where we allow both the short flag `-l`
783
    and the long flag `--level`.
784
    """
785

786
    def __new__(cls) -> LogLevelOption:
2✔
787
        self = super().__new__(
2✔
788
            cls,
789
            default=LogLevel.INFO,
790
            daemon=True,
791
            help="Set the logging level.",
792
        )
793
        self._flag_names = ("-l", "--level")
2✔
794
        return self  # type: ignore[return-value]
2✔
795

796

797
class BootstrapOptions:
2✔
798
    """The set of options necessary to create a Scheduler.
799

800
    If an option is not consumed during creation of a Scheduler, it should be a property of
801
    GlobalOptions instead. Either way these options are injected into the GlobalOptions, which is
802
    how they should be accessed (as normal global-scope options).
803

804
    Their status as "bootstrap options" is only pertinent during option registration.
805
    """
806

807
    _default_distdir_name = "dist"
2✔
808
    _default_rel_distdir = f"/{_default_distdir_name}/"
2✔
809

810
    backend_packages = StrListOption(
2✔
811
        advanced=True,
812
        help=softwrap(
813
            """
814
            Register functionality from these backends.
815

816
            The backend packages must be present on the PYTHONPATH, typically because they are in
817
            the Pants core dist, in a plugin dist, or available as sources in the repo.
818
            """
819
        ),
820
    )
821
    plugins = StrListOption(
2✔
822
        advanced=True,
823
        help=softwrap(
824
            """
825
            Allow backends to be loaded from these plugins (usually released through PyPI).
826
            The default backends for each plugin will be loaded automatically. Other backends
827
            in a plugin can be loaded by listing them in `backend_packages` in the
828
            `[GLOBAL]` scope.
829
            """
830
        ),
831
    )
832
    plugins_force_resolve = BoolOption(
2✔
833
        advanced=True,
834
        default=False,
835
        help="Re-resolve plugins, even if previously resolved.",
836
    )
837
    level = LogLevelOption()
2✔
838
    show_log_target = BoolOption(
2✔
839
        default=False,
840
        daemon=True,
841
        advanced=True,
842
        help=softwrap(
843
            """
844
            Display the target where a log message originates in that log message's output.
845
            This can be helpful when paired with `--log-levels-by-target`.
846
            """
847
        ),
848
    )
849
    log_levels_by_target = DictOption[str](
2✔
850
        # TODO: While we would like this option to be fingerprinted for the daemon, the Rust side
851
        # option parser does not support dict options. See #19832.
852
        # daemon=True,
853
        advanced=True,
854
        help=softwrap(
855
            """
856
            Set a more specific logging level for one or more logging targets. The names of
857
            logging targets are specified in log strings when the --show-log-target option is set.
858
            The logging levels are one of: "error", "warn", "info", "debug", "trace".
859
            All logging targets not specified here use the global log level set with `--level`. For example,
860
            you can set `--log-levels-by-target='{"workunit_store": "info", "pants.engine.rules": "warn"}'`.
861
            """
862
        ),
863
    )
864
    log_show_rust_3rdparty = BoolOption(
2✔
865
        default=False,
866
        daemon=True,
867
        advanced=True,
868
        help="Whether to show/hide logging done by 3rdparty Rust crates used by the Pants engine.",
869
    )
870
    ignore_warnings = StrListOption(
2✔
871
        daemon=True,
872
        advanced=True,
873
        help=softwrap(
874
            """
875
            Ignore logs and warnings matching these strings.
876

877
            Normally, Pants will look for literal matches from the start of the log/warning
878
            message, but you can prefix the ignore with `$regex$` for Pants to instead treat
879
            your string as a regex pattern. For example:
880

881
                ignore_warnings = [
882
                    "DEPRECATED: option 'config' in scope 'flake8' will be removed",
883
                    '$regex$:No files\\s*'
884
                ]
885
            """
886
        ),
887
    )
888
    pants_version = StrOption(
2✔
889
        advanced=True,
890
        default=pants_version(),
891
        default_help_repr="<pants_version>",
892
        daemon=True,
893
        help=softwrap(
894
            f"""
895
            Use this Pants version. Note that Pants only uses this to verify that you are
896
            using the requested version, as Pants cannot dynamically change the version it
897
            is using once the program is already running.
898

899
            If you use the `{bin_name()}` script from {doc_url("docs/getting-started/installing-pants")}, however, changing
900
            the value in your `pants.toml` will cause the new version to be installed and run automatically.
901

902
            Run `{bin_name()} --version` to check what is being used.
903
            """
904
        ),
905
    )
906
    pants_bin_name = StrOption(
2✔
907
        advanced=True,
908
        default="pants",
909
        help=softwrap(
910
            """
911
            The name of the script or binary used to invoke Pants.
912
            Useful when printing help messages.
913
            """
914
        ),
915
    )
916
    pants_workdir = StrOption(
2✔
917
        advanced=True,
918
        metavar="<dir>",
919
        default=lambda _: os.path.join(get_buildroot(), ".pants.d", "workdir"),
920
        daemon=True,
921
        help="Write intermediate logs and output files to this dir.",
922
    )
923
    pants_physical_workdir_base = StrOption(
2✔
924
        advanced=True,
925
        metavar="<dir>",
926
        default=None,
927
        daemon=True,
928
        help=softwrap(
929
            """
930
            When set, a base directory in which to store `--pants-workdir` contents.
931
            If this option is set, the workdir will be created as a symlink into a
932
            per-workspace subdirectory.
933
            """
934
        ),
935
    )
936
    pants_distdir = StrOption(
2✔
937
        advanced=True,
938
        metavar="<dir>",
939
        default=lambda _: os.path.join(get_buildroot(), "dist"),
940
        help="Write end products, such as the results of `pants package`, to this dir.",
941
    )
942
    pants_subprocessdir = StrOption(
2✔
943
        advanced=True,
944
        default=lambda _: os.path.join(get_buildroot(), ".pants.d", "pids"),
945
        daemon=True,
946
        help=softwrap(
947
            """
948
            The directory to use for tracking subprocess metadata. This should
949
            live outside of the dir used by `pants_workdir` to allow for tracking
950
            subprocesses that outlive the workdir data.
951
            """
952
        ),
953
    )
954
    pants_config_files = StrListOption(
2✔
955
        advanced=True,
956
        # NB: We don't fingerprint the list of config files, because the content of the config
957
        # files independently affects fingerprints.
958
        fingerprint=False,
959
        default=lambda _: [get_default_pants_config_file()],
960
        help=softwrap(
961
            """
962
            Paths to Pants config files. This may only be set through the environment variable
963
            `PANTS_CONFIG_FILES` and the command line argument `--pants-config-files`; it will
964
            be ignored if in a config file like `pants.toml`.
965
            """
966
        ),
967
    )
968
    pantsrc = BoolOption(
2✔
969
        advanced=True,
970
        default=True,
971
        # NB: See `--pants-config-files`.
972
        fingerprint=False,
973
        help="Use pantsrc files located at the paths specified in the global option `pantsrc_files`.",
974
    )
975
    pantsrc_files = StrListOption(
2✔
976
        advanced=True,
977
        metavar="<path>",
978
        # NB: See `--pants-config-files`.
979
        fingerprint=False,
980
        default=["/etc/pantsrc", "~/.pants.rc", ".pants.rc"],
981
        help="Override config with values from these files, using syntax matching that of `--pants-config-files`.",
982
    )
983
    pythonpath = StrListOption(
2✔
984
        advanced=True,
985
        help=softwrap(
986
            """
987
            Add these directories to PYTHONPATH to search for plugins. This does not impact the
988
            PYTHONPATH used by Pants when running your Python code.
989
            """
990
        ),
991
    )
992
    spec_files = StrListOption(
2✔
993
        # NB: We don't fingerprint spec files because the content of the files independently
994
        # affects fingerprints.
995
        fingerprint=False,
996
        help=softwrap(
997
            """
998
            Read additional specs (target addresses, files, and/or globs), one per line, from these
999
            files.
1000
            """
1001
        ),
1002
    )
1003
    verify_config = BoolOption(
2✔
1004
        default=True,
1005
        advanced=True,
1006
        help="Verify that all config file values correspond to known options.",
1007
    )
1008
    stats_record_option_scopes = StrListOption(
2✔
1009
        advanced=True,
1010
        default=["*"],
1011
        help=softwrap(
1012
            """
1013
            Option scopes to record in stats on run completion.
1014
            Options may be selected by joining the scope and the option with a ^ character,
1015
            i.e. to get option `pantsd` in the GLOBAL scope, you'd pass `GLOBAL^pantsd`.
1016
            Add a '*' to the list to capture all known scopes.
1017
            """
1018
        ),
1019
    )
1020
    pants_ignore = StrListOption(
2✔
1021
        advanced=True,
1022
        default=[".*/", _default_rel_distdir, "__pycache__", "!.semgrep/", "!.github/"],
1023
        help=softwrap(
1024
            """
1025
            Paths to ignore for all filesystem operations performed by pants
1026
            (e.g. BUILD file scanning, glob matching, etc).
1027

1028
            Patterns use the gitignore syntax (https://git-scm.com/docs/gitignore).
1029
            The `pants_distdir` and `pants_workdir` locations are automatically ignored.
1030

1031
            `pants_ignore` can be used in tandem with `pants_ignore_use_gitignore`; any rules
1032
            specified here are applied after rules specified in a .gitignore file.
1033
            """
1034
        ),
1035
    )
1036
    pants_ignore_use_gitignore = BoolOption(
2✔
1037
        advanced=True,
1038
        default=True,
1039
        help=softwrap(
1040
            """
1041
            Include patterns from `.gitignore`, `.git/info/exclude`, and the global gitignore
1042
            files in the option `[GLOBAL].pants_ignore`, which is used for Pants to ignore
1043
            filesystem operations on those patterns.
1044

1045
            Patterns from `[GLOBAL].pants_ignore` take precedence over these files' rules. For
1046
            example, you can use `!my_pattern` in `pants_ignore` to have Pants operate on files
1047
            that are gitignored.
1048

1049
            Warning: this does not yet support reading nested gitignore files.
1050
            """
1051
        ),
1052
    )
1053
    # These logging options are registered in the bootstrap phase so that plugins can log during
1054
    # registration and not so that their values can be interpolated in configs.
1055
    logdir = StrOption(
2✔
1056
        advanced=True,
1057
        default=None,
1058
        metavar="<dir>",
1059
        daemon=True,
1060
        help="Write logs to files under this directory.",
1061
    )
1062
    pantsd = BoolOption(
2✔
1063
        default=True,
1064
        daemon=True,
1065
        help=softwrap(
1066
            """
1067
            Enables use of the Pants daemon (pantsd). pantsd can significantly improve
1068
            runtime performance by lowering per-run startup cost, and by memoizing filesystem
1069
            operations and rule execution.
1070
            """
1071
        ),
1072
    )
1073
    enable_stack_trampoline = StrOption(
2✔
1074
        default=None,
1075
        daemon=True,
1076
        help=softwrap(
1077
            """ Enable `sys.activate_stack_trampoline` for Pants's own Python
1078
            code. This allows profilers like the Linux `perf` profiler to see
1079
            Python stack frames. Set to `perf` to enable the `perf`
1080
            trampoline.
1081

1082
            See https://docs.python.org/3/howto/perf_profiling.html
1083
            """
1084
        ),
1085
    )
1086
    sandboxer = BoolOption(
2✔
1087
        default=False,
1088
        daemon=False,
1089
        help=softwrap(
1090
            """
1091
            Enables use of the sandboxer process. The sandboxer materializes files into the sandbox
1092
            on behalf of the main process (either pantsd or the pants client process if running
1093
            without pantsd). This works around a well-known race condition when a multithreaded
1094
            program writes executable files and then spawns subprocesses to execute them, which
1095
            can lead to ETXTBSY errors.
1096

1097
            This is a new feature so it is off by default. In the future, once this is stable,
1098
            it will likely default to True.
1099
            """
1100
        ),
1101
    )
1102
    # Whether or not to make necessary arrangements to have concurrent runs in pants.
1103
    # In practice, this means that if this is set, a run will not even try to use pantsd.
1104
    # NB: Eventually, we would like to deprecate this flag in favor of making pantsd runs parallelizable.
1105
    concurrent = BoolOption(
2✔
1106
        default=False,
1107
        help=softwrap(
1108
            """
1109
            Enable concurrent runs of Pants. With this enabled, Pants will
1110
            start up all concurrent invocations (e.g. in other terminals) without pantsd.
1111
            As a result, enabling this option will increase the per-run startup cost, but
1112
            will not block subsequent invocations.
1113
            """
1114
        ),
1115
    )
1116

1117
    # NB: We really don't want this option to invalidate the daemon, because different clients might have
1118
    # different needs. For instance, an IDE might have a very long timeout because it only wants to refresh
1119
    # a project in the background, while a user might want a shorter timeout for interactivity.
1120
    pantsd_timeout_when_multiple_invocations = FloatOption(
2✔
1121
        advanced=True,
1122
        default=60.0,
1123
        help=softwrap(
1124
            """
1125
            The maximum amount of time to wait for the invocation to start until
1126
            raising a timeout exception.
1127
            Because pantsd currently does not support parallel runs,
1128
            any prior running Pants command must be finished for the current one to start.
1129
            To never timeout, use the value -1.
1130
            """
1131
        ),
1132
    )
1133
    pantsd_max_memory_usage = MemorySizeOption(
2✔
1134
        advanced=True,
1135
        default=memory_size("4GiB"),
1136
        default_help_repr="4GiB",
1137
        help=softwrap(
1138
            """
1139
            The maximum memory usage of the pantsd process.
1140

1141
            When the maximum memory is exceeded, the daemon will restart gracefully,
1142
            although all previous in-memory caching will be lost. Setting too low means that
1143
            you may miss out on some caching, whereas setting too high may over-consume
1144
            resources and may result in the operating system killing Pantsd due to memory
1145
            overconsumption (e.g. via the OOM killer).
1146

1147
            You can suffix with `GiB`, `MiB`, `KiB`, or `B` to indicate the unit, e.g.
1148
            `2GiB` or `2.12GiB`. A bare number will be in bytes.
1149

1150
            There is at most one pantsd process per workspace.
1151
            """
1152
        ),
1153
    )
1154

1155
    # These facilitate configuring the native engine.
1156
    print_stacktrace = BoolOption(
2✔
1157
        advanced=True,
1158
        default=False,
1159
        help="Print the full exception stack trace for any errors.",
1160
    )
1161
    engine_visualize_to = DirOption(
2✔
1162
        advanced=True,
1163
        default=None,
1164
        help=softwrap(
1165
            """
1166
            A directory to write execution and rule graphs to as `dot` files. The contents
1167
            of the directory will be overwritten if any filenames collide.
1168
            """
1169
        ),
1170
    )
1171
    # Pants Daemon options.
1172
    pantsd_nailgun_port = IntOption(
2✔
1173
        # TODO: The name "pailgun" is likely historical, and this should be renamed to "nailgun".
1174
        "--pantsd-pailgun-port",
1175
        advanced=True,
1176
        default=0,
1177
        daemon=True,
1178
        help="The port to bind the Pants nailgun server to. Defaults to a random port.",
1179
    )
1180
    pantsd_invalidation_globs = StrListOption(
2✔
1181
        advanced=True,
1182
        daemon=True,
1183
        help=softwrap(
1184
            """
1185
            Filesystem events matching any of these globs will trigger a daemon restart.
1186
            Pants's own code, plugins, and `--pants-config-files` are inherently invalidated.
1187
            """
1188
        ),
1189
    )
1190
    rule_threads_core = IntOption(
2✔
1191
        default=max(2, CPU_COUNT // 2),
1192
        default_help_repr="max(2, #cores/2)",
1193
        advanced=True,
1194
        help=softwrap(
1195
            """
1196
            The number of threads to keep active and ready to execute `@rule` logic (see
1197
            also: `--rule-threads-max`).
1198

1199
            Values less than 2 are not currently supported.
1200

1201
            This value is independent of the number of processes that may be spawned in
1202
            parallel locally (controlled by `--process-execution-local-parallelism`).
1203
            """
1204
        ),
1205
    )
1206
    rule_threads_max = IntOption(
2✔
1207
        default=None,
1208
        advanced=True,
1209
        help=softwrap(
1210
            """
1211
            The maximum number of threads to use to execute `@rule` logic. Defaults to
1212
            a small multiple of `--rule-threads-core`.
1213
            """
1214
        ),
1215
    )
1216
    cache_instructions = softwrap(
2✔
1217
        """
1218
        The path may be absolute or relative. If the directory is within the build root, be
1219
        sure to include it in `--pants-ignore`.
1220
        """
1221
    )
1222
    local_store_dir = StrOption(
2✔
1223
        advanced=True,
1224
        help=softwrap(
1225
            f"""
1226
            Directory to use for the local file store, which stores the results of
1227
            subprocesses run by Pants.
1228

1229
            {cache_instructions}
1230
            """
1231
        ),
1232
        # This default is also hard-coded into the engine's rust code in
1233
        # fs::Store::default_path so that tools using a Store outside of pants
1234
        # are likely to be able to use the same storage location.
1235
        default=DEFAULT_LOCAL_STORE_OPTIONS.store_dir,
1236
    )
1237
    local_store_shard_count = IntOption(
2✔
1238
        advanced=True,
1239
        help=softwrap(
1240
            """
1241
            The number of LMDB shards created for the local store. This setting also impacts
1242
            the maximum size of stored files: see `--local-store-files-max-size-bytes`
1243
            for more information.
1244

1245
            Because LMDB allows only one simultaneous writer per database, the store is split
1246
            into multiple shards to allow for more concurrent writers. The faster your disks
1247
            are, the fewer shards you are likely to need for performance.
1248

1249
            NB: After changing this value, you will likely want to manually clear the
1250
            `--local-store-dir` directory to clear the space used by old shard layouts.
1251
            """
1252
        ),
1253
        default=DEFAULT_LOCAL_STORE_OPTIONS.shard_count,
1254
    )
1255
    local_store_processes_max_size_bytes = IntOption(
2✔
1256
        advanced=True,
1257
        help=softwrap(
1258
            """
1259
            The maximum size in bytes of the local store containing process cache entries.
1260
            Stored below `--local-store-dir`.
1261
            """
1262
        ),
1263
        default=DEFAULT_LOCAL_STORE_OPTIONS.processes_max_size_bytes,
1264
    )
1265
    local_store_files_max_size_bytes = IntOption(
2✔
1266
        advanced=True,
1267
        help=softwrap(
1268
            """
1269
            The maximum size in bytes of the local store containing files.
1270
            Stored below `--local-store-dir`.
1271

1272
            NB: This size value bounds the total size of all files, but (due to sharding of the
1273
            store on disk) it also bounds the per-file size to (VALUE /
1274
            `--local-store-shard-count`).
1275

1276
            This value doesn't reflect space allocated on disk, or RAM allocated (it
1277
            may be reflected in VIRT but not RSS). However, the default is lower than you
1278
            might otherwise choose because macOS creates core dumps that include MMAP'd
1279
            pages, and setting this too high might cause core dumps to use an unreasonable
1280
            amount of disk if they are enabled.
1281
            """
1282
        ),
1283
        default=DEFAULT_LOCAL_STORE_OPTIONS.files_max_size_bytes,
1284
    )
1285
    local_store_directories_max_size_bytes = IntOption(
2✔
1286
        advanced=True,
1287
        help=softwrap(
1288
            """
1289
            The maximum size in bytes of the local store containing directories.
1290
            Stored below `--local-store-dir`.
1291
            """
1292
        ),
1293
        default=DEFAULT_LOCAL_STORE_OPTIONS.directories_max_size_bytes,
1294
    )
1295
    _named_caches_dir = StrOption(
2✔
1296
        advanced=True,
1297
        help=softwrap(
1298
            f"""
1299
            Directory to use for named global caches for tools and processes with trusted,
1300
            concurrency-safe caches.
1301

1302
            {cache_instructions}
1303
            """
1304
        ),
1305
        default=os.path.join(get_pants_cachedir(), "named_caches"),
1306
    )
1307
    local_execution_root_dir = StrOption(
2✔
1308
        advanced=True,
1309
        help=softwrap(
1310
            f"""
1311
            Directory to use for local process execution sandboxing.
1312

1313
            {cache_instructions}
1314
            """
1315
        ),
1316
        default=tempfile.gettempdir(),
1317
        default_help_repr="<tmp_dir>",
1318
    )
1319
    local_cache = BoolOption(
2✔
1320
        default=DEFAULT_EXECUTION_OPTIONS.local_cache,
1321
        help=softwrap(
1322
            """
1323
            Whether to cache process executions in a local cache persisted to disk at
1324
            `--local-store-dir`.
1325
            """
1326
        ),
1327
    )
1328
    cache_content_behavior = EnumOption(
2✔
1329
        advanced=True,
1330
        default=DEFAULT_EXECUTION_OPTIONS.cache_content_behavior,
1331
        help=softwrap(
1332
            """
1333
            Controls how the content of cache entries is handled during process execution.
1334

1335
            When using a remote cache, the `fetch` behavior will fetch remote cache content from the
1336
            remote store before considering the cache lookup a hit, while the `validate` behavior
1337
            will only validate (for either a local or remote cache) that the content exists, without
1338
            fetching it.
1339

1340
            The `defer` behavior, on the other hand, will neither fetch nor validate the cache
1341
            content before calling a cache hit a hit. This "defers" actually fetching the cache
1342
            entry until Pants needs it (which may be never).
1343

1344
            The `defer` mode is the most network efficient (because it will completely skip network
1345
            requests in many cases), followed by the `validate` mode (since it can still skip
1346
            fetching the content if no consumer ends up needing it). But both the `validate` and
1347
            `defer` modes rely on an experimental feature called "backtracking" to attempt to
1348
            recover if content later turns out to be missing (`validate` has a much narrower window
1349
            for backtracking though, since content would need to disappear between validation and
1350
            consumption: generally, within one `pantsd` session).
1351
            """
1352
        ),
1353
    )
1354
    ca_certs_path = StrOption(
2✔
1355
        advanced=True,
1356
        default=None,
1357
        help=softwrap(
1358
            f"""
1359
            Path to a file containing PEM-format CA certificates used for verifying secure
1360
            connections when downloading files required by a build.
1361

1362
            Even when using the `docker_environment` and `remote_environment` targets, this path
1363
            will be read from the local host, and those certs will be used in the environment.
1364

1365
            This option cannot be overridden via environment targets, so if you need a different
1366
            value than what the rest of your organization is using, override the value via an
1367
            environment variable, CLI argument, or `.pants.rc` file. See {doc_url("docs/using-pants/key-concepts/options")}.
1368
            """
1369
        ),
1370
    )
1371
    process_total_child_memory_usage = MemorySizeOption(
2✔
1372
        advanced=True,
1373
        default=None,
1374
        help=softwrap(
1375
            """
1376
            The maximum memory usage for all "pooled" child processes.
1377

1378
            When set, this value participates in precomputing the pool size of child processes
1379
            used by Pants (pooling is currently used only for the JVM). When not set, Pants will
1380
            default to spawning `2 * --process-execution-local-parallelism` pooled processes.
1381

1382
            A high value would result in a high number of child processes spawned, potentially
1383
            overconsuming your resources and triggering the OS' OOM killer. A low value would
1384
            mean a low number of child processes launched and therefore less parallelism for the
1385
            tasks that need those processes.
1386

1387
            If setting this value, consider also adjusting the value of the
1388
            `--process-per-child-memory-usage` option.
1389

1390
            You can suffix with `GiB`, `MiB`, `KiB`, or `B` to indicate the unit, e.g.
1391
            `2GiB` or `2.12GiB`. A bare number will be in bytes.
1392
            """
1393
        ),
1394
    )
1395
    process_per_child_memory_usage = MemorySizeOption(
2✔
1396
        advanced=True,
1397
        default=DEFAULT_EXECUTION_OPTIONS.process_per_child_memory_usage,
1398
        default_help_repr=_PER_CHILD_MEMORY_USAGE,
1399
        help=softwrap(
1400
            """
1401
            The default memory usage for a single "pooled" child process.
1402

1403
            Check the documentation for the `--process-total-child-memory-usage` for advice on
1404
            how to choose an appropriate value for this option.
1405

1406
            You can suffix with `GiB`, `MiB`, `KiB`, or `B` to indicate the unit, e.g.
1407
            `2GiB` or `2.12GiB`. A bare number will be in bytes.
1408
            """
1409
        ),
1410
    )
1411
    process_execution_local_parallelism = IntOption(
2✔
1412
        default=DEFAULT_EXECUTION_OPTIONS.process_execution_local_parallelism,
1413
        default_help_repr="#cores",
1414
        advanced=True,
1415
        help=softwrap(
1416
            """
1417
            Number of concurrent processes that may be executed locally.
1418

1419
            This value is independent of the number of threads that may be used to
1420
            execute the logic in `@rules` (controlled by `--rule-threads-core`).
1421
            """
1422
        ),
1423
    )
1424
    process_execution_remote_parallelism = IntOption(
2✔
1425
        default=DEFAULT_EXECUTION_OPTIONS.process_execution_remote_parallelism,
1426
        advanced=True,
1427
        help="Number of concurrent processes that may be executed remotely.",
1428
    )
1429
    process_execution_cache_namespace = StrOption(
2✔
1430
        advanced=True,
1431
        default=cast(str, DEFAULT_EXECUTION_OPTIONS.process_execution_cache_namespace),
1432
        help=softwrap(
1433
            """
1434
            The cache namespace for process execution.
1435
            Change this value to invalidate every artifact's execution, or to prevent
1436
            process cache entries from being (re)used for different use-cases or users.
1437
            """
1438
        ),
1439
    )
1440
    process_execution_local_enable_nailgun = BoolOption(
2✔
1441
        default=DEFAULT_EXECUTION_OPTIONS.process_execution_local_enable_nailgun,
1442
        help=softwrap(
1443
            """
1444
            Whether or not to use nailgun to run JVM requests that are marked as supporting nailgun.
1445
            Note that nailgun only works correctly on JDK <= 17 and must be disabled manually for
1446
            later versions.
1447
            """
1448
        ),
1449
        advanced=True,
1450
    )
1451
    process_execution_graceful_shutdown_timeout = IntOption(
2✔
1452
        default=DEFAULT_EXECUTION_OPTIONS.process_execution_graceful_shutdown_timeout,
1453
        help=softwrap(
1454
            f"""
1455
            The time in seconds to wait when gracefully shutting down an interactive process (such
1456
            as one opened using `{bin_name()} run`) before killing it.
1457
            """
1458
        ),
1459
        advanced=True,
1460
    )
1461
    session_end_tasks_timeout = FloatOption(
2✔
1462
        default=3.0,
1463
        help=softwrap(
1464
            """
1465
            The time in seconds to wait for still-running "session end" tasks to complete before finishing
1466
            completion of a Pants invocation. "Session end" tasks include, for example, writing data that was
1467
            generated during the applicable Pants invocation to a configured remote cache.
1468
            """
1469
        ),
1470
    )
1471

1472
    remote_provider = EnumOption(
2✔
1473
        default=RemoteProvider.reapi,
1474
        help=RemoteProvider.provider_help(),
1475
    )
1476

1477
    remote_execution = BoolOption(
2✔
1478
        default=DEFAULT_EXECUTION_OPTIONS.remote_execution,
1479
        help=softwrap(
1480
            """
1481
            Enables remote workers for increased parallelism. (Alpha)
1482

1483
            Alternatively, you can use `[GLOBAL].remote_cache_read` and `[GLOBAL].remote_cache_write` to still run
1484
            everything locally, but to use a remote cache.
1485
            """
1486
        ),
1487
    )
1488
    remote_cache_read = BoolOption(
2✔
1489
        default=DEFAULT_EXECUTION_OPTIONS.remote_cache_read,
1490
        help=softwrap(
1491
            """
1492
            Whether to enable reading from a remote cache.
1493

1494
            This cannot be used at the same time as `[GLOBAL].remote_execution`.
1495
            """
1496
        ),
1497
    )
1498
    remote_cache_write = BoolOption(
2✔
1499
        default=DEFAULT_EXECUTION_OPTIONS.remote_cache_write,
1500
        help=softwrap(
1501
            """
1502
            Whether to enable writing results to a remote cache.
1503

1504
            This cannot be used at the same time as `[GLOBAL].remote_execution`.
1505
            """
1506
        ),
1507
    )
1508
    # TODO: update all these remote_... option helps for the new support for non-REAPI schemes
1509
    remote_instance_name = StrOption(
2✔
1510
        default=None,
1511
        advanced=True,
1512
        help=softwrap(
1513
            """
1514
            Name of the remote instance to use by remote caching and remote execution.
1515

1516
            This is used by some remote servers for routing. Consult your remote server for
1517
            whether this should be set.
1518

1519
            You can also use a Pants plugin which provides remote authentication to dynamically
1520
            set this value.
1521
            """
1522
        ),
1523
    )
1524
    remote_ca_certs_path = StrOption(
2✔
1525
        default=None,
1526
        advanced=True,
1527
        help=softwrap(
1528
            """
1529
            Path to a PEM file containing CA certificates used for verifying secure
1530
            connections to `[GLOBAL].remote_execution_address` and `[GLOBAL].remote_store_address`.
1531

1532
            If unspecified, Pants will attempt to auto-discover root CA certificates when TLS
1533
            is enabled with remote execution and caching.
1534
            """
1535
        ),
1536
    )
1537
    remote_client_certs_path = StrOption(
2✔
1538
        default=None,
1539
        advanced=True,
1540
        help=softwrap(
1541
            """
1542
            Path to a PEM file containing client certificates used for verifying secure connections to
1543
            `[GLOBAL].remote_execution_address` and `[GLOBAL].remote_store_address` when using
1544
            client authentication (mTLS).
1545

1546
            If unspecified, will use regular TLS. Requires `remote_client_key_path` to also be
1547
            specified.
1548
            """
1549
        ),
1550
    )
1551

1552
    remote_client_key_path = StrOption(
2✔
1553
        default=None,
1554
        advanced=True,
1555
        help=softwrap(
1556
            """
1557
            Path to a PEM file containing a private key used for verifying secure connections to
1558
            `[GLOBAL].remote_execution_address` and `[GLOBAL].remote_store_address` when using
1559
            client authentication (mTLS).
1560

1561
            If unspecified, will use regular TLS. Requires `remote_client_certs_path` to also be
1562
            specified.
1563
            """
1564
        ),
1565
    )
1566

1567
    remote_oauth_bearer_token = StrOption(
2✔
1568
        default=None,
1569
        advanced=True,
1570
        help=softwrap(
1571
            f"""
1572
            An oauth token to use for gGRPC connections to
1573
            `[GLOBAL].remote_execution_address` and `[GLOBAL].remote_store_address`.
1574

1575
            If specified, Pants will add a header in the format `authorization: Bearer <token>`.
1576
            You can also manually add this header via `[GLOBAL].remote_execution_headers` and
1577
            `[GLOBAL].remote_store_headers`, or use `[GLOBAL].remote_auth_plugin` to provide a plugin to
1578
            dynamically set the relevant headers. Otherwise, no authorization will be performed.
1579

1580
            Recommendation: do not place a token directly in `pants.toml`, instead do one of: set
1581
            the token via the environment variable (`PANTS_REMOTE_OAUTH_BEARER_TOKEN`), CLI option
1582
            (`--remote-oauth-bearer-token`), or store the token in a file and set the option to
1583
            `"@/path/to/token.txt"` to [read the value from that
1584
            file]({doc_url("docs/using-pants/key-concepts/options#reading-individual-option-values-from-files")}).
1585
            """
1586
        ),
1587
    )
1588
    remote_store_address = StrOption(
2✔
1589
        advanced=True,
1590
        default=cast(str, DEFAULT_EXECUTION_OPTIONS.remote_store_address),
1591
        help=softwrap(
1592
            """
1593
            The URI of a server/entity used as a remote file store. The supported URIs depends on
1594
            the value of the `remote_provider` option.
1595
            """
1596
        ),
1597
    )
1598
    remote_store_headers = DictOption(
2✔
1599
        advanced=True,
1600
        default=DEFAULT_EXECUTION_OPTIONS.remote_store_headers,
1601
        help=softwrap(
1602
            """
1603
            Headers to set on remote store requests.
1604

1605
            Format: header=value. Pants may add additional headers.
1606

1607
            See `[GLOBAL].remote_execution_headers` as well.
1608
            """
1609
        ),
1610
        default_help_repr=repr(DEFAULT_EXECUTION_OPTIONS.remote_store_headers).replace(
1611
            VERSION, "<pants_version>"
1612
        ),
1613
    )
1614
    remote_store_chunk_bytes = IntOption(
2✔
1615
        advanced=True,
1616
        default=DEFAULT_EXECUTION_OPTIONS.remote_store_chunk_bytes,
1617
        help="Size in bytes of chunks transferred to/from the remote file store.",
1618
    )
1619
    remote_store_rpc_retries = IntOption(
2✔
1620
        advanced=True,
1621
        default=DEFAULT_EXECUTION_OPTIONS.remote_store_rpc_retries,
1622
        help="Number of times to retry any RPC to the remote store before giving up.",
1623
    )
1624
    remote_store_rpc_concurrency = IntOption(
2✔
1625
        advanced=True,
1626
        default=DEFAULT_EXECUTION_OPTIONS.remote_store_rpc_concurrency,
1627
        help="The number of concurrent requests allowed to the remote store service.",
1628
    )
1629
    remote_store_rpc_timeout_millis = IntOption(
2✔
1630
        advanced=True,
1631
        default=DEFAULT_EXECUTION_OPTIONS.remote_store_rpc_timeout_millis,
1632
        help="Timeout value for remote store RPCs (not including streaming requests) in milliseconds.",
1633
    )
1634
    remote_store_batch_api_size_limit = IntOption(
2✔
1635
        advanced=True,
1636
        default=DEFAULT_EXECUTION_OPTIONS.remote_store_batch_api_size_limit,
1637
        help="The maximum total size of blobs allowed to be sent in a single batch API call to the remote store.",
1638
    )
1639
    remote_store_batch_load_enabled = BoolOption(
2✔
1640
        default=DEFAULT_EXECUTION_OPTIONS.remote_store_batch_load_enabled,
1641
        advanced=True,
1642
        help="Whether to enable batch load requests to the remote store.",
1643
    )
1644
    remote_cache_warnings = EnumOption(
2✔
1645
        default=DEFAULT_EXECUTION_OPTIONS.remote_cache_warnings,
1646
        advanced=True,
1647
        help=softwrap(
1648
            """
1649
            How frequently to log remote cache failures at the `warn` log level.
1650

1651
            All errors not logged at the `warn` level will instead be logged at the
1652
            `debug` level.
1653
            """
1654
        ),
1655
    )
1656
    remote_cache_rpc_concurrency = IntOption(
2✔
1657
        advanced=True,
1658
        default=DEFAULT_EXECUTION_OPTIONS.remote_cache_rpc_concurrency,
1659
        help="The number of concurrent requests allowed to the remote cache service.",
1660
    )
1661
    remote_cache_rpc_timeout_millis = IntOption(
2✔
1662
        advanced=True,
1663
        default=DEFAULT_EXECUTION_OPTIONS.remote_cache_rpc_timeout_millis,
1664
        help="Timeout value for remote cache RPCs in milliseconds.",
1665
    )
1666
    remote_execution_address = StrOption(
2✔
1667
        advanced=True,
1668
        default=cast(str, DEFAULT_EXECUTION_OPTIONS.remote_execution_address),
1669
        help=softwrap(
1670
            """
1671
            The URI of a server/entity used as a remote execution scheduler. The supported URIs depends on
1672
            the value of the `remote_provider` option.
1673

1674
            You must also set `[GLOBAL].remote_store_address`, which will often be the same value.
1675
            """
1676
        ),
1677
    )
1678
    remote_execution_headers = DictOption(
2✔
1679
        advanced=True,
1680
        default=DEFAULT_EXECUTION_OPTIONS.remote_execution_headers,
1681
        help=softwrap(
1682
            """
1683
            Headers to set on remote execution requests. Format: header=value. Pants
1684
            may add additional headers.
1685

1686
            See `[GLOBAL].remote_store_headers` as well.
1687
            """
1688
        ),
1689
        default_help_repr=repr(DEFAULT_EXECUTION_OPTIONS.remote_execution_headers).replace(
1690
            VERSION, "<pants_version>"
1691
        ),
1692
    )
1693
    remote_execution_overall_deadline_secs = IntOption(
2✔
1694
        default=DEFAULT_EXECUTION_OPTIONS.remote_execution_overall_deadline_secs,
1695
        advanced=True,
1696
        help="Overall timeout in seconds for each remote execution request from time of submission",
1697
    )
1698
    remote_execution_rpc_concurrency = IntOption(
2✔
1699
        advanced=True,
1700
        default=DEFAULT_EXECUTION_OPTIONS.remote_execution_rpc_concurrency,
1701
        help="The number of concurrent requests allowed to the remote execution service.",
1702
    )
1703
    remote_execution_append_only_caches_base_path = StrOption(
2✔
1704
        default=None,
1705
        advanced=True,
1706
        help=softwrap(
1707
            """
1708
            Sets the base path to use when setting up an append-only cache for a process running remotely.
1709
            If this option is not set, then append-only caches will not be used with remote execution.
1710
            The option should be set to the absolute path of a writable directory in the remote execution
1711
            environment where Pants can create append-only caches for use with remotely executing processes.
1712
            """
1713
        ),
1714
    )
1715
    watch_filesystem = BoolOption(
2✔
1716
        default=True,
1717
        advanced=True,
1718
        help=softwrap(
1719
            """
1720
            Set to False if Pants should not watch the filesystem for changes. `pantsd` or `loop`
1721
            may not be enabled.
1722
            """
1723
        ),
1724
    )
1725
    _file_downloads_retry_delay = FloatOption(
2✔
1726
        default=0.2,
1727
        advanced=True,
1728
        help=softwrap(
1729
            """
1730
            When Pants downloads files (for example, for the `http_source` source), Pants will retry the download
1731
            if a "retryable" error occurs. Between each attempt, Pants will delay a random amount of time using an
1732
            exponential backoff algorithm.
1733

1734
            This option sets the "base" duration in seconds used for calculating the retry delay.
1735
            """
1736
        ),
1737
    )
1738
    _file_downloads_max_attempts = IntOption(
2✔
1739
        default=4,
1740
        advanced=True,
1741
        help=softwrap(
1742
            """
1743
            When Pants downloads files (for example, for the `http_source` source), Pants will retry the download
1744
            if a "retryable" error occurs.
1745

1746
            This option sets the maximum number of attempts Pants will make to try to download the file before giving up
1747
            with an error.
1748
            """
1749
        ),
1750
    )
1751

1752
    @property
2✔
1753
    def file_downloads_retry_delay(self) -> timedelta:
2✔
1754
        value = self._file_downloads_retry_delay
2✔
1755
        if value <= 0.0:
2✔
1756
            raise ValueError(
×
1757
                f"Global option `--file-downloads-retry-delay` must a positive number, got {value}"
1758
            )
1759
        return timedelta(seconds=value)
2✔
1760

1761
    @property
2✔
1762
    def file_downloads_max_attempts(self) -> int:
2✔
1763
        value = self._file_downloads_max_attempts
2✔
1764
        if value < 1:
2✔
1765
            raise ValueError(
×
1766
                f"Global option `--file-downloads-max-attempts` must be at least 1, got {value}"
1767
            )
1768
        return value
2✔
1769

1770
    allow_deprecated_macos_versions = StrListOption(
2✔
1771
        default=[],
1772
        advanced=True,
1773
        help=softwrap(
1774
            f"""
1775
            Silence warnings/errors about running Pants on these versions of macOS. Pants only supports
1776
            recent versions of macOS. You can try running on older versions, but it may or may not work.
1777

1778
            If you have questions or concerns about this, please reach out to us at
1779
            {doc_url("community/getting-help")}.
1780
            """
1781
        ),
1782
    )
1783

1784
    @classmethod
2✔
1785
    def validate_instance(cls, opts: OptionValueContainer):
2✔
1786
        """Validates an instance of global options for cases that are not prohibited via
1787
        registration.
1788

1789
        For example: mutually exclusive options may be registered by passing a `mutually_exclusive_group`,
1790
        but when multiple flags must be specified together, it can be necessary to specify post-parse
1791
        checks.
1792

1793
        Raises pants.option.errors.OptionsError on validation failure.
1794
        """
1795
        if opts.pants_version != pants_version():
2✔
1796
            raise BuildConfigurationError(
×
1797
                softwrap(
1798
                    f"""
1799
                        Version mismatch: Requested version was {opts.pants_version},
1800
                        our version is {pants_version()}.
1801
                        """
1802
                )
1803
            )
1804

1805
        if opts.rule_threads_core < 2:
2✔
1806
            # TODO: This is a defense against deadlocks due to #11329: we only run one `@goal_rule`
1807
            # at a time, and a `@goal_rule` will only block one thread.
1808
            raise OptionsError(
×
1809
                softwrap(
1810
                    f"""
1811
                    --rule-threads-core values less than 2 are not supported, but it was set to
1812
                    {opts.rule_threads_core}.
1813
                    """
1814
                )
1815
            )
1816

1817
        if (
2✔
1818
            opts.process_total_child_memory_usage is not None
1819
            and opts.process_total_child_memory_usage < opts.process_per_child_memory_usage
1820
        ):
1821
            raise OptionsError(
×
1822
                softwrap(
1823
                    f"""
1824
                    Nailgun pool can not be initialised as the total amount of memory allowed is \
1825
                    smaller than the memory allocation for a single child process.
1826

1827
                    - total child process memory allowed: {fmt_memory_size(opts.process_total_child_memory_usage)}
1828

1829
                    - default child process memory: {fmt_memory_size(opts.process_per_child_memory_usage)}
1830
                    """
1831
                )
1832
            )
1833

1834
        # TODO: --loop is not a bootstrap option, so it probably shouldn't be referenced here.
1835
        #   But we don't want the rest of this validation code in GlobalOptions, as this would
1836
        #   cause an import cycle.
1837
        if not opts.watch_filesystem and (opts.pantsd or opts.loop):
2✔
1838
            raise OptionsError(
×
1839
                softwrap(
1840
                    """
1841
                    The `--no-watch-filesystem` option may not be set if
1842
                    `--pantsd` or `--loop` is set.
1843
                    """
1844
                )
1845
            )
1846

1847
        provider_source = "the `[GLOBAL].remote_provider` option"
2✔
1848
        if opts.remote_execution_address:
2✔
1849
            address_source = "the `[GLOBAL].remote_execution_address` option"
×
1850
            opts.remote_provider.validate_execution_supported(
×
1851
                provider_source=provider_source, execution_implied_by=address_source
1852
            )
1853
            opts.remote_provider.validate_address(
×
1854
                opts.remote_execution_address,
1855
                address_source=address_source,
1856
                provider_source=provider_source,
1857
            )
1858
        if opts.remote_store_address:
2✔
1859
            opts.remote_provider.validate_address(
×
1860
                opts.remote_store_address,
1861
                address_source="the `[GLOBAL].remote_store_address` option",
1862
                provider_source=provider_source,
1863
            )
1864

1865
        # Ensure that remote headers are ASCII.
1866
        def validate_remote_headers(opt_name: str) -> None:
2✔
1867
            command_line_opt_name = f"--{opt_name.replace('_', '-')}"
2✔
1868
            for k, v in getattr(opts, opt_name).items():
2✔
1869
                if not k.isascii():
×
1870
                    raise OptionsError(
×
1871
                        softwrap(
1872
                            f"""
1873
                            All values in `{command_line_opt_name}` must be ASCII, but the key
1874
                            in `{k}: {v}` has non-ASCII characters.
1875
                            """
1876
                        )
1877
                    )
1878
                if not v.isascii():
×
1879
                    raise OptionsError(
×
1880
                        softwrap(
1881
                            f"""
1882
                            All values in `{command_line_opt_name}` must be ASCII, but the value in
1883
                            `{k}: {v}` has non-ASCII characters.
1884
                            """
1885
                        )
1886
                    )
1887

1888
        validate_remote_headers("remote_execution_headers")
2✔
1889
        validate_remote_headers("remote_store_headers")
2✔
1890

1891
        is_remote_client_key_set = opts.remote_client_key_path is not None
2✔
1892
        is_remote_client_certs_set = opts.remote_client_certs_path is not None
2✔
1893
        if is_remote_client_key_set != is_remote_client_certs_set:
2✔
1894
            raise OptionsError(
×
1895
                softwrap(
1896
                    """
1897
                    `--remote-client-key-path` and `--remote-client-certs-path` must be specified
1898
                    together.
1899
                    """
1900
                )
1901
            )
1902

1903
        illegal_build_ignores = [i for i in opts.build_ignore if i.startswith("!")]
2✔
1904
        if illegal_build_ignores:
2✔
1905
            raise OptionsError(
×
1906
                softwrap(
1907
                    f"""
1908
                    The `--build-ignore` option does not support negated globs, but was
1909
                    given: {illegal_build_ignores}.
1910
                    """
1911
                )
1912
            )
1913

1914
    @staticmethod
2✔
1915
    def maybe_enable_stack_trampoline(opts: OptionValueContainer) -> None:
2✔
1916
        if sys.platform == "linux" and opts.enable_stack_trampoline is not None:
×
1917
            sys.activate_stack_trampoline(opts.enable_stack_trampoline)
×
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

© 2026 Coveralls, Inc