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

pantsbuild / pants / 22285099215

22 Feb 2026 08:52PM UTC coverage: 75.854% (-17.1%) from 92.936%
22285099215

Pull #23121

github

web-flow
Merge c7299df9c into ba8359840
Pull Request #23121: fix issue with optional fields in dependency validator

28 of 29 new or added lines in 2 files covered. (96.55%)

11174 existing lines in 400 files now uncovered.

53694 of 70786 relevant lines covered (75.85%)

1.88 hits per line

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

98.79
/src/python/pants/backend/adhoc/target_types.py
1
# Copyright 2023 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
from __future__ import annotations
3✔
5

6
from enum import Enum
3✔
7
from typing import ClassVar
3✔
8

9
from pants.base.glob_match_error_behavior import GlobMatchErrorBehavior
3✔
10
from pants.core.environments.target_types import EnvironmentField
3✔
11
from pants.core.util_rules.adhoc_process_support import PathEnvModifyMode
3✔
12
from pants.engine.env_vars import EXTRA_ENV_VARS_USAGE_HELP
3✔
13
from pants.engine.fs import GlobExpansionConjunction
3✔
14
from pants.engine.process import ProcessCacheScope
3✔
15
from pants.engine.target import (
3✔
16
    COMMON_TARGET_FIELDS,
17
    BoolField,
18
    Dependencies,
19
    DictStringToStringField,
20
    IntField,
21
    MultipleSourcesField,
22
    SpecialCasedDependencies,
23
    StringField,
24
    StringSequenceField,
25
    Target,
26
    ValidNumbers,
27
)
28
from pants.util.docutil import bin_name
3✔
29
from pants.util.strutil import help_text
3✔
30

31

32
class AdhocToolDependenciesField(Dependencies):
3✔
33
    pass
3✔
34

35

36
class AdhocToolRunnableField(StringField):
3✔
37
    alias: ClassVar[str] = "runnable"
3✔
38
    required = True
3✔
39
    help = help_text(
3✔
40
        lambda: f"""
41
        Address to a target that can be invoked by the `run` goal (and does not set
42
        `run_in_sandbox_behavior=NOT_SUPPORTED`). This will be executed along with any arguments
43
        specified by `{AdhocToolArgumentsField.alias}`, in a sandbox with that target's transitive
44
        dependencies, along with the transitive dependencies specified by
45
        `{AdhocToolExecutionDependenciesField.alias}`.
46
        """
47
    )
48

49

50
class AdhocToolOutputFilesField(StringSequenceField):
3✔
51
    alias: ClassVar[str] = "output_files"
3✔
52
    required = False
3✔
53
    default = ()
3✔
54
    help = help_text(
3✔
55
        lambda: f"""
56
        Specify the output files to capture, relative to the value of
57
        `{AdhocToolWorkdirField.alias}`.
58

59
        For directories, use `{AdhocToolOutputDirectoriesField.alias}`. At least one of
60
        `{AdhocToolOutputFilesField.alias}` and `{AdhocToolOutputDirectoriesField.alias}` must be
61
        specified.
62

63
        Relative paths (including `..`) may be used, as long as the path does not ascend further
64
        than the build root.
65
        """
66
    )
67

68

69
class AdhocToolOutputDirectoriesField(StringSequenceField):
3✔
70
    alias: ClassVar[str] = "output_directories"
3✔
71
    required = False
3✔
72
    default = ()
3✔
73
    help = help_text(
3✔
74
        lambda: f"""
75
        Specify full directories (including recursive descendants) of output to capture, relative
76
        to the value of `{AdhocToolWorkdirField.alias}`.
77

78
        For individual files, use `{AdhocToolOutputFilesField.alias}`. At least one of
79
        `{AdhocToolOutputFilesField.alias}` and `{AdhocToolOutputDirectoriesField.alias}` must be
80
        specified.
81

82
        Relative paths (including `..`) may be used, as long as the path does not ascend further
83
        than the build root.
84
        """
85
    )
86

87

88
class AdhocToolOutputDependenciesField(AdhocToolDependenciesField):
3✔
89
    supports_transitive_excludes = True
3✔
90
    alias: ClassVar[str] = "output_dependencies"
3✔
91

92
    help = help_text(
3✔
93
        lambda: f"""
94
        Any dependencies that need to be present (as transitive dependencies) whenever the outputs
95
        of this target are consumed (including as dependencies).
96

97
        See also `{AdhocToolExecutionDependenciesField.alias}` and
98
        `{AdhocToolRunnableDependenciesField.alias}`.
99
        """
100
    )
101

102

103
class AdhocToolExecutionDependenciesField(SpecialCasedDependencies):
3✔
104
    alias: ClassVar[str] = "execution_dependencies"
3✔
105
    required = False
3✔
106
    default = None
3✔
107

108
    help = help_text(
3✔
109
        lambda: f"""
110
        The execution dependencies for this command.
111

112
        Dependencies specified here are those required to make the command complete successfully
113
        (e.g. file inputs, packages compiled from other targets, etc), but NOT required to make
114
        the outputs of the command useful. Dependencies that are required to use the outputs
115
        produced by this command should be specified using the
116
        `{AdhocToolOutputDependenciesField.alias}` field.
117

118
        If this field is specified, dependencies from `{AdhocToolOutputDependenciesField.alias}`
119
        will not be added to the execution sandbox.
120

121
        See also `{AdhocToolOutputDependenciesField.alias}` and
122
        `{AdhocToolRunnableDependenciesField.alias}`.
123
        """
124
    )
125

126

127
class AdhocToolRunnableDependenciesField(SpecialCasedDependencies):
3✔
128
    alias: ClassVar[str] = "runnable_dependencies"
3✔
129
    required = False
3✔
130
    default = None
3✔
131

132
    help = help_text(
3✔
133
        lambda: f"""
134
        The runnable dependencies for this command.
135

136
        Dependencies specified here are those required to exist on the `PATH` to make the command
137
        complete successfully (interpreters specified in a `#!` command, etc). Note that these
138
        dependencies will be made available on the `PATH` with the name of the target.
139

140
        See also `{AdhocToolOutputDependenciesField.alias}` and
141
        `{AdhocToolExecutionDependenciesField.alias}`.
142
        """
143
    )
144

145

146
class AdhocToolSourcesField(MultipleSourcesField):
3✔
147
    # We solely register this field for codegen to work.
148
    alias: ClassVar[str] = "_sources"
3✔
149
    uses_source_roots = False
3✔
150
    expected_num_files = 0
3✔
151

152

153
class AdhocToolArgumentsField(StringSequenceField):
3✔
154
    alias: ClassVar[str] = "args"
3✔
155
    default = ()
3✔
156
    help = help_text(
3✔
157
        lambda: f"Extra arguments to pass into the `{AdhocToolRunnableField.alias}` field."
158
    )
159

160

161
class AdhocToolStdoutFilenameField(StringField):
3✔
162
    alias: ClassVar[str] = "stdout"
3✔
163
    default = None
3✔
164
    help = help_text(
3✔
165
        lambda: f"""
166
        A filename to capture the contents of `stdout` to. Relative paths are
167
        relative to the value of `{AdhocToolWorkdirField.alias}`, absolute paths
168
        start at the build root.
169
        """
170
    )
171

172

173
class AdhocToolStderrFilenameField(StringField):
3✔
174
    alias: ClassVar[str] = "stderr"
3✔
175
    default = None
3✔
176
    help = help_text(
3✔
177
        lambda: f"""
178
        A filename to capture the contents of `stderr` to. Relative paths are
179
        relative to the value of `{AdhocToolWorkdirField.alias}`, absolute paths
180
        start at the build root.
181
        """
182
    )
183

184

185
class AdhocToolTimeoutField(IntField):
3✔
186
    alias: ClassVar[str] = "timeout"
3✔
187
    default = 30
3✔
188
    help = "Command execution timeout (in seconds)."
3✔
189
    valid_numbers = ValidNumbers.positive_only
3✔
190

191

192
class AdhocToolExtraEnvVarsField(StringSequenceField):
3✔
193
    alias: ClassVar[str] = "extra_env_vars"
3✔
194
    help = help_text(
3✔
195
        f"""
196
        Additional environment variables to provide to the process.
197

198
        {EXTRA_ENV_VARS_USAGE_HELP}
199
        """
200
    )
201

202

203
class AdhocToolLogOutputField(BoolField):
3✔
204
    alias: ClassVar[str] = "log_output"
3✔
205
    default = False
3✔
206
    help = "Set to true if you want the output logged to the console."
3✔
207

208

209
class AdhocToolWorkdirField(StringField):
3✔
210
    alias: ClassVar[str] = "workdir"
3✔
211
    default = "."
3✔
212
    help = help_text(
3✔
213
        """
214
        Sets the working directory for the process.
215

216
        Values are relative to the build root, except in the following cases:
217

218
        * `.` specifies the location of the `BUILD` file.
219
        * Values beginning with `./` are relative to the location of the `BUILD` file.
220
        * `/` or the empty string specifies the build root.
221
        * Values beginning with `/` are also relative to the build root.
222
        """
223
    )
224

225

226
class AdhocToolNamedCachesField(DictStringToStringField):
3✔
227
    alias = "experimental_named_caches"
3✔
228
    help = help_text(
3✔
229
        """
230
        Named caches to construct for the execution.
231
        See https://www.pantsbuild.org/docs/reference-global#named_caches_dir.
232

233
        The keys of the mapping are the directory name to be created in the named caches dir.
234
        The values are the name of the symlink (relative to the sandbox root) in the sandbox which
235
        points to the subdirectory in the named caches dir
236

237
        NOTE: The named caches MUST be handled with great care. Processes accessing the named caches
238
        can be run in parallel, and can be cancelled at any point in their execution (and
239
        potentially restarted). That means that _every_ operation modifying the contents of the cache
240
        MUST be concurrency and cancellation safe.
241
        """
242
    )
243

244

245
class AdhocToolOutputRootDirField(StringField):
3✔
246
    alias: ClassVar[str] = "root_output_directory"
3✔
247
    default = "/"
3✔
248
    help = help_text(
3✔
249
        """
250
        Adjusts the location of files output by this target, when consumed as a dependency.
251

252
        Values are relative to the build root, except in the following cases:
253

254
          * `.` specifies the location of the `BUILD` file.
255
          * Values beginning with `./` are relative to the location of the `BUILD` file.
256
          * `/` or the empty string specifies the build root.
257
          * Values beginning with `/` are also relative to the build root.
258
        """
259
    )
260

261

262
class AdhocToolWorkspaceInvalidationSourcesField(StringSequenceField):
3✔
263
    alias: ClassVar[str] = "workspace_invalidation_sources"
3✔
264
    help = help_text(
3✔
265
        """
266
        Path globs for source files on which this target depends and for which any changes should cause
267
        this target's process to be re-executed. Unlike ordinary dependencies, the files referenced by
268
        `workspace_invalidation_sources` globs are not materialized into any execution sandbox
269
        and are referenced solely for cache invalidation purposes.
270

271
        Note: This field is intended to work with the in-workspace execution environment configured by
272
        the `workspace_environment` target type. It should only be used when the configured
273
        environment for a target is a `workspace_environment`.
274

275
        Implementation: Pants computes a digest of all of the files referenced by the provided globs
276
        and injects that digest into the process as an environment variable. Since environment variables
277
        are part of the cache key for a process's execution, any changes to the referenced files will
278
        change the digest and thus force re-exection of the process.
279
        """
280
    )
281

282

283
class AdhocToolPathEnvModifyModeField(StringField):
3✔
284
    alias = "path_env_modify"
3✔
285
    default = PathEnvModifyMode.PREPEND.value
3✔
286
    help = help_text(
3✔
287
        """
288
        When executing the command of an `adhoc_tool`, `shell_command`, or `test_shell_command` target,
289
        Pants may augment the `PATH` environment variable with the location of any binary shims created for
290
        `tools` and any runnable dependencies.
291

292
        Modification of the `PATH` environment variable can be configured as follows:
293

294
        - `prepend`: Prepend the extra path components to any existing `PATH` value.
295

296
        - `append`: Append the extra path componenets to any existing `PATH` value.
297

298
        - `off`: Do not modify the existing `PATH` value.
299
        """
300
    )
301
    valid_choices = PathEnvModifyMode
3✔
302

303
    @property
3✔
304
    def enum_value(self) -> PathEnvModifyMode:
3✔
305
        return PathEnvModifyMode(self.value)
2✔
306

307

308
class OutputsMatchMode(Enum):
3✔
309
    """The different types of output checks for adhoc_tool / shell_command targets."""
310

311
    ALL = "all"
3✔
312
    ALL_WARN = "all_warn"
3✔
313
    AT_LEAST_ONE = "at_least_one"
3✔
314
    AT_LEAST_ONE_WARN = "at_least_one_warn"
3✔
315
    ALLOW_EMPTY = "allow_empty"
3✔
316

317
    @property
3✔
318
    def glob_match_error_behavior(self) -> GlobMatchErrorBehavior:
3✔
319
        if self in (OutputsMatchMode.ALL, OutputsMatchMode.AT_LEAST_ONE):
2✔
320
            return GlobMatchErrorBehavior.error
1✔
321
        else:
322
            return GlobMatchErrorBehavior.warn
2✔
323

324
    @property
3✔
325
    def glob_expansion_conjunction(self) -> GlobExpansionConjunction | None:
3✔
326
        if self in (OutputsMatchMode.ALL, OutputsMatchMode.ALL_WARN):
2✔
327
            return GlobExpansionConjunction.all_match
2✔
328
        elif self in (OutputsMatchMode.AT_LEAST_ONE, OutputsMatchMode.AT_LEAST_ONE_WARN):
1✔
329
            return GlobExpansionConjunction.any_match
1✔
330
        else:
331
            return None
1✔
332

333

334
class AdhocToolOutputsMatchMode(StringField):
3✔
335
    alias = "outputs_match_mode"
3✔
336
    default = OutputsMatchMode.ALL_WARN.value
3✔
337
    help = help_text(
3✔
338
        """
339
        Configure whether all, or some, of the values in the `output_files` and `output_directories` fields must actually match
340
        the outputs generated by the invoked process. These values are called "globs". Outputs may be matched by more than one
341
        glob.
342

343
        Valid values are:
344

345
        - `all_warn`: Log a warning if any glob fails to match an output. (In other words, all globs must match to avoid a
346
        warning.) This is the default value.
347

348
        - `all`: Ensure all globs match an output or else raise an error.
349

350
        - `at_least_one_warn`: Log a warning if none of the globs match an output.
351

352
        - `at_least_one`: Ensure at least one glob matches an output or else raise an error.
353

354
        - `allow_empty`: Allow empty digests (which means nothing was captured). This disables checking that globs match outputs.
355
        """
356
    )
357
    valid_choices = OutputsMatchMode
3✔
358

359
    @property
3✔
360
    def enum_value(self) -> OutputsMatchMode:
3✔
361
        return OutputsMatchMode(self.value)
2✔
362

363

364
class AdhocToolCacheScopeField(StringField):
3✔
365
    alias = "cache_scope"
3✔
366
    default = "from_environment"
3✔
367
    help = help_text(
3✔
368
        f"""
369
        Set the "cache scope" of the executed process to provided value. The cache scope determines for how long
370
        Pants will cache the result of the process execution (assuming no changes to files or dependencies
371
        invalidate the result in the meantime).
372

373
        The valid values are:
374

375
        - `from_environment`: Use the default cache scope for the applicable environment in which the process will execute.
376
        This is `success` for all environments except for `experimental_workspace_environment`, in which case `session`
377
        cache scope will be used.
378

379
        - `success`: Cache successful executions of the process.
380

381
        - `success_per_pantsd_restart`: Cache successful executions of the process for the life of the
382
         applicable pantsd process.
383

384
        - `session`: Only cache the result for a single Pants session. This will usually be a single invocation of the
385
        `{bin_name()}` tool.
386
        """
387
    )
388
    valid_choices = ("from_environment", "success", "success_per_pantsd_restart", "session")
3✔
389

390
    @property
3✔
391
    def enum_value(self) -> ProcessCacheScope | None:
3✔
392
        value = self.value
2✔
393
        if value == "success":
2✔
UNCOV
394
            return ProcessCacheScope.SUCCESSFUL
×
395
        elif value == "success_per_pantsd_restart":
2✔
396
            return ProcessCacheScope.PER_RESTART_SUCCESSFUL
×
397
        elif value == "session":
2✔
398
            return ProcessCacheScope.PER_SESSION
1✔
399
        else:
400
            # Default case `from_environment`
401
            return None
2✔
402

403

404
class AdhocToolTarget(Target):
3✔
405
    alias: ClassVar[str] = "adhoc_tool"
3✔
406
    core_fields = (
3✔
407
        *COMMON_TARGET_FIELDS,
408
        AdhocToolRunnableField,
409
        AdhocToolArgumentsField,
410
        AdhocToolExecutionDependenciesField,
411
        AdhocToolOutputDependenciesField,
412
        AdhocToolRunnableDependenciesField,
413
        AdhocToolLogOutputField,
414
        AdhocToolOutputFilesField,
415
        AdhocToolOutputDirectoriesField,
416
        AdhocToolSourcesField,
417
        AdhocToolTimeoutField,
418
        AdhocToolExtraEnvVarsField,
419
        AdhocToolWorkdirField,
420
        AdhocToolOutputRootDirField,
421
        AdhocToolStdoutFilenameField,
422
        AdhocToolStderrFilenameField,
423
        AdhocToolWorkspaceInvalidationSourcesField,
424
        AdhocToolPathEnvModifyModeField,
425
        AdhocToolOutputsMatchMode,
426
        AdhocToolCacheScopeField,
427
        EnvironmentField,
428
    )
429
    help = help_text(
3✔
430
        lambda: f"""
431
        Execute any runnable target for its side effects.
432

433
        Example BUILD file:
434

435
            {AdhocToolTarget.alias}(
436
                {AdhocToolRunnableField.alias}=":python_source",
437
                {AdhocToolArgumentsField.alias}=[""],
438
                {AdhocToolExecutionDependenciesField.alias}=[":scripts"],
439
                {AdhocToolOutputDirectoriesField.alias}=["results/"],
440
                {AdhocToolOutputFilesField.alias}=["logs/my-script.log"],
441
            )
442

443
            shell_sources(name="scripts")
444
        """
445
    )
446

447

448
# ---
449
# `system_binary` target
450
# ---
451

452

453
class SystemBinaryNameField(StringField):
3✔
454
    alias: ClassVar[str] = "binary_name"
3✔
455
    required = True
3✔
456
    help = "The name of the binary to find."
3✔
457

458

459
class SystemBinaryExtraSearchPathsField(StringSequenceField):
3✔
460
    alias: ClassVar[str] = "extra_search_paths"
3✔
461
    default = ()
3✔
462
    help = help_text(
3✔
463
        """
464
        Extra search paths to look for the binary. These take priority over Pants' default
465
        search paths.
466
        """
467
    )
468

469

470
class SystemBinaryFingerprintPattern(StringField):
3✔
471
    alias: ClassVar[str] = "fingerprint"
3✔
472
    required = False
3✔
473
    default = None
3✔
474
    help = help_text(
3✔
475
        """
476
        A regular expression which will be used to match the fingerprint outputs from
477
        candidate binaries found during the search process.
478
        """
479
    )
480

481

482
class SystemBinaryFingerprintArgsField(StringSequenceField):
3✔
483
    alias: ClassVar[str] = "fingerprint_args"
3✔
484
    default = ()
3✔
485
    help = help_text(
3✔
486
        "Specifies arguments that will be used to run the binary during the search process."
487
    )
488

489

490
class SystemBinaryFingerprintDependenciesField(AdhocToolRunnableDependenciesField):
3✔
491
    alias: ClassVar[str] = "fingerprint_dependencies"
3✔
492
    help = help_text(
3✔
493
        """
494
        Specifies any runnable dependencies that need to be available on the `PATH` when the binary
495
        is run, so that the search process may complete successfully. The name of the target must
496
        be the name of the runnable dependency that is called by this binary.
497
        """
498
    )
499

500

501
class SystemBinaryLogFingerprintingErrorsField(BoolField):
3✔
502
    alias = "log_fingerprinting_errors"
3✔
503
    default = True
3✔
504
    help = help_text(
3✔
505
        """
506
        If True, then any errors encountered while fingerprinting candidate binaries will be logged as a warning.
507
        """
508
    )
509

510

511
class SystemBinaryTarget(Target):
3✔
512
    alias: ClassVar[str] = "system_binary"
3✔
513
    core_fields = (
3✔
514
        *COMMON_TARGET_FIELDS,
515
        SystemBinaryNameField,
516
        SystemBinaryExtraSearchPathsField,
517
        SystemBinaryFingerprintPattern,
518
        SystemBinaryFingerprintArgsField,
519
        SystemBinaryFingerprintDependenciesField,
520
        SystemBinaryLogFingerprintingErrorsField,
521
    )
522
    help = help_text(
3✔
523
        lambda: f"""
524
        A system binary that can be run with `pants run` or consumed by `{AdhocToolTarget.alias}`.
525

526
        Pants will search for binaries with name `{SystemBinaryNameField.alias}` in the search
527
        paths provided, as well as default search paths. If
528
        `{SystemBinaryFingerprintPattern.alias}` is specified, each binary that is located will be
529
        executed with the arguments from `{SystemBinaryFingerprintArgsField.alias}`. Any binaries
530
        whose output does not match the pattern will be excluded.
531

532
        The first non-excluded binary will be the one that is resolved.
533
        """
534
    )
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