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

pantsbuild / pants / 19015773527

02 Nov 2025 05:33PM UTC coverage: 17.872% (-62.4%) from 80.3%
19015773527

Pull #22816

github

web-flow
Merge a12d75757 into 6c024e162
Pull Request #22816: Update Pants internal Python to 3.14

4 of 5 new or added lines in 3 files covered. (80.0%)

28452 existing lines in 683 files now uncovered.

9831 of 55007 relevant lines covered (17.87%)

0.18 hits per line

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

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

UNCOV
4
from __future__ import annotations
×
5

UNCOV
6
import re
×
UNCOV
7
from enum import Enum
×
8

UNCOV
9
from pants.backend.adhoc.target_types import (
×
10
    AdhocToolCacheScopeField,
11
    AdhocToolDependenciesField,
12
    AdhocToolExecutionDependenciesField,
13
    AdhocToolExtraEnvVarsField,
14
    AdhocToolLogOutputField,
15
    AdhocToolNamedCachesField,
16
    AdhocToolOutputDependenciesField,
17
    AdhocToolOutputDirectoriesField,
18
    AdhocToolOutputFilesField,
19
    AdhocToolOutputRootDirField,
20
    AdhocToolOutputsMatchMode,
21
    AdhocToolPathEnvModifyModeField,
22
    AdhocToolRunnableDependenciesField,
23
    AdhocToolTimeoutField,
24
    AdhocToolWorkdirField,
25
    AdhocToolWorkspaceInvalidationSourcesField,
26
)
UNCOV
27
from pants.backend.shell.subsystems.shell_setup import ShellSetup
×
UNCOV
28
from pants.core.environments.target_types import EnvironmentField
×
UNCOV
29
from pants.core.goals.package import OutputPathField
×
UNCOV
30
from pants.core.goals.test import RuntimePackageDependenciesField, TestTimeoutField
×
UNCOV
31
from pants.core.util_rules.system_binaries import BinaryPathTest
×
UNCOV
32
from pants.engine.rules import collect_rules, rule
×
UNCOV
33
from pants.engine.target import (
×
34
    COMMON_TARGET_FIELDS,
35
    BoolField,
36
    MultipleSourcesField,
37
    OverridesField,
38
    SingleSourceField,
39
    StringField,
40
    StringSequenceField,
41
    Target,
42
    TargetFilesGenerator,
43
    TargetFilesGeneratorSettings,
44
    TargetFilesGeneratorSettingsRequest,
45
    generate_file_based_overrides_field_help_message,
46
    generate_multiple_sources_field_help_message,
47
)
UNCOV
48
from pants.engine.unions import UnionRule
×
UNCOV
49
from pants.util.enums import match
×
UNCOV
50
from pants.util.strutil import help_text
×
51

52

UNCOV
53
class ShellDependenciesField(AdhocToolDependenciesField):
×
UNCOV
54
    pass
×
55

56

UNCOV
57
class ShellSourceField(SingleSourceField):
×
58
    # Normally, we would add `expected_file_extensions = ('.sh',)`, but Bash scripts don't need a
59
    # file extension, so we don't use this.
UNCOV
60
    uses_source_roots = False
×
61

62

UNCOV
63
class ShellGeneratingSourcesBase(MultipleSourcesField):
×
UNCOV
64
    uses_source_roots = False
×
65

66

UNCOV
67
class ShellGeneratorSettingsRequest(TargetFilesGeneratorSettingsRequest):
×
UNCOV
68
    pass
×
69

70

UNCOV
71
@rule
×
UNCOV
72
async def generator_settings(
×
73
    _: ShellGeneratorSettingsRequest,
74
    shell_setup: ShellSetup,
75
) -> TargetFilesGeneratorSettings:
76
    return TargetFilesGeneratorSettings(
×
77
        add_dependencies_on_all_siblings=not shell_setup.dependency_inference
78
    )
79

80

81
# -----------------------------------------------------------------------------------------------
82
# `shunit2_test` target
83
# -----------------------------------------------------------------------------------------------
84

85

UNCOV
86
class Shunit2Shell(Enum):
×
UNCOV
87
    sh = "sh"
×
UNCOV
88
    bash = "bash"
×
UNCOV
89
    dash = "dash"
×
UNCOV
90
    ksh = "ksh"
×
UNCOV
91
    pdksh = "pdksh"
×
UNCOV
92
    zsh = "zsh"
×
93

UNCOV
94
    @classmethod
×
UNCOV
95
    def parse_shebang(cls, shebang: bytes) -> Shunit2Shell | None:
×
UNCOV
96
        if not shebang:
×
UNCOV
97
            return None
×
UNCOV
98
        first_line = shebang.splitlines()[0]
×
UNCOV
99
        matches = re.match(rb"^#! *[/\w]*/(?P<program>\w+) *(?P<arg>\w*)", first_line)
×
UNCOV
100
        if not matches:
×
UNCOV
101
            return None
×
UNCOV
102
        program = matches.group("program")
×
UNCOV
103
        if program == b"env":
×
UNCOV
104
            program = matches.group("arg")
×
UNCOV
105
        try:
×
UNCOV
106
            return cls(program.decode())
×
107
        except ValueError:
×
108
            return None
×
109

UNCOV
110
    @property
×
UNCOV
111
    def binary_path_test(self) -> BinaryPathTest | None:
×
112
        arg = match(
×
113
            self,
114
            {
115
                self.sh: None,
116
                self.bash: "--version",
117
                self.dash: None,
118
                self.ksh: "--version",
119
                self.pdksh: None,
120
                self.zsh: "--version",
121
            },
122
        )
123
        if not arg:
×
124
            return None
×
125
        return BinaryPathTest((arg,))
×
126

127

UNCOV
128
class Shunit2TestDependenciesField(ShellDependenciesField):
×
UNCOV
129
    supports_transitive_excludes = True
×
130

131

UNCOV
132
class Shunit2TestTimeoutField(TestTimeoutField):
×
UNCOV
133
    pass
×
134

135

UNCOV
136
class SkipShunit2TestsField(BoolField):
×
UNCOV
137
    alias = "skip_tests"
×
UNCOV
138
    default = False
×
UNCOV
139
    help = "If true, don't run this target's tests."
×
140

141

UNCOV
142
class Shunit2TestSourceField(ShellSourceField):
×
UNCOV
143
    pass
×
144

145

UNCOV
146
class Shunit2ShellField(StringField):
×
UNCOV
147
    alias = "shell"
×
UNCOV
148
    valid_choices = Shunit2Shell
×
UNCOV
149
    help = "Which shell to run the tests with. If unspecified, Pants will look for a shebang line."
×
150

151

UNCOV
152
class Shunit2TestTarget(Target):
×
UNCOV
153
    alias = "shunit2_test"
×
UNCOV
154
    core_fields = (
×
155
        *COMMON_TARGET_FIELDS,
156
        Shunit2TestSourceField,
157
        Shunit2TestDependenciesField,
158
        Shunit2TestTimeoutField,
159
        SkipShunit2TestsField,
160
        Shunit2ShellField,
161
        RuntimePackageDependenciesField,
162
    )
UNCOV
163
    help = help_text(
×
164
        f"""
165
        A single test file for Bourne-based shell scripts using the shunit2 test framework.
166

167
        To use, add tests to your file per https://github.com/kward/shunit2/. Specify the shell
168
        to run with by either setting the field `{Shunit2ShellField.alias}` or including a
169
        shebang. To test the same file with multiple shells, create multiple `shunit2_tests`
170
        targets, one for each shell.
171

172
        Pants will automatically download the `shunit2` bash script and add
173
        `source ./shunit2` to your test for you. If you already have `source ./shunit2`,
174
        Pants will overwrite it to use the correct relative path.
175
        """
176
    )
177

178

179
# -----------------------------------------------------------------------------------------------
180
# `shunit2_tests` target generator
181
# -----------------------------------------------------------------------------------------------
182

183

UNCOV
184
class Shunit2TestsGeneratorSourcesField(ShellGeneratingSourcesBase):
×
UNCOV
185
    default = ("*_test.sh", "test_*.sh", "tests.sh")
×
UNCOV
186
    help = generate_multiple_sources_field_help_message(
×
187
        "Example: `sources=['test.sh', 'test_*.sh', '!test_ignore.sh']`"
188
    )
189

190

UNCOV
191
class Shunit2TestsOverrideField(OverridesField):
×
UNCOV
192
    help = generate_file_based_overrides_field_help_message(
×
193
        Shunit2TestTarget.alias,
194
        """
195
        overrides={
196
            "foo_test.sh": {"timeout": 120},
197
            "bar_test.sh": {"timeout": 200},
198
            ("foo_test.sh", "bar_test.sh"): {"tags": ["slow_tests"]},
199
        }
200
        """,
201
    )
202

203

UNCOV
204
class Shunit2TestsGeneratorTarget(TargetFilesGenerator):
×
UNCOV
205
    alias = "shunit2_tests"
×
UNCOV
206
    core_fields = (
×
207
        *COMMON_TARGET_FIELDS,
208
        Shunit2TestsGeneratorSourcesField,
209
        Shunit2TestsOverrideField,
210
    )
UNCOV
211
    generated_target_cls = Shunit2TestTarget
×
UNCOV
212
    copied_fields = COMMON_TARGET_FIELDS
×
UNCOV
213
    moved_fields = (
×
214
        Shunit2TestDependenciesField,
215
        Shunit2TestTimeoutField,
216
        SkipShunit2TestsField,
217
        Shunit2ShellField,
218
        RuntimePackageDependenciesField,
219
    )
UNCOV
220
    help = "Generate a `shunit2_test` target for each file in the `sources` field."
×
221

222

223
# -----------------------------------------------------------------------------------------------
224
# `shell_source` and `shell_sources` targets
225
# -----------------------------------------------------------------------------------------------
226

227

UNCOV
228
class ShellSourceTarget(Target):
×
UNCOV
229
    alias = "shell_source"
×
UNCOV
230
    core_fields = (*COMMON_TARGET_FIELDS, ShellDependenciesField, ShellSourceField)
×
UNCOV
231
    help = "A single Bourne-based shell script, e.g. a Bash script."
×
232

233

UNCOV
234
class ShellSourcesGeneratingSourcesField(ShellGeneratingSourcesBase):
×
UNCOV
235
    default = ("*.sh",) + tuple(f"!{pat}" for pat in Shunit2TestsGeneratorSourcesField.default)
×
UNCOV
236
    help = generate_multiple_sources_field_help_message(
×
237
        "Example: `sources=['example.sh', 'new_*.sh', '!old_ignore.sh']`"
238
    )
239

240

UNCOV
241
class ShellSourcesOverridesField(OverridesField):
×
UNCOV
242
    help = generate_file_based_overrides_field_help_message(
×
243
        ShellSourceTarget.alias,
244
        """
245
        overrides={
246
            "foo.sh": {"skip_shellcheck": True]},
247
            "bar.sh": {"skip_shfmt": True]},
248
            ("foo.sh", "bar.sh"): {"tags": ["linter_disabled"]},
249
        }
250
        """,
251
    )
252

253

UNCOV
254
class ShellSourcesGeneratorTarget(TargetFilesGenerator):
×
UNCOV
255
    alias = "shell_sources"
×
UNCOV
256
    core_fields = (
×
257
        *COMMON_TARGET_FIELDS,
258
        ShellSourcesGeneratingSourcesField,
259
        ShellSourcesOverridesField,
260
    )
UNCOV
261
    generated_target_cls = ShellSourceTarget
×
UNCOV
262
    copied_fields = COMMON_TARGET_FIELDS
×
UNCOV
263
    moved_fields = (ShellDependenciesField,)
×
UNCOV
264
    help = "Generate a `shell_source` target for each file in the `sources` field."
×
265

266

267
# -----------------------------------------------------------------------------------------------
268
# `shell_command` target
269
# -----------------------------------------------------------------------------------------------
270

271

UNCOV
272
class ShellCommandCommandField(StringField):
×
UNCOV
273
    alias = "command"
×
UNCOV
274
    required = True
×
UNCOV
275
    help = help_text(
×
276
        """
277
        Shell command to execute.
278

279
        The command is executed as `'bash -c <command>'` by default. If you want to invoke a binary
280
        use `exec -a $0 <binary> <args>` as the command so that the binary gets the correct `argv[0]`
281
        set.
282
        """
283
    )
284

285

UNCOV
286
class ShellCommandOutputFilesField(AdhocToolOutputFilesField):
×
UNCOV
287
    pass
×
288

289

UNCOV
290
class ShellCommandOutputDirectoriesField(AdhocToolOutputDirectoriesField):
×
UNCOV
291
    pass
×
292

293

UNCOV
294
class ShellCommandOutputDependenciesField(AdhocToolOutputDependenciesField):
×
UNCOV
295
    pass
×
296

297

UNCOV
298
class ShellCommandExecutionDependenciesField(AdhocToolExecutionDependenciesField):
×
UNCOV
299
    pass
×
300

301

UNCOV
302
class RunShellCommandExecutionDependenciesField(ShellCommandExecutionDependenciesField):
×
UNCOV
303
    help = help_text(
×
304
        lambda: f"""
305
        The execution dependencies for this command.
306

307
        Dependencies specified here are those required to make the command complete successfully
308
        (e.g. file inputs, packages compiled from other targets, etc), but NOT required to make
309
        the outputs of the command useful.
310

311
        See also `{RunShellCommandRunnableDependenciesField.alias}`.
312
        """
313
    )
314

315

UNCOV
316
class ShellCommandRunnableDependenciesField(AdhocToolRunnableDependenciesField):
×
UNCOV
317
    pass
×
318

319

UNCOV
320
class RunShellCommandRunnableDependenciesField(ShellCommandRunnableDependenciesField):
×
UNCOV
321
    help = help_text(
×
322
        lambda: f"""
323
        The runnable dependencies for this command.
324

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

329
        See also `{RunShellCommandExecutionDependenciesField.alias}`.
330
        """
331
    )
332

333

UNCOV
334
class ShellCommandSourcesField(MultipleSourcesField):
×
335
    # We solely register this field for codegen to work.
UNCOV
336
    alias = "_sources"
×
UNCOV
337
    uses_source_roots = False
×
UNCOV
338
    expected_num_files = 0
×
339

340

UNCOV
341
class ShellCommandTimeoutField(AdhocToolTimeoutField):
×
UNCOV
342
    pass
×
343

344

UNCOV
345
class ShellCommandToolsField(StringSequenceField):
×
UNCOV
346
    alias = "tools"
×
UNCOV
347
    default = ()
×
UNCOV
348
    help = help_text(
×
349
        """
350
        Specify required executable tools that might be used.
351

352
        Only the tools explicitly provided will be available on the search PATH,
353
        and these tools must be found on the paths provided by
354
        `[shell-setup].executable_search_paths` (which defaults to the system PATH).
355
        """
356
    )
357

358

UNCOV
359
class ShellCommandExtraEnvVarsField(AdhocToolExtraEnvVarsField):
×
UNCOV
360
    pass
×
361

362

UNCOV
363
class ShellCommandLogOutputField(AdhocToolLogOutputField):
×
UNCOV
364
    pass
×
365

366

UNCOV
367
class ShellCommandWorkdirField(AdhocToolWorkdirField):
×
UNCOV
368
    pass
×
369

370

UNCOV
371
class RunShellCommandWorkdirField(AdhocToolWorkdirField):
×
UNCOV
372
    pass
×
373

374

UNCOV
375
class ShellCommandOutputRootDirField(AdhocToolOutputRootDirField):
×
UNCOV
376
    pass
×
377

378

UNCOV
379
class ShellCommandTestDependenciesField(ShellCommandExecutionDependenciesField):
×
UNCOV
380
    pass
×
381

382

UNCOV
383
class ShellCommandNamedCachesField(AdhocToolNamedCachesField):
×
UNCOV
384
    pass
×
385

386

UNCOV
387
class ShellCommandWorkspaceInvalidationSourcesField(AdhocToolWorkspaceInvalidationSourcesField):
×
UNCOV
388
    pass
×
389

390

UNCOV
391
class ShellCommandPathEnvModifyModeField(AdhocToolPathEnvModifyModeField):
×
UNCOV
392
    pass
×
393

394

UNCOV
395
class ShellCommandOutputsMatchMode(AdhocToolOutputsMatchMode):
×
UNCOV
396
    pass
×
397

398

UNCOV
399
class ShellCommandCacheScopeField(AdhocToolCacheScopeField):
×
UNCOV
400
    pass
×
401

402

UNCOV
403
class SkipShellCommandTestsField(BoolField):
×
UNCOV
404
    alias = "skip_tests"
×
UNCOV
405
    default = False
×
UNCOV
406
    help = "If true, don't run this tests for target."
×
407

408

UNCOV
409
class SkipShellCommandPackageField(BoolField):
×
UNCOV
410
    alias = "skip_package"
×
UNCOV
411
    default = False
×
UNCOV
412
    help = "If true, don't run this package for target."
×
413

414

UNCOV
415
class ShellCommandTarget(Target):
×
UNCOV
416
    alias = "shell_command"
×
UNCOV
417
    core_fields = (
×
418
        *COMMON_TARGET_FIELDS,
419
        ShellCommandOutputDependenciesField,
420
        ShellCommandExecutionDependenciesField,
421
        ShellCommandRunnableDependenciesField,
422
        ShellCommandCommandField,
423
        ShellCommandLogOutputField,
424
        ShellCommandOutputFilesField,
425
        ShellCommandOutputDirectoriesField,
426
        ShellCommandSourcesField,
427
        ShellCommandTimeoutField,
428
        ShellCommandToolsField,
429
        ShellCommandExtraEnvVarsField,
430
        ShellCommandWorkdirField,
431
        ShellCommandNamedCachesField,
432
        ShellCommandOutputRootDirField,
433
        ShellCommandWorkspaceInvalidationSourcesField,
434
        ShellCommandPathEnvModifyModeField,
435
        ShellCommandOutputsMatchMode,
436
        ShellCommandCacheScopeField,
437
        EnvironmentField,
438
    )
UNCOV
439
    help = help_text(
×
440
        """
441
        Execute any external tool for its side effects.
442

443
        Example BUILD file:
444

445
            shell_command(
446
                command="./my-script.sh --flag",
447
                tools=["tar", "curl", "cat", "bash", "env"],
448
                execution_dependencies=[":scripts"],
449
                output_files=["logs/my-script.log"],
450
                output_directories=["results"],
451
            )
452

453
            shell_sources(name="scripts")
454

455
        Remember to add this target to the dependencies of each consumer, such as your
456
        `python_tests` or `docker_image`. When relevant, Pants will run your `command` and
457
        insert the `outputs` into that consumer's context.
458

459
        The command may be retried and/or cancelled, so ensure that it is idempotent.
460
        """
461
    )
462

463

UNCOV
464
class ShellCommandRunTarget(Target):
×
UNCOV
465
    alias = "run_shell_command"
×
UNCOV
466
    core_fields = (
×
467
        *COMMON_TARGET_FIELDS,
468
        RunShellCommandExecutionDependenciesField,
469
        RunShellCommandRunnableDependenciesField,
470
        ShellCommandCommandField,
471
        RunShellCommandWorkdirField,
472
    )
UNCOV
473
    help = help_text(
×
474
        """
475
        Run a script in the workspace, with all dependencies packaged/copied into a chroot.
476

477
        Example BUILD file:
478

479
            run_shell_command(
480
                command="./scripts/my-script.sh --data-files-dir={chroot}",
481
                execution_dependencies=["src/project/files:data"],
482
            )
483

484
        The `command` may use either `{chroot}` on the command line, or the `$CHROOT`
485
        environment variable to get the root directory for where any dependencies are located.
486

487
        In contrast to the `shell_command`, in addition to `workdir` you only have
488
        the `command` and `execution_dependencies` fields as the `tools` you are going to use are
489
        already on the PATH which is inherited from the Pants environment. Also, the `outputs` does
490
        not apply, as any output files produced will end up directly in your project tree.
491
        """
492
    )
493

494

UNCOV
495
class ShellCommandTestTarget(Target):
×
UNCOV
496
    alias = "test_shell_command"
×
497

UNCOV
498
    core_fields = (
×
499
        *COMMON_TARGET_FIELDS,
500
        ShellCommandTestDependenciesField,
501
        ShellCommandRunnableDependenciesField,
502
        ShellCommandCommandField,
503
        ShellCommandLogOutputField,
504
        ShellCommandSourcesField,
505
        ShellCommandTimeoutField,
506
        ShellCommandToolsField,
507
        ShellCommandExtraEnvVarsField,
508
        ShellCommandPathEnvModifyModeField,
509
        EnvironmentField,
510
        SkipShellCommandTestsField,
511
        ShellCommandWorkdirField,
512
        ShellCommandOutputFilesField,
513
        ShellCommandOutputDirectoriesField,
514
        ShellCommandOutputRootDirField,
515
        ShellCommandOutputsMatchMode,
516
        ShellCommandCacheScopeField,
517
    )
UNCOV
518
    help = help_text(
×
519
        """
520
        Run a script as a test via the `test` goal, with all dependencies packaged/copied available in the chroot.
521

522
        Example BUILD file:
523

524
            test_shell_command(
525
                name="test",
526
                tools=["test"],
527
                command="test -r $CHROOT/some-data-file.txt",
528
                execution_dependencies=["src/project/files:data"],
529
            )
530

531
        The `command` may use the `{chroot}` marker on the command line or in environment variables
532
        to get the root directory where any dependencies are materialized during execution.
533

534
        In contrast to the `run_shell_command`, this target is intended to run shell commands as tests
535
        and will only run them via the `test` goal.
536
        """
537
    )
538

539

UNCOV
540
class ShellCommandPackageDependenciesField(ShellCommandExecutionDependenciesField):
×
UNCOV
541
    pass
×
542

543

UNCOV
544
class ShellCommandPackageTarget(Target):
×
UNCOV
545
    alias = "package_shell_command"
×
546

UNCOV
547
    core_fields = (
×
548
        *COMMON_TARGET_FIELDS,
549
        ShellCommandPackageDependenciesField,
550
        ShellCommandRunnableDependenciesField,
551
        ShellCommandCommandField,
552
        ShellCommandLogOutputField,
553
        ShellCommandSourcesField,
554
        ShellCommandTimeoutField,
555
        ShellCommandToolsField,
556
        ShellCommandExtraEnvVarsField,
557
        ShellCommandPathEnvModifyModeField,
558
        ShellCommandNamedCachesField,
559
        ShellCommandWorkspaceInvalidationSourcesField,
560
        ShellCommandCacheScopeField,
561
        EnvironmentField,
562
        SkipShellCommandPackageField,
563
        ShellCommandWorkdirField,
564
        ShellCommandOutputFilesField,
565
        ShellCommandOutputDirectoriesField,
566
        ShellCommandOutputRootDirField,
567
        ShellCommandOutputsMatchMode,
568
        ShellCommandOutputDependenciesField,
569
        OutputPathField,
570
    )
571

UNCOV
572
    help = help_text(
×
573
        """
574
        Run a script to produce distributable outputs via the `package` goal.
575

576
        Example BUILD file:
577

578
            package_shell_command(
579
                name="build-rust-app",
580
                tools=["cargo"],
581
                command="cargo build --release",
582
                output_files=["target/release/binary"],
583
            )
584

585
        The `command` may use the `{chroot}` marker on the command line or in environment variables
586
        to get the root directory where any dependencies are materialized during execution.
587

588
        The outputs specified via `output_files` and `output_directories` will be captured and
589
        made available for other Pants targets to depend on. They will also be copied to the path
590
        specified via the `output_path` field (relative to the dist directory) when running
591
        `pants package`.
592

593
        This target is experimental and its behavior may change in future versions.
594
        """
595
    )
596

597

UNCOV
598
def rules():
×
UNCOV
599
    return [
×
600
        *collect_rules(),
601
        UnionRule(TargetFilesGeneratorSettingsRequest, ShellGeneratorSettingsRequest),
602
    ]
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