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

pantsbuild / pants / 19529437518

20 Nov 2025 07:44AM UTC coverage: 78.884% (-1.4%) from 80.302%
19529437518

push

github

web-flow
nfpm.native_libs: Add RPM package depends from packaged pex_binaries (#22899)

## PR Series Overview

This is the second in a series of PRs that introduces a new backend:
`pants.backend.npm.native_libs`
Initially, the backend will be available as:
`pants.backend.experimental.nfpm.native_libs`

I proposed this new backend (originally named `bindeps`) in discussion
#22396.

This backend will inspect ELF bin/lib files (like `lib*.so`) in packaged
contents (for this PR series, only in `pex_binary` targets) to identify
package dependency metadata and inject that metadata on the relevant
`nfpm_deb_package` or `nfpm_rpm_package` targets. Effectively, it will
provide an approximation of these native packager features:
- `rpm`: `rpmdeps` + `elfdeps`
- `deb`: `dh_shlibdeps` + `dpkg-shlibdeps` (These substitute
`${shlibs:Depends}` in debian control files have)

### Goal: Host-agnostic package builds

This pants backend is designed to be host-agnostic, like
[nFPM](https://nfpm.goreleaser.com/).

Native packaging tools are often restricted to a single release of a
single distro. Unlike native package builders, this new pants backend
does not use any of those distro-specific or distro-release-specific
utilities or local package databases. This new backend should be able to
run (help with building deb and rpm packages) anywhere that pants can
run (MacOS, rpm linux distros, deb linux distros, other linux distros,
docker, ...).

### Previous PRs in series

- #22873

## PR Overview

This PR adds rules in `nfpm.native_libs` to add package dependency
metadata to `nfpm_rpm_package`. The 2 new rules are:

- `inject_native_libs_dependencies_in_package_fields`:

    - An implementation of the polymorphic rule `inject_nfpm_package_fields`.
      This rule is low priority (`priority = 2`) so that in-repo plugins can
      override/augment what it injects. (See #22864)

    - Rule logic overview:
        - find any pex_binaries that will be packaged in an `nfpm_rpm_package`
   ... (continued)

96 of 118 new or added lines in 3 files covered. (81.36%)

910 existing lines in 53 files now uncovered.

73897 of 93678 relevant lines covered (78.88%)

3.21 hits per line

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

71.05
/src/python/pants/option/errors.py
1
# Copyright 2014 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4

5
from pants.option.scope import GLOBAL_SCOPE
11✔
6
from pants.util.strutil import pluralize, softwrap
11✔
7

8

9
class OptionsError(Exception):
11✔
10
    """An options system-related error."""
11

12

13
# -----------------------------------------------------------------------
14
# Option registration errors
15
# -----------------------------------------------------------------------
16

17

18
class RegistrationError(OptionsError):
11✔
19
    """An error at option registration time."""
20

21
    def __init__(self, scope: str, option: str, **msg_format_args) -> None:
11✔
UNCOV
22
        scope_str = "global scope" if scope == GLOBAL_SCOPE else f"scope {scope}"
×
UNCOV
23
        if self.__doc__ is None:
×
24
            raise ValueError(
×
25
                softwrap(
26
                    """
27
                    Invalid RegistrationError definition.
28
                    Please specify the error message in the docstring.
29
                    """
30
                )
31
            )
UNCOV
32
        docstring = self.__doc__.format(**msg_format_args)
×
UNCOV
33
        super().__init__(f"{docstring} [option {option} in {scope_str}].")
×
34

35

36
class BooleanOptionNameWithNo(RegistrationError):
11✔
37
    """Boolean option names cannot start with --no."""
38

39

40
class DefaultValueType(RegistrationError):
11✔
41
    """Default value {value_type}({default_value!r}) does not match option type {option_type}."""
42

43

44
class DefaultMemberValueType(DefaultValueType):
11✔
45
    """Default member value type mismatch.
46

47
    Member value {value_type}({member_value!r}) does not match list option type {member_type}.
48
    """
49

50

51
class HelpType(RegistrationError):
11✔
52
    """The `help=` argument must be a string, but was of type `{help_type}`."""
53

54

55
class InvalidKwarg(RegistrationError):
11✔
56
    """Invalid registration kwarg {kwarg}."""
57

58

59
class InvalidKwargNonGlobalScope(RegistrationError):
11✔
60
    """Invalid registration kwarg {kwarg} on non-global scope."""
61

62

63
class InvalidMemberType(RegistrationError):
11✔
64
    """member_type {member_type} not allowed."""
65

66

67
class MemberTypeNotAllowed(RegistrationError):
11✔
68
    """member_type not allowed on option with type {type_}.
69

70
    It may only be specified if type=list.
71
    """
72

73

74
class NoOptionNames(RegistrationError):
11✔
75
    """No option names provided."""
76

77

78
class OptionAlreadyRegistered(RegistrationError):
11✔
79
    """An option with this name was already registered on this scope."""
80

81

82
class OptionNameDoubleDash(RegistrationError):
11✔
83
    """Option name must begin with a double-dash."""
84

85

86
class PassthroughType(RegistrationError):
11✔
87
    """Options marked passthrough must be typed as a string list."""
88

89

90
# -----------------------------------------------------------------------
91
# Flag parsing errors
92
# -----------------------------------------------------------------------
93

94

95
class ParseError(OptionsError):
11✔
96
    """An error at flag parsing time."""
97

98

99
class BooleanConversionError(ParseError):
11✔
100
    """Indicates a value other than 'True' or 'False' when attempting to parse a bool."""
101

102

103
class FromfileError(ParseError):
11✔
104
    """Indicates a problem reading a value @fromfile."""
105

106

107
class MutuallyExclusiveOptionError(ParseError):
11✔
108
    """Indicates that two options in the same mutually exclusive group were specified."""
109

110

111
class UnknownFlagsError(ParseError):
11✔
112
    """Indicates that unknown command-line flags were encountered in some scope."""
113

114
    def __init__(self, flags: tuple[str, ...], arg_scope: str):
11✔
UNCOV
115
        self.flags = flags
×
UNCOV
116
        self.arg_scope = arg_scope
×
UNCOV
117
        scope = f"scope {self.arg_scope}" if self.arg_scope else "global scope"
×
UNCOV
118
        msg = f"Unknown {pluralize(len(self.flags), 'flag')} {', '.join(self.flags)} on {scope}"
×
UNCOV
119
        super().__init__(msg)
×
120

121

122
# -----------------------------------------------------------------------
123
# Config parsing errors
124
# -----------------------------------------------------------------------
125

126

127
class ConfigError(OptionsError):
11✔
128
    """An error encountered while parsing a config file."""
129

130

131
class ConfigValidationError(ConfigError):
11✔
132
    """A config file is invalid."""
133

134

135
class InterpolationMissingOptionError(ConfigError):
11✔
136
    def __init__(self, option, section, rawval, reference):
11✔
137
        super().__init__(
×
138
            self,
139
            softwrap(
140
                f"""
141
                Bad value substitution: option {option} in section {section} contains an
142
                interpolation key {reference} which is not a valid option name.
143

144
                Raw value: {rawval}
145
                """
146
            ),
147
        )
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