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

pantsbuild / pants / 22740642519

05 Mar 2026 11:00PM UTC coverage: 52.677% (-40.3%) from 92.931%
22740642519

Pull #23157

github

web-flow
Merge 2aa18e6d4 into f0030f5e7
Pull Request #23157: [pants ng] Partition source files by config.

31678 of 60136 relevant lines covered (52.68%)

0.53 hits per line

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

95.83
/src/python/pants/backend/docker/lint/hadolint/rules.py
1
# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3
from __future__ import annotations
1✔
4

5
from dataclasses import dataclass
1✔
6
from typing import Any
1✔
7

8
from pants.backend.docker.lint.hadolint.skip_field import SkipHadolintField
1✔
9
from pants.backend.docker.lint.hadolint.subsystem import Hadolint
1✔
10
from pants.backend.docker.subsystems.dockerfile_parser import (
1✔
11
    DockerfileInfo,
12
    DockerfileInfoRequest,
13
    parse_dockerfile,
14
)
15
from pants.backend.docker.target_types import DockerImageSourceField
1✔
16
from pants.core.goals.lint import LintResult, LintTargetsRequest
1✔
17
from pants.core.goals.resolves import ExportableTool
1✔
18
from pants.core.util_rules.config_files import find_config_file
1✔
19
from pants.core.util_rules.external_tool import download_external_tool
1✔
20
from pants.core.util_rules.partitions import PartitionerType
1✔
21
from pants.engine.fs import MergeDigests
1✔
22
from pants.engine.intrinsics import execute_process, merge_digests
1✔
23
from pants.engine.platform import Platform
1✔
24
from pants.engine.process import Process
1✔
25
from pants.engine.rules import collect_rules, concurrently, implicitly, rule
1✔
26
from pants.engine.target import FieldSet, Target
1✔
27
from pants.engine.unions import UnionRule
1✔
28
from pants.util.logging import LogLevel
1✔
29
from pants.util.strutil import pluralize
1✔
30

31

32
@dataclass(frozen=True)
1✔
33
class HadolintFieldSet(FieldSet):
1✔
34
    required_fields = (DockerImageSourceField,)
1✔
35

36
    source: DockerImageSourceField
1✔
37

38
    @classmethod
1✔
39
    def opt_out(cls, tgt: Target) -> bool:
1✔
40
        return tgt.get(SkipHadolintField).value
×
41

42

43
class HadolintRequest(LintTargetsRequest):
1✔
44
    field_set_type = HadolintFieldSet
1✔
45
    tool_subsystem = Hadolint  # type: ignore[assignment]
1✔
46
    partitioner_type = PartitionerType.DEFAULT_SINGLE_PARTITION
1✔
47

48

49
def generate_argv(
1✔
50
    dockerfile_infos: tuple[DockerfileInfo, ...], hadolint: Hadolint
51
) -> tuple[str, ...]:
52
    args = []
1✔
53
    if hadolint.config:
1✔
54
        args.append(f"--config={hadolint.config}")
×
55
    args.extend(hadolint.args)
1✔
56
    args.extend(info.source for info in dockerfile_infos)
1✔
57
    return tuple(args)
1✔
58

59

60
@rule(desc="Lint with Hadolint", level=LogLevel.DEBUG)
1✔
61
async def run_hadolint(
1✔
62
    request: HadolintRequest.Batch[HadolintFieldSet, Any],
63
    hadolint: Hadolint,
64
    platform: Platform,
65
) -> LintResult:
66
    downloaded_hadolint, config_files = await concurrently(
1✔
67
        download_external_tool(hadolint.get_request(platform)),
68
        find_config_file(hadolint.config_request()),
69
    )
70

71
    dockerfile_infos = await concurrently(
1✔
72
        parse_dockerfile(DockerfileInfoRequest(field_set.address), **implicitly())
73
        for field_set in request.elements
74
    )
75

76
    input_digest = await merge_digests(
1✔
77
        MergeDigests(
78
            (
79
                downloaded_hadolint.digest,
80
                config_files.snapshot.digest,
81
                *(info.digest for info in dockerfile_infos),
82
            )
83
        )
84
    )
85
    process_result = await execute_process(
1✔
86
        Process(
87
            argv=[downloaded_hadolint.exe, *generate_argv(dockerfile_infos, hadolint)],
88
            # Hadolint tries to read a configuration file from a few locations on the system:
89
            # https://github.com/hadolint/hadolint/blob/43d2bfe9f71dea9ddd203d5bdbd2cc1fb512e4dd/src/Hadolint/Config/Configfile.hs#L75-L101
90
            #
91
            # We don't want it to do this in order to have reproducible results machine to machine
92
            # and there is also the problem that on some machines, an unset (as opposed to empty)
93
            # HOME env var crashes hadolint with SIGSEGV.
94
            # See: https://github.com/hadolint/hadolint/issues/741
95
            #
96
            # As such, we set HOME to blank so no system configuration is found and, as a side
97
            # benefit, we don't crash.
98
            #
99
            # See https://github.com/pantsbuild/pants/issues/13735 for more details.
100
            env={"HOME": ""},
101
            input_digest=input_digest,
102
            description=f"Run `hadolint` on {pluralize(len(dockerfile_infos), 'Dockerfile')}.",
103
            level=LogLevel.DEBUG,
104
        ),
105
        **implicitly(),
106
    )
107

108
    return LintResult.create(request, process_result)
1✔
109

110

111
def rules():
1✔
112
    return [
1✔
113
        *collect_rules(),
114
        *HadolintRequest.rules(),
115
        UnionRule(ExportableTool, Hadolint),
116
    ]
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