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

pantsbuild / pants / 18812500213

26 Oct 2025 03:42AM UTC coverage: 80.284% (+0.005%) from 80.279%
18812500213

Pull #22804

github

web-flow
Merge 2a56fdb46 into 4834308dc
Pull Request #22804: test_shell_command: use correct default cache scope for a test's environment

29 of 31 new or added lines in 2 files covered. (93.55%)

1314 existing lines in 64 files now uncovered.

77900 of 97030 relevant lines covered (80.28%)

3.35 hits per line

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

95.91
/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
12✔
5

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

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

48
logger = logging.getLogger(__name__)
12✔
49

50

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

55

56
MEGABYTES = 1_000_000
12✔
57
GIGABYTES = 1_000 * MEGABYTES
12✔
58

59

60
_G = TypeVar("_G", bound="_GlobMatchErrorBehaviorOptionBase")
12✔
61

62

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

66
    reapi = "reapi"
12✔
67
    experimental_file = "experimental-file"
12✔
68
    experimental_github_actions_cache = "experimental-github-actions-cache"
12✔
69

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

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

81
        assert_never(self)
×
82

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

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

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

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

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

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

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

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

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

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

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

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

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

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

164
                Supported values:
165

166
                {list_items}
167
                """
168
            )
169

170
        return renderer
12✔
171

172

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

179
    error_behavior: GlobMatchErrorBehavior
12✔
180

181
    @classmethod
12✔
182
    def ignore(cls: type[_G]) -> _G:
12✔
183
        return cls(GlobMatchErrorBehavior.ignore)
1✔
184

185
    @classmethod
12✔
186
    def warn(cls: type[_G]) -> _G:
12✔
187
        return cls(GlobMatchErrorBehavior.warn)
1✔
188

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

193

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

197

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

201

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

205

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

213

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

220

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

226

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

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

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

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

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

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

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

274
    @property
12✔
275
    def is_available(self) -> bool:
12✔
276
        return self.state == AuthPluginState.OK
1✔
277

278

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

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

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

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

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

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

365
    @classmethod
12✔
366
    def _use_oauth_token(cls, bootstrap_options: OptionValueContainer) -> DynamicRemoteOptions:
12✔
367
        oauth_token = bootstrap_options.remote_oauth_bearer_token
1✔
368

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

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

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

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

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

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

527
        logger.debug(
1✔
528
            f"Remote auth plugin `{plugin_name}` succeeded. Remote caching/execution will be attempted."
529
        )
530
        provider = auth_plugin_result.provider
1✔
531
        execution_headers = auth_plugin_result.execution_headers
1✔
532
        store_headers = auth_plugin_result.store_headers
1✔
533
        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}."
1✔
534
        if auth_plugin_result.instance_name is not None:
1✔
535
            if instance_name is not None:
1✔
536
                logger.warning(
1✔
537
                    plugin_provided_opt_log.format(opt="instance_name", plugin_name=plugin_name)
538
                )
539
            instance_name = auth_plugin_result.instance_name
1✔
540
        if auth_plugin_result.store_address is not None:
1✔
541
            if store_address is not None:
1✔
542
                logger.warning(
1✔
543
                    plugin_provided_opt_log.format(opt="store_address", plugin_name=plugin_name)
544
                )
545
            store_address = auth_plugin_result.store_address
1✔
546
        if auth_plugin_result.execution_address is not None:
1✔
547
            if execution_address is not None:
1✔
548
                logger.warning(
1✔
549
                    plugin_provided_opt_log.format(opt="execution_address", plugin_name=plugin_name)
550
                )
551
            execution_address = auth_plugin_result.execution_address
1✔
552

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

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

579

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

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

588
    remote_provider: RemoteProvider
12✔
589

590
    remote_execution: bool
12✔
591
    remote_cache_read: bool
12✔
592
    remote_cache_write: bool
12✔
593

594
    remote_instance_name: str | None
12✔
595
    remote_ca_certs_path: str | None
12✔
596
    remote_client_certs_path: str | None
12✔
597
    remote_client_key_path: str | None
12✔
598

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

608
    process_total_child_memory_usage: int | None
12✔
609
    process_per_child_memory_usage: int
12✔
610

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

620
    remote_cache_warnings: RemoteCacheWarningsBehavior
12✔
621
    remote_cache_rpc_concurrency: int
12✔
622
    remote_cache_rpc_timeout_millis: int
12✔
623

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

629
    remote_execution_append_only_caches_base_path: str | None
12✔
630

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

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

687

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

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

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

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

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

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

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

728

729
_PER_CHILD_MEMORY_USAGE = "512MiB"
12✔
730

731

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

775
DEFAULT_LOCAL_STORE_OPTIONS = LocalStoreOptions()
12✔
776

777

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

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

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

795

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

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

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

806
    _default_distdir_name = "dist"
12✔
807
    _default_rel_distdir = f"/{_default_distdir_name}/"
12✔
808

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

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

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

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

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

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

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

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

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

1048
            Warning: this does not yet support reading nested gitignore files.
1049
            """
1050
        ),
1051
    )
1052
    # These logging options are registered in the bootstrap phase so that plugins can log during
1053
    # registration and not so that their values can be interpolated in configs.
1054
    logdir = StrOption(
12✔
1055
        advanced=True,
1056
        default=None,
1057
        metavar="<dir>",
1058
        daemon=True,
1059
        help="Write logs to files under this directory.",
1060
    )
1061
    pantsd = BoolOption(
12✔
1062
        default=True,
1063
        daemon=True,
1064
        help=softwrap(
1065
            """
1066
            Enables use of the Pants daemon (pantsd). pantsd can significantly improve
1067
            runtime performance by lowering per-run startup cost, and by memoizing filesystem
1068
            operations and rule execution.
1069
            """
1070
        ),
1071
    )
1072
    sandboxer = BoolOption(
12✔
1073
        default=False,
1074
        daemon=False,
1075
        help=softwrap(
1076
            """
1077
            Enables use of the sandboxer process. The sandboxer materializes files into the sandbox
1078
            on behalf of the main process (either pantsd or the pants client process if running
1079
            without pantsd). This works around a well-known race condition when a multithreaded
1080
            program writes executable files and then spawns subprocesses to execute them, which
1081
            can lead to ETXTBSY errors.
1082

1083
            This is a new feature so it is off by default. In the future, once this is stable,
1084
            it will likely default to True.
1085
            """
1086
        ),
1087
    )
1088
    # Whether or not to make necessary arrangements to have concurrent runs in pants.
1089
    # In practice, this means that if this is set, a run will not even try to use pantsd.
1090
    # NB: Eventually, we would like to deprecate this flag in favor of making pantsd runs parallelizable.
1091
    concurrent = BoolOption(
12✔
1092
        default=False,
1093
        help=softwrap(
1094
            """
1095
            Enable concurrent runs of Pants. With this enabled, Pants will
1096
            start up all concurrent invocations (e.g. in other terminals) without pantsd.
1097
            As a result, enabling this option will increase the per-run startup cost, but
1098
            will not block subsequent invocations.
1099
            """
1100
        ),
1101
    )
1102

1103
    # NB: We really don't want this option to invalidate the daemon, because different clients might have
1104
    # different needs. For instance, an IDE might have a very long timeout because it only wants to refresh
1105
    # a project in the background, while a user might want a shorter timeout for interactivity.
1106
    pantsd_timeout_when_multiple_invocations = FloatOption(
12✔
1107
        advanced=True,
1108
        default=60.0,
1109
        help=softwrap(
1110
            """
1111
            The maximum amount of time to wait for the invocation to start until
1112
            raising a timeout exception.
1113
            Because pantsd currently does not support parallel runs,
1114
            any prior running Pants command must be finished for the current one to start.
1115
            To never timeout, use the value -1.
1116
            """
1117
        ),
1118
    )
1119
    pantsd_max_memory_usage = MemorySizeOption(
12✔
1120
        advanced=True,
1121
        default=memory_size("4GiB"),
1122
        default_help_repr="4GiB",
1123
        help=softwrap(
1124
            """
1125
            The maximum memory usage of the pantsd process.
1126

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

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

1136
            There is at most one pantsd process per workspace.
1137
            """
1138
        ),
1139
    )
1140

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

1185
            Values less than 2 are not currently supported.
1186

1187
            This value is independent of the number of processes that may be spawned in
1188
            parallel locally (controlled by `--process-execution-local-parallelism`).
1189
            """
1190
        ),
1191
    )
1192
    rule_threads_max = IntOption(
12✔
1193
        default=None,
1194
        advanced=True,
1195
        help=softwrap(
1196
            """
1197
            The maximum number of threads to use to execute `@rule` logic. Defaults to
1198
            a small multiple of `--rule-threads-core`.
1199
            """
1200
        ),
1201
    )
1202
    cache_instructions = softwrap(
12✔
1203
        """
1204
        The path may be absolute or relative. If the directory is within the build root, be
1205
        sure to include it in `--pants-ignore`.
1206
        """
1207
    )
1208
    local_store_dir = StrOption(
12✔
1209
        advanced=True,
1210
        help=softwrap(
1211
            f"""
1212
            Directory to use for the local file store, which stores the results of
1213
            subprocesses run by Pants.
1214

1215
            {cache_instructions}
1216
            """
1217
        ),
1218
        # This default is also hard-coded into the engine's rust code in
1219
        # fs::Store::default_path so that tools using a Store outside of pants
1220
        # are likely to be able to use the same storage location.
1221
        default=DEFAULT_LOCAL_STORE_OPTIONS.store_dir,
1222
    )
1223
    local_store_shard_count = IntOption(
12✔
1224
        advanced=True,
1225
        help=softwrap(
1226
            """
1227
            The number of LMDB shards created for the local store. This setting also impacts
1228
            the maximum size of stored files: see `--local-store-files-max-size-bytes`
1229
            for more information.
1230

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

1235
            NB: After changing this value, you will likely want to manually clear the
1236
            `--local-store-dir` directory to clear the space used by old shard layouts.
1237
            """
1238
        ),
1239
        default=DEFAULT_LOCAL_STORE_OPTIONS.shard_count,
1240
    )
1241
    local_store_processes_max_size_bytes = IntOption(
12✔
1242
        advanced=True,
1243
        help=softwrap(
1244
            """
1245
            The maximum size in bytes of the local store containing process cache entries.
1246
            Stored below `--local-store-dir`.
1247
            """
1248
        ),
1249
        default=DEFAULT_LOCAL_STORE_OPTIONS.processes_max_size_bytes,
1250
    )
1251
    local_store_files_max_size_bytes = IntOption(
12✔
1252
        advanced=True,
1253
        help=softwrap(
1254
            """
1255
            The maximum size in bytes of the local store containing files.
1256
            Stored below `--local-store-dir`.
1257

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

1262
            This value doesn't reflect space allocated on disk, or RAM allocated (it
1263
            may be reflected in VIRT but not RSS). However, the default is lower than you
1264
            might otherwise choose because macOS creates core dumps that include MMAP'd
1265
            pages, and setting this too high might cause core dumps to use an unreasonable
1266
            amount of disk if they are enabled.
1267
            """
1268
        ),
1269
        default=DEFAULT_LOCAL_STORE_OPTIONS.files_max_size_bytes,
1270
    )
1271
    local_store_directories_max_size_bytes = IntOption(
12✔
1272
        advanced=True,
1273
        help=softwrap(
1274
            """
1275
            The maximum size in bytes of the local store containing directories.
1276
            Stored below `--local-store-dir`.
1277
            """
1278
        ),
1279
        default=DEFAULT_LOCAL_STORE_OPTIONS.directories_max_size_bytes,
1280
    )
1281
    _named_caches_dir = StrOption(
12✔
1282
        advanced=True,
1283
        help=softwrap(
1284
            f"""
1285
            Directory to use for named global caches for tools and processes with trusted,
1286
            concurrency-safe caches.
1287

1288
            {cache_instructions}
1289
            """
1290
        ),
1291
        default=os.path.join(get_pants_cachedir(), "named_caches"),
1292
    )
1293
    local_execution_root_dir = StrOption(
12✔
1294
        advanced=True,
1295
        help=softwrap(
1296
            f"""
1297
            Directory to use for local process execution sandboxing.
1298

1299
            {cache_instructions}
1300
            """
1301
        ),
1302
        default=tempfile.gettempdir(),
1303
        default_help_repr="<tmp_dir>",
1304
    )
1305
    local_cache = BoolOption(
12✔
1306
        default=DEFAULT_EXECUTION_OPTIONS.local_cache,
1307
        help=softwrap(
1308
            """
1309
            Whether to cache process executions in a local cache persisted to disk at
1310
            `--local-store-dir`.
1311
            """
1312
        ),
1313
    )
1314
    cache_content_behavior = EnumOption(
12✔
1315
        advanced=True,
1316
        default=DEFAULT_EXECUTION_OPTIONS.cache_content_behavior,
1317
        help=softwrap(
1318
            """
1319
            Controls how the content of cache entries is handled during process execution.
1320

1321
            When using a remote cache, the `fetch` behavior will fetch remote cache content from the
1322
            remote store before considering the cache lookup a hit, while the `validate` behavior
1323
            will only validate (for either a local or remote cache) that the content exists, without
1324
            fetching it.
1325

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

1330
            The `defer` mode is the most network efficient (because it will completely skip network
1331
            requests in many cases), followed by the `validate` mode (since it can still skip
1332
            fetching the content if no consumer ends up needing it). But both the `validate` and
1333
            `defer` modes rely on an experimental feature called "backtracking" to attempt to
1334
            recover if content later turns out to be missing (`validate` has a much narrower window
1335
            for backtracking though, since content would need to disappear between validation and
1336
            consumption: generally, within one `pantsd` session).
1337
            """
1338
        ),
1339
    )
1340
    ca_certs_path = StrOption(
12✔
1341
        advanced=True,
1342
        default=None,
1343
        help=softwrap(
1344
            f"""
1345
            Path to a file containing PEM-format CA certificates used for verifying secure
1346
            connections when downloading files required by a build.
1347

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

1351
            This option cannot be overridden via environment targets, so if you need a different
1352
            value than what the rest of your organization is using, override the value via an
1353
            environment variable, CLI argument, or `.pants.rc` file. See {doc_url("docs/using-pants/key-concepts/options")}.
1354
            """
1355
        ),
1356
    )
1357
    process_total_child_memory_usage = MemorySizeOption(
12✔
1358
        advanced=True,
1359
        default=None,
1360
        help=softwrap(
1361
            """
1362
            The maximum memory usage for all "pooled" child processes.
1363

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

1368
            A high value would result in a high number of child processes spawned, potentially
1369
            overconsuming your resources and triggering the OS' OOM killer. A low value would
1370
            mean a low number of child processes launched and therefore less parallelism for the
1371
            tasks that need those processes.
1372

1373
            If setting this value, consider also adjusting the value of the
1374
            `--process-per-child-memory-usage` option.
1375

1376
            You can suffix with `GiB`, `MiB`, `KiB`, or `B` to indicate the unit, e.g.
1377
            `2GiB` or `2.12GiB`. A bare number will be in bytes.
1378
            """
1379
        ),
1380
    )
1381
    process_per_child_memory_usage = MemorySizeOption(
12✔
1382
        advanced=True,
1383
        default=DEFAULT_EXECUTION_OPTIONS.process_per_child_memory_usage,
1384
        default_help_repr=_PER_CHILD_MEMORY_USAGE,
1385
        help=softwrap(
1386
            """
1387
            The default memory usage for a single "pooled" child process.
1388

1389
            Check the documentation for the `--process-total-child-memory-usage` for advice on
1390
            how to choose an appropriate value for this option.
1391

1392
            You can suffix with `GiB`, `MiB`, `KiB`, or `B` to indicate the unit, e.g.
1393
            `2GiB` or `2.12GiB`. A bare number will be in bytes.
1394
            """
1395
        ),
1396
    )
1397
    process_execution_local_parallelism = IntOption(
12✔
1398
        default=DEFAULT_EXECUTION_OPTIONS.process_execution_local_parallelism,
1399
        default_help_repr="#cores",
1400
        advanced=True,
1401
        help=softwrap(
1402
            """
1403
            Number of concurrent processes that may be executed locally.
1404

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

1458
    remote_provider = EnumOption(
12✔
1459
        default=RemoteProvider.reapi,
1460
        help=RemoteProvider.provider_help(),
1461
    )
1462

1463
    remote_execution = BoolOption(
12✔
1464
        default=DEFAULT_EXECUTION_OPTIONS.remote_execution,
1465
        help=softwrap(
1466
            """
1467
            Enables remote workers for increased parallelism. (Alpha)
1468

1469
            Alternatively, you can use `[GLOBAL].remote_cache_read` and `[GLOBAL].remote_cache_write` to still run
1470
            everything locally, but to use a remote cache.
1471
            """
1472
        ),
1473
    )
1474
    remote_cache_read = BoolOption(
12✔
1475
        default=DEFAULT_EXECUTION_OPTIONS.remote_cache_read,
1476
        help=softwrap(
1477
            """
1478
            Whether to enable reading from a remote cache.
1479

1480
            This cannot be used at the same time as `[GLOBAL].remote_execution`.
1481
            """
1482
        ),
1483
    )
1484
    remote_cache_write = BoolOption(
12✔
1485
        default=DEFAULT_EXECUTION_OPTIONS.remote_cache_write,
1486
        help=softwrap(
1487
            """
1488
            Whether to enable writing results to a remote cache.
1489

1490
            This cannot be used at the same time as `[GLOBAL].remote_execution`.
1491
            """
1492
        ),
1493
    )
1494
    # TODO: update all these remote_... option helps for the new support for non-REAPI schemes
1495
    remote_instance_name = StrOption(
12✔
1496
        default=None,
1497
        advanced=True,
1498
        help=softwrap(
1499
            """
1500
            Name of the remote instance to use by remote caching and remote execution.
1501

1502
            This is used by some remote servers for routing. Consult your remote server for
1503
            whether this should be set.
1504

1505
            You can also use a Pants plugin which provides remote authentication to dynamically
1506
            set this value.
1507
            """
1508
        ),
1509
    )
1510
    remote_ca_certs_path = StrOption(
12✔
1511
        default=None,
1512
        advanced=True,
1513
        help=softwrap(
1514
            """
1515
            Path to a PEM file containing CA certificates used for verifying secure
1516
            connections to `[GLOBAL].remote_execution_address` and `[GLOBAL].remote_store_address`.
1517

1518
            If unspecified, Pants will attempt to auto-discover root CA certificates when TLS
1519
            is enabled with remote execution and caching.
1520
            """
1521
        ),
1522
    )
1523
    remote_client_certs_path = StrOption(
12✔
1524
        default=None,
1525
        advanced=True,
1526
        help=softwrap(
1527
            """
1528
            Path to a PEM file containing client certificates used for verifying secure connections to
1529
            `[GLOBAL].remote_execution_address` and `[GLOBAL].remote_store_address` when using
1530
            client authentication (mTLS).
1531

1532
            If unspecified, will use regular TLS. Requires `remote_client_key_path` to also be
1533
            specified.
1534
            """
1535
        ),
1536
    )
1537

1538
    remote_client_key_path = StrOption(
12✔
1539
        default=None,
1540
        advanced=True,
1541
        help=softwrap(
1542
            """
1543
            Path to a PEM file containing a private key used for verifying secure connections to
1544
            `[GLOBAL].remote_execution_address` and `[GLOBAL].remote_store_address` when using
1545
            client authentication (mTLS).
1546

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

1553
    remote_oauth_bearer_token = StrOption(
12✔
1554
        default=None,
1555
        advanced=True,
1556
        help=softwrap(
1557
            f"""
1558
            An oauth token to use for gGRPC connections to
1559
            `[GLOBAL].remote_execution_address` and `[GLOBAL].remote_store_address`.
1560

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

1566
            Recommendation: do not place a token directly in `pants.toml`, instead do one of: set
1567
            the token via the environment variable (`PANTS_REMOTE_OAUTH_BEARER_TOKEN`), CLI option
1568
            (`--remote-oauth-bearer-token`), or store the token in a file and set the option to
1569
            `"@/path/to/token.txt"` to [read the value from that
1570
            file]({doc_url("docs/using-pants/key-concepts/options#reading-individual-option-values-from-files")}).
1571
            """
1572
        ),
1573
    )
1574
    remote_store_address = StrOption(
12✔
1575
        advanced=True,
1576
        default=cast(str, DEFAULT_EXECUTION_OPTIONS.remote_store_address),
1577
        help=softwrap(
1578
            """
1579
            The URI of a server/entity used as a remote file store. The supported URIs depends on
1580
            the value of the `remote_provider` option.
1581
            """
1582
        ),
1583
    )
1584
    remote_store_headers = DictOption(
12✔
1585
        advanced=True,
1586
        default=DEFAULT_EXECUTION_OPTIONS.remote_store_headers,
1587
        help=softwrap(
1588
            """
1589
            Headers to set on remote store requests.
1590

1591
            Format: header=value. Pants may add additional headers.
1592

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

1637
            All errors not logged at the `warn` level will instead be logged at the
1638
            `debug` level.
1639
            """
1640
        ),
1641
    )
1642
    remote_cache_rpc_concurrency = IntOption(
12✔
1643
        advanced=True,
1644
        default=DEFAULT_EXECUTION_OPTIONS.remote_cache_rpc_concurrency,
1645
        help="The number of concurrent requests allowed to the remote cache service.",
1646
    )
1647
    remote_cache_rpc_timeout_millis = IntOption(
12✔
1648
        advanced=True,
1649
        default=DEFAULT_EXECUTION_OPTIONS.remote_cache_rpc_timeout_millis,
1650
        help="Timeout value for remote cache RPCs in milliseconds.",
1651
    )
1652
    remote_execution_address = StrOption(
12✔
1653
        advanced=True,
1654
        default=cast(str, DEFAULT_EXECUTION_OPTIONS.remote_execution_address),
1655
        help=softwrap(
1656
            """
1657
            The URI of a server/entity used as a remote execution scheduler. The supported URIs depends on
1658
            the value of the `remote_provider` option.
1659

1660
            You must also set `[GLOBAL].remote_store_address`, which will often be the same value.
1661
            """
1662
        ),
1663
    )
1664
    remote_execution_headers = DictOption(
12✔
1665
        advanced=True,
1666
        default=DEFAULT_EXECUTION_OPTIONS.remote_execution_headers,
1667
        help=softwrap(
1668
            """
1669
            Headers to set on remote execution requests. Format: header=value. Pants
1670
            may add additional headers.
1671

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

1720
            This option sets the "base" duration in seconds used for calculating the retry delay.
1721
            """
1722
        ),
1723
    )
1724
    _file_downloads_max_attempts = IntOption(
12✔
1725
        default=4,
1726
        advanced=True,
1727
        help=softwrap(
1728
            """
1729
            When Pants downloads files (for example, for the `http_source` source), Pants will retry the download
1730
            if a "retryable" error occurs.
1731

1732
            This option sets the maximum number of attempts Pants will make to try to download the file before giving up
1733
            with an error.
1734
            """
1735
        ),
1736
    )
1737

1738
    @property
12✔
1739
    def file_downloads_retry_delay(self) -> timedelta:
12✔
1740
        value = self._file_downloads_retry_delay
1✔
1741
        if value <= 0.0:
1✔
1742
            raise ValueError(
×
1743
                f"Global option `--file-downloads-retry-delay` must a positive number, got {value}"
1744
            )
1745
        return timedelta(seconds=value)
1✔
1746

1747
    @property
12✔
1748
    def file_downloads_max_attempts(self) -> int:
12✔
1749
        value = self._file_downloads_max_attempts
1✔
1750
        if value < 1:
1✔
1751
            raise ValueError(
×
1752
                f"Global option `--file-downloads-max-attempts` must be at least 1, got {value}"
1753
            )
1754
        return value
1✔
1755

1756
    allow_deprecated_macos_versions = StrListOption(
12✔
1757
        default=[],
1758
        advanced=True,
1759
        help=softwrap(
1760
            f"""
1761
            Silence warnings/errors about running Pants on these versions of macOS. Pants only supports
1762
            recent versions of macOS. You can try running on older versions, but it may or may not work.
1763

1764
            If you have questions or concerns about this, please reach out to us at
1765
            {doc_url("community/getting-help")}.
1766
            """
1767
        ),
1768
    )
1769

1770
    @classmethod
12✔
1771
    def validate_instance(cls, opts: OptionValueContainer):
12✔
1772
        """Validates an instance of global options for cases that are not prohibited via
1773
        registration.
1774

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

1779
        Raises pants.option.errors.OptionsError on validation failure.
1780
        """
1781
        if opts.pants_version != pants_version():
12✔
1782
            raise BuildConfigurationError(
×
1783
                softwrap(
1784
                    f"""
1785
                        Version mismatch: Requested version was {opts.pants_version},
1786
                        our version is {pants_version()}.
1787
                        """
1788
                )
1789
            )
1790

1791
        if opts.rule_threads_core < 2:
12✔
1792
            # TODO: This is a defense against deadlocks due to #11329: we only run one `@goal_rule`
1793
            # at a time, and a `@goal_rule` will only block one thread.
1794
            raise OptionsError(
×
1795
                softwrap(
1796
                    f"""
1797
                    --rule-threads-core values less than 2 are not supported, but it was set to
1798
                    {opts.rule_threads_core}.
1799
                    """
1800
                )
1801
            )
1802

1803
        if (
12✔
1804
            opts.process_total_child_memory_usage is not None
1805
            and opts.process_total_child_memory_usage < opts.process_per_child_memory_usage
1806
        ):
1807
            raise OptionsError(
×
1808
                softwrap(
1809
                    f"""
1810
                    Nailgun pool can not be initialised as the total amount of memory allowed is \
1811
                    smaller than the memory allocation for a single child process.
1812

1813
                    - total child process memory allowed: {fmt_memory_size(opts.process_total_child_memory_usage)}
1814

1815
                    - default child process memory: {fmt_memory_size(opts.process_per_child_memory_usage)}
1816
                    """
1817
                )
1818
            )
1819

1820
        # TODO: --loop is not a bootstrap option, so it probably shouldn't be referenced here.
1821
        #   But we don't want the rest of this validation code in GlobalOptions, as this would
1822
        #   cause an import cycle.
1823
        if not opts.watch_filesystem and (opts.pantsd or opts.loop):
12✔
1824
            raise OptionsError(
×
1825
                softwrap(
1826
                    """
1827
                    The `--no-watch-filesystem` option may not be set if
1828
                    `--pantsd` or `--loop` is set.
1829
                    """
1830
                )
1831
            )
1832

1833
        provider_source = "the `[GLOBAL].remote_provider` option"
12✔
1834
        if opts.remote_execution_address:
12✔
1835
            address_source = "the `[GLOBAL].remote_execution_address` option"
2✔
1836
            opts.remote_provider.validate_execution_supported(
2✔
1837
                provider_source=provider_source, execution_implied_by=address_source
1838
            )
1839
            opts.remote_provider.validate_address(
2✔
1840
                opts.remote_execution_address,
1841
                address_source=address_source,
1842
                provider_source=provider_source,
1843
            )
1844
        if opts.remote_store_address:
12✔
1845
            opts.remote_provider.validate_address(
3✔
1846
                opts.remote_store_address,
1847
                address_source="the `[GLOBAL].remote_store_address` option",
1848
                provider_source=provider_source,
1849
            )
1850

1851
        # Ensure that remote headers are ASCII.
1852
        def validate_remote_headers(opt_name: str) -> None:
12✔
1853
            command_line_opt_name = f"--{opt_name.replace('_', '-')}"
12✔
1854
            for k, v in getattr(opts, opt_name).items():
12✔
1855
                if not k.isascii():
1✔
1856
                    raise OptionsError(
×
1857
                        softwrap(
1858
                            f"""
1859
                            All values in `{command_line_opt_name}` must be ASCII, but the key
1860
                            in `{k}: {v}` has non-ASCII characters.
1861
                            """
1862
                        )
1863
                    )
1864
                if not v.isascii():
1✔
1865
                    raise OptionsError(
×
1866
                        softwrap(
1867
                            f"""
1868
                            All values in `{command_line_opt_name}` must be ASCII, but the value in
1869
                            `{k}: {v}` has non-ASCII characters.
1870
                            """
1871
                        )
1872
                    )
1873

1874
        validate_remote_headers("remote_execution_headers")
12✔
1875
        validate_remote_headers("remote_store_headers")
12✔
1876

1877
        is_remote_client_key_set = opts.remote_client_key_path is not None
12✔
1878
        is_remote_client_certs_set = opts.remote_client_certs_path is not None
12✔
1879
        if is_remote_client_key_set != is_remote_client_certs_set:
12✔
1880
            raise OptionsError(
×
1881
                softwrap(
1882
                    """
1883
                    `--remote-client-key-path` and `--remote-client-certs-path` must be specified
1884
                    together.
1885
                    """
1886
                )
1887
            )
1888

1889
        illegal_build_ignores = [i for i in opts.build_ignore if i.startswith("!")]
12✔
1890
        if illegal_build_ignores:
12✔
1891
            raise OptionsError(
×
1892
                softwrap(
1893
                    f"""
1894
                    The `--build-ignore` option does not support negated globs, but was
1895
                    given: {illegal_build_ignores}.
1896
                    """
1897
                )
1898
            )
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