• 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/nfpm/util_rules/inject_config.py
1
# Copyright 2025 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 abc import ABC, abstractmethod
×
UNCOV
7
from collections.abc import Iterable
×
UNCOV
8
from dataclasses import dataclass
×
9

UNCOV
10
from pants.backend.nfpm.fields.scripts import NfpmPackageScriptsField
×
UNCOV
11
from pants.engine.environment import EnvironmentName
×
UNCOV
12
from pants.engine.internals.native_engine import Address, Field
×
UNCOV
13
from pants.engine.rules import collect_rules, implicitly, rule
×
UNCOV
14
from pants.engine.target import Target
×
UNCOV
15
from pants.engine.unions import UnionMembership, union
×
UNCOV
16
from pants.util.frozendict import FrozenDict
×
UNCOV
17
from pants.util.strutil import softwrap
×
18

19

UNCOV
20
@dataclass(frozen=True)
×
UNCOV
21
class InjectedNfpmPackageFields:
×
22
    """The injected fields that should be used instead of the target's fields.
23

24
    Though any field can technically be provided (except "scripts" which is banned),
25
    only nfpm package metadata fields will have an impact. Passing other fields are
26
    silently ignored. For example, "dependencies", and "output_path" are not used
27
    when generating nfpm config, so they will be ignored; "sources" is not a valid
28
    field for nfpm package targets, so it will also be ignored.
29

30
    The "scripts" field is special in that it has dependency inference tied to it.
31
    If you write your own dependency inference rule (possibly based on a custom
32
    field you've added to the nfpm package target), then you can pass
33
    _allow_banned_fields=True to allow injection of the "scripts" field.
34
    """
35

UNCOV
36
    field_values: FrozenDict[type[Field], Field]
×
37

UNCOV
38
    def __init__(
×
39
        self,
40
        fields: Iterable[Field],
41
        *,
42
        address: Address,
43
        _allow_banned_fields: bool = False,
44
    ) -> None:
45
        super().__init__()
×
46
        if not _allow_banned_fields:
×
47
            aliases = [field.alias for field in fields]
×
48
            for alias in {
×
49
                NfpmPackageScriptsField.alias,  # if _allow_banned_fields, the plugin author must handle scripts deps.
50
            }:
51
                if alias in aliases:
×
52
                    raise ValueError(
×
53
                        softwrap(
54
                            f"""
55
                            {alias} cannot be an injected nfpm package field for {address} to avoid
56
                            breaking dependency inference.
57
                            """
58
                        )
59
                    )
60
        # Ignore any fields that do not have a value (assuming nfpm fields have 'none_is_valid_value=False').
61
        field_values = {type(field): field for field in fields if field.value is not None}
×
62
        object.__setattr__(
×
63
            self,
64
            "field_values",
65
            FrozenDict(
66
                sorted(
67
                    field_values.items(),
68
                    key=lambda field_type_to_val_pair: field_type_to_val_pair[0].alias,
69
                )
70
            ),
71
        )
72

73

74
# Note: This only exists as a hook for additional logic for nFPM config generation, e.g. for plugin
75
# authors. To resolve `InjectedNfpmPackageFields`, call `determine_injected_nfpm_package_fields`,
76
# which handles running any custom implementations vs. using the default implementation.
UNCOV
77
@union(in_scope_types=[EnvironmentName])
×
UNCOV
78
@dataclass(frozen=True)
×
UNCOV
79
class InjectNfpmPackageFieldsRequest(ABC):
×
80
    """A request to inject nFPM config for nfpm_package_* targets.
81

82
    By default, Pants will use the nfpm_package_* fields in the BUILD file unchanged to generate the
83
    nfpm.yaml config file for nFPM. To customize this, subclass `InjectNfpmPackageFieldsRequest`,
84
    register `UnionRule(InjectNfpmPackageFieldsRequest, MyCustomInjectNfpmPackageFieldsRequest)`,
85
    and add a rule that takes your subclass as a parameter and returns `InjectedNfpmPackageFields`.
86
    """
87

UNCOV
88
    target: Target
×
89

UNCOV
90
    @classmethod
×
UNCOV
91
    @abstractmethod
×
UNCOV
92
    def is_applicable(cls, target: Target) -> bool:
×
93
        """Whether to use this InjectNfpmPackageFieldsRequest implementation for this target."""
94

95

UNCOV
96
@rule(polymorphic=True)
×
UNCOV
97
async def inject_nfpm_package_fields(
×
98
    req: InjectNfpmPackageFieldsRequest, env_name: EnvironmentName
99
) -> InjectedNfpmPackageFields:
100
    raise NotImplementedError()
×
101

102

UNCOV
103
@dataclass(frozen=True)
×
UNCOV
104
class NfpmPackageTargetWrapper:
×
105
    """Nfpm Package target Wrapper.
106

107
    This is not meant to be used by plugin authors.
108
    """
109

UNCOV
110
    target: Target
×
111

112

UNCOV
113
@rule
×
UNCOV
114
async def determine_injected_nfpm_package_fields(
×
115
    wrapper: NfpmPackageTargetWrapper, union_membership: UnionMembership
116
) -> InjectedNfpmPackageFields:
117
    target = wrapper.target
×
118
    inject_nfpm_config_requests = union_membership.get(InjectNfpmPackageFieldsRequest)
×
119
    applicable_inject_nfpm_config_requests = tuple(
×
120
        request for request in inject_nfpm_config_requests if request.is_applicable(target)
121
    )
122

123
    # If no provided implementations, fall back to our default implementation that simply returns
124
    # what the user explicitly specified in the BUILD file.
125
    if not applicable_inject_nfpm_config_requests:
×
126
        return InjectedNfpmPackageFields((), address=target.address)
×
127

128
    if len(applicable_inject_nfpm_config_requests) > 1:
×
129
        possible_requests = sorted(
×
130
            plugin.__name__ for plugin in applicable_inject_nfpm_config_requests
131
        )
132
        raise ValueError(
×
133
            softwrap(
134
                f"""
135
                Multiple registered `InjectNfpmPackageFieldsRequest`s can work on the target
136
                {target.address}, and it's ambiguous which to use: {possible_requests}
137

138
                Please activate fewer implementations, or make the classmethod `is_applicable()`
139
                more precise so that only one implementation is applicable for this target.
140
                """
141
            )
142
        )
143
    inject_nfpm_config_request_type = applicable_inject_nfpm_config_requests[0]
×
144
    inject_nfpm_config_request: InjectNfpmPackageFieldsRequest = inject_nfpm_config_request_type(
×
145
        target
146
    )  # type: ignore[abstract]
147
    return await inject_nfpm_package_fields(
×
148
        **implicitly({inject_nfpm_config_request: InjectNfpmPackageFieldsRequest})
149
    )
150

151

UNCOV
152
def rules():
×
UNCOV
153
    return [
×
154
        *collect_rules(),
155
    ]
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