• 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/fields/rpm.py
1
# Copyright 2023 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 enum import Enum
×
UNCOV
7
from typing import ClassVar
×
8

UNCOV
9
from pants.backend.nfpm.config import NfpmContent
×
UNCOV
10
from pants.backend.nfpm.fields._relationships import NfpmPackageRelationshipsField
×
UNCOV
11
from pants.backend.nfpm.fields.all import NfpmDependencies
×
UNCOV
12
from pants.backend.nfpm.fields.scripts import NfpmPackageScriptsField
×
UNCOV
13
from pants.engine.addresses import Address
×
UNCOV
14
from pants.engine.target import InvalidFieldException, StringField, StringSequenceField
×
UNCOV
15
from pants.util.frozendict import FrozenDict
×
UNCOV
16
from pants.util.strutil import help_text
×
17

18
# These fields are used by the `nfpm_rpm_package` target
19
# Some fields will be duplicated by other package types which
20
# allows the help string to be packager-specific.
21

22

23
# Internally, nFPM just uses this to populate the default for 'packager'.
24
# So, there is no point exposing both in our UX.
25
# class NfpmRpmMaintainerField(StringField):
26
#     alias = "maintainer"
27

28

UNCOV
29
class NfpmRpmPackagerField(StringField):
×
UNCOV
30
    nfpm_alias = "rpm.packager"  # nFPM uses value of 'maintainer' as default.
×
UNCOV
31
    alias: ClassVar[str] = "packager"
×
UNCOV
32
    help = help_text(
×
33
        # based in part on the docs at:
34
        # https://nfpm.goreleaser.com/configuration/#reference
35
        lambda: f"""
36
        The name and email address of the packager or packager organization.
37

38
        The '{NfpmRpmPackagerField.alias}' is used to identify who actually
39
        packaged the software, as opposed to the author of the software.
40

41
        The name is first, then the email address inside angle brackets `<>`
42
        (in RFC5322 format). For example: "Foo Bar <maintainer@example.com>"
43
        This format is the conventional format; it not a hard requirement.
44

45
        See: https://ftp.osuosl.org/pub/rpm/max-rpm/s1-rpm-inside-tags.html#S3-RPM-INSIDE-PACKAGER-TAG
46

47
        N.B.: Packages distributed by Fedora do not use this field.
48
        https://docs.fedoraproject.org/en-US/packaging-guidelines/#_tags_and_sections
49
        """
50
    )
51
    # nFPM embeds this string as-is in the rpm package.
52

53

UNCOV
54
class NfpmRpmVendorField(StringField):
×
UNCOV
55
    nfpm_alias = "vendor"
×
UNCOV
56
    alias: ClassVar[str] = nfpm_alias
×
UNCOV
57
    help = help_text(
×
58
        """
59
        The entity responsible for packaging (typically an organization).
60

61
        See: https://ftp.osuosl.org/pub/rpm/max-rpm/s1-rpm-inside-tags.html#S3-RPM-INSIDE-VENDOR-TAG
62

63
        N.B.: Packages distributed by Fedora do not use this field.
64
        https://docs.fedoraproject.org/en-US/packaging-guidelines/#_tags_and_sections
65
        """
66
    )
67

68

UNCOV
69
class NfpmRpmGroupField(StringField):
×
UNCOV
70
    nfpm_alias = "rpm.group"
×
UNCOV
71
    alias: ClassVar[str] = "group"
×
UNCOV
72
    help = help_text(
×
73
        lambda: f"""
74
        For older rpm-based distros, this groups packages by their functionality.
75

76
        '{NfpmRpmGroupField.alias}' is a path-like string to allow for hierarchical
77
        grouping of applications like "Applications/Editors".
78

79
        See: https://ftp.osuosl.org/pub/rpm/max-rpm/s1-rpm-inside-tags.html#S3-RPM-INSIDE-GROUP-TAG
80

81
        N.B.: This field is only useful when packaging for old distros (EL 5 or earlier).
82
        All newer rpm-based distros have deprecated--and do not use--this field.
83
        https://docs.fedoraproject.org/en-US/packaging-guidelines/#_tags_and_sections
84
        """
85
    )
86

87

UNCOV
88
class NfpmRpmSummaryField(StringField):
×
UNCOV
89
    nfpm_alias = "rpm.summary"
×
UNCOV
90
    alias: ClassVar[str] = "summary"
×
UNCOV
91
    help = help_text(
×
92
        lambda: f"""
93
        A one-line description of the packaged software.
94

95
        If unset, nFPM will use the first line of 'description' for
96
        the '{NfpmRpmSummaryField.alias}'.
97

98
        See: https://ftp.osuosl.org/pub/rpm/max-rpm/s1-rpm-inside-tags.html#S3-RPM-INSIDE-SUMMARY-TAG
99
        """
100
    )
101

102

UNCOV
103
class NfpmRpmPrefixesField(StringSequenceField):
×
UNCOV
104
    nfpm_alias = "rpm.prefixes"
×
UNCOV
105
    alias: ClassVar[str] = "prefixes"
×
UNCOV
106
    help = help_text(
×
107
        lambda: f"""
108
        A list of relocatable prefixes (to support relocatable rpms).
109

110
        Defining '{NfpmRpmPrefixesField.alias}' allows rpm to install your package
111
        at an alternative prefix if the user requests that using the `--prefix` flag.
112
        This list is the default list of prefixes used by this package.
113
        If rpm relocates an installation of this package, it will strip off each of
114
        these prefixes, replacing them with the `--prefix` requested by the user.
115

116
        Before using this, ensure that any packaged software can actually run
117
        from alternate prefixes, and watch out for any absolute symlinks targets which
118
        might not be relocated correctly.
119

120
        RPM specs use the `Prefix:` tag to list each prefix in this list.
121

122
        See:
123
        https://rpm-software-management.github.io/rpm/manual/tags.html#packages-with-files
124
        https://ftp.osuosl.org/pub/rpm/max-rpm/ch-rpm-reloc.html
125
        https://ftp.osuosl.org/pub/rpm/max-rpm/s1-rpm-inside-tags.html#S3-RPM-INSIDE-PREFIX-TAG
126
        """
127
    )
128

129

UNCOV
130
class NfpmRpmReplacesField(NfpmPackageRelationshipsField):
×
UNCOV
131
    nfpm_alias = "replaces"
×
UNCOV
132
    alias: ClassVar[str] = nfpm_alias
×
UNCOV
133
    help = help_text(
×
134
        lambda: f"""
135
        A list of packages that this package obsoletes (replaces).
136

137
        When a pacakge name changes or splits, rpm uses "obsoletes" (ie the
138
        '{NfpmRpmReplacesField.alias}' field) on the new package to list the old
139
        package name(s) that can be upgraded to this package.
140

141
        The rpm file header uses the term "obsoletes" for this. This field is
142
        named "{NfpmRpmReplacesField.alias}" because that is the term used by nFPM.
143

144
        See:
145
        https://rpm-software-management.github.io/rpm/manual/dependencies.html#obsoletes
146
        """
147
    )
148

149

UNCOV
150
class NfpmRpmProvidesField(NfpmPackageRelationshipsField):
×
UNCOV
151
    nfpm_alias = "provides"
×
UNCOV
152
    alias: ClassVar[str] = nfpm_alias
×
UNCOV
153
    help = help_text(
×
154
        lambda: f"""
155
        A list of virtual packages or file paths that this package provides.
156

157
        This is used so that multiple packages can be be alternatives for each other.
158
        The list can include virtual package names and/or file paths. For example
159
        the `bash` package includes these in '{NfpmRpmProvidesField.alias}':
160

161
          - "bash"
162
          - "/bin/sh"
163
          - "/bin/bash"
164

165
        This means another package could also provide alternative implementations for
166
        the "bash" package name and could provide "/bin/sh" and/or "/bin/bash".
167

168
        N.B.: Virtual package names must not include any version numbers.
169

170
        See:
171
        https://rpm-software-management.github.io/rpm/manual/dependencies.html#provides
172
        https://ftp.osuosl.org/pub/rpm/max-rpm/s1-rpm-depend-manual-dependencies.html#S2-RPM-DEPEND-PROVIDES-TAG
173
        """
174
    )
175

176

UNCOV
177
class NfpmRpmDependsField(NfpmPackageRelationshipsField):
×
UNCOV
178
    nfpm_alias = "depends"
×
UNCOV
179
    alias: ClassVar[str] = nfpm_alias
×
UNCOV
180
    help = help_text(
×
181
        lambda: f"""
182
        List of package requirements (for package installers).
183

184
        The '{NfpmRpmDependsField.alias}' field has install-time requirements that can
185
        use version selectors (with one of `<`, `<=`, `=`, `>=`, `>` surrounded by
186
        spaces), where the version is formatted: `[epoch:]version[-release]`
187

188
          - "git"
189
          - "bash < 5"
190
          - "perl >= 9:5.00502-3"
191

192
        The rpm file header uses the term "requires" for this. This field is
193
        named "{NfpmRpmDependsField.alias}" because that is the term used by nFPM.
194

195
        WARNING: This is NOT the same as the 'dependencies' field!
196
        It does not accept pants-style dependencies like target addresses.
197

198
        See:
199
        https://rpm-software-management.github.io/rpm/manual/dependencies.html#requires
200
        https://rpm-software-management.github.io/rpm/manual/dependencies.html#versioning
201
        https://ftp.osuosl.org/pub/rpm/max-rpm/s1-rpm-inside-tags.html#S3-RPM-INSIDE-REQUIRES-TAG
202
        https://ftp.osuosl.org/pub/rpm/max-rpm/s1-rpm-depend-manual-dependencies.html#S2-RPM-DEPEND-REQUIRES-TAG
203
        """
204
    )
205

206

UNCOV
207
class NfpmRpmRecommendsField(NfpmPackageRelationshipsField):
×
UNCOV
208
    nfpm_alias = "recommends"
×
UNCOV
209
    alias: ClassVar[str] = nfpm_alias
×
UNCOV
210
    help = help_text(
×
211
        lambda: f"""
212
        List of weak package requirements (for package installers).
213

214
        This is like the '{NfpmRpmDependsField.alias}' field, but the package resolver
215
        can ignore the requirement if it cannot resolve the packages with it included.
216
        If an entry in '{NfpmRpmRecommendsField.alias}' is ignored, no error or warning gets
217
        reported.
218

219
        The '{NfpmRpmRecommendsField.alias}' field has the same syntax as the
220
        '{NfpmRpmDependsField.alias}' field.
221

222
        See:
223
        https://rpm-software-management.github.io/rpm/manual/dependencies.html#weak-dependencies
224
        """
225
    )
226

227

UNCOV
228
class NfpmRpmSuggestsField(NfpmPackageRelationshipsField):
×
UNCOV
229
    nfpm_alias = "suggests"
×
UNCOV
230
    alias: ClassVar[str] = nfpm_alias
×
UNCOV
231
    help = help_text(
×
232
        lambda: f"""
233
        List of very weak package requirements (for package installers).
234

235
        These suggestions are ignored by the package resolver. They are merely shown
236
        to the user as optional packages that the user might want to also install.
237

238
        The '{NfpmRpmSuggestsField.alias}' field has the same syntax as the
239
        '{NfpmRpmDependsField.alias}' field.
240

241
        See:
242
        https://rpm-software-management.github.io/rpm/manual/dependencies.html#weak-dependencies
243
        """
244
    )
245

246

UNCOV
247
class NfpmRpmConflictsField(NfpmPackageRelationshipsField):
×
UNCOV
248
    nfpm_alias = "conflicts"
×
UNCOV
249
    alias: ClassVar[str] = nfpm_alias
×
UNCOV
250
    help = help_text(
×
251
        lambda: f"""
252
        A list of packages that this package conflicts with.
253

254
        Packages that conflict with each other cannot be installed at the same time.
255

256
        The '{NfpmRpmConflictsField.alias}' field has the same syntax as the
257
        '{NfpmRpmDependsField.alias}' field.
258

259
        See:
260
        https://rpm-software-management.github.io/rpm/manual/dependencies.html#conflicts
261
        https://docs.fedoraproject.org/en-US/packaging-guidelines/Conflicts/
262
        https://ftp.osuosl.org/pub/rpm/max-rpm/s1-rpm-inside-tags.html#S3-RPM-INSIDE-CONFLICTS-TAG
263
        https://ftp.osuosl.org/pub/rpm/max-rpm/s1-rpm-depend-manual-dependencies.html#S2-RPM-DEPEND-CONFLICTS-TAG
264
        """
265
    )
266

267

UNCOV
268
class NfpmRpmCompressionAlgorithm(Enum):
×
269
    # This is what nFPM implements.
UNCOV
270
    gzip = "gzip"
×
UNCOV
271
    lzma = "lzma"
×
UNCOV
272
    xz = "xz"
×
UNCOV
273
    zstd = "zstd"
×
274

275

UNCOV
276
class NfpmRpmCompressionField(StringField):
×
UNCOV
277
    nfpm_alias = "rpm.compression"
×
UNCOV
278
    alias: ClassVar[str] = "compression"
×
UNCOV
279
    valid_choices = NfpmRpmCompressionAlgorithm
×
UNCOV
280
    default = f"{NfpmRpmCompressionAlgorithm.gzip.value}:-1"  # same default as nFPM
×
UNCOV
281
    help = help_text(
×
282
        lambda: f"""
283
        The compression algorithm to use on the rpm package.
284

285
        This takes a compression algorithm and optionally a compression level.
286
        To specify a level, use 'algorithm:level'. Specifying a compression level
287
        is only valid for '{NfpmRpmCompressionAlgorithm.gzip.value}' or
288
        '{NfpmRpmCompressionAlgorithm.zstd.value}'.
289

290
        Here are several gzip examples with and without the optional compression level
291
        (-1 means use the default level which is 5; 9 is the max).
292

293
          - '{NfpmRpmCompressionAlgorithm.gzip.value}:9'
294
          - '{NfpmRpmCompressionAlgorithm.gzip.value}:0'
295
          - '{NfpmRpmCompressionAlgorithm.gzip.value}:-1'
296
          - '{NfpmRpmCompressionAlgorithm.gzip.value}:5'
297
          - '{NfpmRpmCompressionAlgorithm.gzip.value}'
298

299
        Here are several zstd examples. Note that nFPM uses a library that  only
300
        defines four named compression levels, and then maps the zstd integer
301
        levels to those. You may specify the zstd level as an integer, or using
302
        these names: https://github.com/klauspost/compress/tree/master/zstd#status
303

304
          - '{NfpmRpmCompressionAlgorithm.zstd.value}:fastest'
305
          - '{NfpmRpmCompressionAlgorithm.zstd.value}:default'
306
          - '{NfpmRpmCompressionAlgorithm.zstd.value}:better'
307
          - '{NfpmRpmCompressionAlgorithm.zstd.value}:best'
308
          - '{NfpmRpmCompressionAlgorithm.zstd.value}:3'
309
          - '{NfpmRpmCompressionAlgorithm.zstd.value}:9'
310
          - '{NfpmRpmCompressionAlgorithm.zstd.value}'
311
        """
312
    )
313

UNCOV
314
    @classmethod
×
UNCOV
315
    def compute_value(cls, raw_value: str | None, address: Address) -> str | None:
×
UNCOV
316
        if raw_value is None:
×
317
            # valid_choices has only algorithms, not compression level.
318
            # So, return default value, skipping check for default in valid_choices.
UNCOV
319
            return cls.default
×
320

321
        # We only need to do custom computation if raw_value has the optional level.
322
        if not (isinstance(raw_value, cls.expected_type) and ":" in raw_value):
×
323
            # If defined, only algorithm was provided, not level.
324
            # If not defined, this will apply the default.
325
            return super().compute_value(raw_value, address)
×
326

327
        # ":" is in raw_value so both an algorithm and level were provided.
328
        raw_algorithm, level, *unknown = raw_value.split(":")
×
329

330
        # This will check algorithm against the algorithm Enum.
331
        computed_algorithm = super().compute_value(raw_algorithm, address)
×
332
        if computed_algorithm not in ("gzip", "zstd"):
×
333
            raise InvalidFieldException(
×
334
                f"Values for the '{cls.alias}' field in target {address} "
335
                "may only specify a compression level for gzip or zstd compression, "
336
                f"but {repr(computed_algorithm)} was provided as {repr(raw_value)}."
337
            )
338

339
        if unknown:
×
340
            raise InvalidFieldException(
×
341
                f"Values for the '{cls.alias}' field in target {address} "
342
                f"must not have more than one ':', but got {len(unknown) + 1}."
343
                "Only use ':' to specify an optional compression level after "
344
                "the compression algorithm (ie '<algorithm>[:<level>]')."
345
            )
346

347
        # Pass level as-is w/o sanitization or type checking because
348
        # there are too many possible levels to check it sanely here.
349
        return f"{computed_algorithm}:{level}"
×
350

351

UNCOV
352
class NfpmRpmScriptsField(NfpmPackageScriptsField):
×
UNCOV
353
    nfpm_aliases: ClassVar[FrozenDict[str, str]] = FrozenDict(
×
354
        {
355
            **NfpmPackageScriptsField.nfpm_aliases,
356
            "pretrans": "rpm.scripts.pretrans",
357
            "posttrans": "rpm.scripts.posttrans",
358
            "verify": "rpm.scripts.verify",
359
        }
360
    )
UNCOV
361
    help = help_text(
×
362
        f"""
363
        Map of install scriptlet source files for the deb package.
364

365
        This maps the script type (key) to the script source file (value).
366
        Each of the script source file(s) must be provided via '{NfpmDependencies.alias}'.
367
        The script types are the names used by nFPM. For reference, RPM
368
        uses the following scriptlet tag names instead and runs them before/after
369
        the indicated phase:
370

371
        | nFPM term   | RPM scriptlet | RPM phase   |
372
        +-------------+---------------+-------------+
373
        | preinstall  | %pre          | install     |
374
        | postinstall | %post         | install     |
375
        | preremove   | %preun        | uninstall   |
376
        | postremove  | %postun       | uninstall   |
377
        | pretrans    | %pretrans     | transaction |
378
        | posttrans   | %posttrans    | transaction |
379
        | verify      | %verifyscript | verify      |
380

381
        Please consult the RPM docs to understand what is required of these scripts.
382

383
        See:
384
        https://docs.fedoraproject.org/en-US/packaging-guidelines/Scriptlets/
385
        https://rpm-software-management.github.io/rpm/manual/tags.html#scriptlets
386
        https://ftp.osuosl.org/pub/rpm/max-rpm/s1-rpm-inside-scripts.html#S2-RPM-INSIDE-ERASE-TIME-SCRIPTS
387
        """
388
    )
389

390

UNCOV
391
class NfpmRpmGhostContents(StringSequenceField):
×
UNCOV
392
    nfpm_alias = ""  # does not map directly to a nfpm.yaml field
×
UNCOV
393
    alias = "ghost_contents"
×
UNCOV
394
    help = help_text(
×
395
        """
396
        A list of files that this package owns, but that this package does not include.
397

398
        Examples of ghosted files include:
399
          - A log file or a state file that does not exist until runtime.
400
          - A binary that is managed by 'alternatives'.
401

402
        RPM specs use the `%ghost` directive to list these ghosted files.
403

404
        Each file in this list gets passed to nFPM via the 'contents' field with
405
        'type=ghost'. Then nFPM translates that into the appropriate RPM header.
406
        The file does not need to exist in your pants workspace as nFPM directly
407
        adds it to the RPM header.
408

409
        See: https://ftp.osuosl.org/pub/rpm/max-rpm/s1-rpm-inside-files-list-directives.html#S3-RPM-INSIDE-FLIST-GHOST-DIRECTIVE
410

411
        N.B.: Packages distributed by Fedora must use this if they provide 'alternatives'.
412
        https://docs.fedoraproject.org/en-US/packaging-guidelines/Alternatives/#_how_to_use_alternatives
413
        """
414
    )
415
    # nFPM normalizes paths to be absolute, so "" is effectively the same as "/".
416
    # But this does not validate the paths, leaving it up to nFPM to do any validation
417
    # that a specific packager might require.
418

UNCOV
419
    @property
×
UNCOV
420
    def nfpm_contents(self) -> list[NfpmContent]:
×
UNCOV
421
        contents = [NfpmContent(type="ghost", dst=ghost) for ghost in self.value or ()]
×
UNCOV
422
        return contents
×
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