• 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/docker/util_rules/dependencies.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
from dataclasses import dataclass
×
UNCOV
7
from pathlib import PurePath
×
8

UNCOV
9
from pants.backend.docker.subsystems.dockerfile_parser import (
×
10
    DockerfileInfoRequest,
11
    parse_dockerfile,
12
)
UNCOV
13
from pants.backend.docker.target_types import DockerImageDependenciesField
×
UNCOV
14
from pants.backend.docker.util_rules.docker_build_args import (
×
15
    DockerBuildArgsRequest,
16
    docker_build_args,
17
)
UNCOV
18
from pants.base.glob_match_error_behavior import GlobMatchErrorBehavior
×
UNCOV
19
from pants.base.specs import FileLiteralSpec, RawSpecs
×
UNCOV
20
from pants.core.goals.package import AllPackageableTargets, OutputPathField
×
UNCOV
21
from pants.engine.addresses import Addresses, UnparsedAddressInputs
×
UNCOV
22
from pants.engine.internals.graph import resolve_targets, resolve_unparsed_address_inputs
×
UNCOV
23
from pants.engine.rules import collect_rules, implicitly, rule
×
UNCOV
24
from pants.engine.target import FieldSet, InferDependenciesRequest, InferredDependencies
×
UNCOV
25
from pants.engine.unions import UnionRule
×
UNCOV
26
from pants.util.strutil import softwrap
×
27

28

UNCOV
29
@dataclass(frozen=True)
×
UNCOV
30
class DockerInferenceFieldSet(FieldSet):
×
UNCOV
31
    required_fields = (DockerImageDependenciesField,)
×
32

UNCOV
33
    dependencies: DockerImageDependenciesField
×
34

35

UNCOV
36
class InferDockerDependencies(InferDependenciesRequest):
×
UNCOV
37
    infer_from = DockerInferenceFieldSet
×
38

39

UNCOV
40
@rule
×
UNCOV
41
async def infer_docker_dependencies(
×
42
    request: InferDockerDependencies, all_packageable_targets: AllPackageableTargets
43
) -> InferredDependencies:
44
    """Inspects the Dockerfile for references to known packageable targets."""
45
    dockerfile_info = await parse_dockerfile(
×
46
        DockerfileInfoRequest(request.field_set.address), **implicitly()
47
    )
48
    targets = await resolve_targets(**implicitly(Addresses([request.field_set.address])))
×
49
    build_args = await docker_build_args(
×
50
        DockerBuildArgsRequest(targets.expect_single()), **implicitly()
51
    )
52
    dockerfile_build_args = dockerfile_info.from_image_build_args.with_overrides(
×
53
        build_args
54
    ).nonempty()
55

56
    putative_image_addresses = set(
×
57
        await resolve_unparsed_address_inputs(
58
            UnparsedAddressInputs(
59
                dockerfile_build_args.values(),
60
                owning_address=dockerfile_info.address,
61
                description_of_origin=softwrap(
62
                    f"""
63
                    the FROM arguments from the file {dockerfile_info.source}
64
                    from the target {dockerfile_info.address}
65
                    """
66
                ),
67
                skip_invalid_addresses=True,
68
            ),
69
            **implicitly(),
70
        )
71
    )
72
    putative_copy_target_addresses = set(
×
73
        await resolve_unparsed_address_inputs(
74
            UnparsedAddressInputs(
75
                dockerfile_info.copy_build_args.nonempty().values(),
76
                owning_address=dockerfile_info.address,
77
                description_of_origin=softwrap(
78
                    f"""
79
                    the COPY arguments from the file {dockerfile_info.source}
80
                    from the target {dockerfile_info.address}
81
                    """
82
                ),
83
                skip_invalid_addresses=True,
84
            ),
85
            **implicitly(),
86
        )
87
    )
88
    maybe_output_paths = set(dockerfile_info.copy_source_paths) | set(
×
89
        dockerfile_info.copy_build_args.nonempty().values()
90
    )
91

92
    # NB: There's no easy way of knowing the output path's default file ending as there could
93
    # be none or it could be dynamic. Instead of forcing clients to tell us, we just use all the
94
    # possible ones from the Dockerfile. In rare cases we over-infer, but it is relatively harmless.
95
    # NB: The suffix gets an `or None` `pathlib` includes the ".", but `OutputPathField` doesn't
96
    # expect it (if you give it "", it'll leave a trailing ".").
97
    possible_file_endings = {PurePath(path).suffix[1:] or None for path in maybe_output_paths}
×
98
    inferred_addresses = []
×
99
    for target in all_packageable_targets:
×
100
        # If the target is an image we depend on, add it
101
        if target.address in putative_image_addresses:
×
102
            inferred_addresses.append(target.address)
×
103
            continue
×
104

105
        # If the target looks like it could generate the file we're trying to COPY
106
        output_path_field = target.get(OutputPathField)
×
107
        possible_output_paths = {
×
108
            output_path_field.value_or_default(file_ending=file_ending)
109
            for file_ending in possible_file_endings
110
        }
111
        for output_path in possible_output_paths:
×
112
            if output_path in maybe_output_paths:
×
113
                inferred_addresses.append(target.address)
×
114
                break
×
115

116
        # If the target has the same address as an ARG that will eventually be copied
117
        if target.address in putative_copy_target_addresses:
×
118
            inferred_addresses.append(target.address)
×
119

120
    # add addresses from source paths if they are files directly
121
    addresses_from_source_paths = await resolve_targets(
×
122
        **implicitly(
123
            RawSpecs(
124
                description_of_origin="halp",
125
                unmatched_glob_behavior=GlobMatchErrorBehavior.ignore,
126
                file_literals=tuple(
127
                    FileLiteralSpec(e)
128
                    for e in [
129
                        *dockerfile_info.copy_source_paths,
130
                        *dockerfile_info.copy_build_args.nonempty().values(),
131
                    ]
132
                ),
133
            )
134
        )
135
    )
136

137
    inferred_addresses.extend(e.address for e in addresses_from_source_paths)
×
138

139
    return InferredDependencies(Addresses(inferred_addresses))
×
140

141

UNCOV
142
def rules():
×
UNCOV
143
    return [
×
144
        *collect_rules(),
145
        UnionRule(InferDependenciesRequest, InferDockerDependencies),
146
    ]
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