• 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

74.77
/src/python/pants/bsp/spec/targets.py
1
# Copyright 2022 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 enum import IntEnum
1✔
7
from typing import Any
1✔
8

9
from pants.bsp.spec.base import BSPData, BuildTarget, BuildTargetIdentifier, Uri
1✔
10

11
# -----------------------------------------------------------------------------------------------
12
# Workspace Build Targets Request
13
# See https://build-server-protocol.github.io/docs/specification.html#workspace-build-targets-request
14
# -----------------------------------------------------------------------------------------------
15

16

17
@dataclass(frozen=True)
1✔
18
class WorkspaceBuildTargetsParams:
1✔
19
    @classmethod
1✔
20
    def from_json_dict(cls, _d):
1✔
UNCOV
21
        return cls()
×
22

23
    def to_json_dict(self):
1✔
UNCOV
24
        return {}
×
25

26

27
@dataclass(frozen=True)
1✔
28
class WorkspaceBuildTargetsResult:
1✔
29
    targets: tuple[BuildTarget, ...]
1✔
30

31
    @classmethod
1✔
32
    def from_json_dict(cls, d):
1✔
UNCOV
33
        return cls(targets=tuple(BuildTarget.from_json_dict(tgt) for tgt in d["targets"]))
×
34

35
    def to_json_dict(self):
1✔
UNCOV
36
        return {"targets": [tgt.to_json_dict() for tgt in self.targets]}
×
37

38

39
# -----------------------------------------------------------------------------------------------
40
# Build Target Sources Request
41
# See https://build-server-protocol.github.io/docs/specification.html#build-target-sources-request
42
# -----------------------------------------------------------------------------------------------
43

44

45
@dataclass(frozen=True)
1✔
46
class SourcesParams:
1✔
47
    targets: tuple[BuildTargetIdentifier, ...]
1✔
48

49
    @classmethod
1✔
50
    def from_json_dict(cls, d):
1✔
UNCOV
51
        return cls(
×
52
            targets=tuple(BuildTargetIdentifier.from_json_dict(x) for x in d["targets"]),
53
        )
54

55
    def to_json_dict(self):
1✔
UNCOV
56
        return {
×
57
            "targets": [tgt.to_json_dict() for tgt in self.targets],
58
        }
59

60

61
class SourceItemKind(IntEnum):
1✔
62
    FILE = 1
1✔
63
    DIRECTORY = 2
1✔
64

65

66
@dataclass(frozen=True)
1✔
67
class SourceItem:
1✔
68
    uri: Uri
1✔
69
    kind: SourceItemKind
1✔
70
    generated: bool = False
1✔
71

72
    @classmethod
1✔
73
    def from_json_dict(cls, d: Any):
1✔
UNCOV
74
        return cls(
×
75
            uri=d["uri"],
76
            kind=SourceItemKind(d["kind"]),
77
            generated=d["generated"],
78
        )
79

80
    def to_json_dict(self):
1✔
UNCOV
81
        return {
×
82
            "uri": self.uri,
83
            "kind": self.kind.value,
84
            "generated": self.generated,
85
        }
86

87

88
@dataclass(frozen=True)
1✔
89
class SourcesItem:
1✔
90
    target: BuildTargetIdentifier
1✔
91
    sources: tuple[SourceItem, ...]
1✔
92
    roots: tuple[Uri, ...] | None
1✔
93

94
    @classmethod
1✔
95
    def from_json_dict(cls, d: Any):
1✔
UNCOV
96
        return cls(
×
97
            target=BuildTargetIdentifier.from_json_dict(d["target"]),
98
            sources=tuple(SourceItem.from_json_dict(i) for i in d["sources"]),
99
            roots=tuple(d.get("sources", ())),
100
        )
101

102
    def to_json_dict(self):
1✔
UNCOV
103
        result = {
×
104
            "target": self.target.to_json_dict(),
105
            "sources": [src.to_json_dict() for src in self.sources],
106
        }
UNCOV
107
        if self.roots is not None:
×
UNCOV
108
            result["roots"] = list(self.roots)
×
UNCOV
109
        return result
×
110

111

112
@dataclass(frozen=True)
1✔
113
class SourcesResult:
1✔
114
    items: tuple[SourcesItem, ...]
1✔
115

116
    @classmethod
1✔
117
    def from_json_dict(cls, d: Any):
1✔
UNCOV
118
        return cls(
×
119
            items=tuple(SourcesItem.from_json_dict(i) for i in d["items"]),
120
        )
121

122
    def to_json_dict(self):
1✔
UNCOV
123
        return {"items": [item.to_json_dict() for item in self.items]}
×
124

125

126
# -----------------------------------------------------------------------------------------------
127
# Dependency Sources Request
128
# See https://build-server-protocol.github.io/docs/specification.html#dependency-sources-request
129
# -----------------------------------------------------------------------------------------------
130

131

132
@dataclass(frozen=True)
1✔
133
class DependencySourcesParams:
1✔
134
    targets: tuple[BuildTargetIdentifier, ...]
1✔
135

136
    @classmethod
1✔
137
    def from_json_dict(cls, d):
1✔
UNCOV
138
        return cls(
×
139
            targets=tuple(BuildTargetIdentifier.from_json_dict(x) for x in d["targets"]),
140
        )
141

142
    def to_json_dict(self):
1✔
UNCOV
143
        return {
×
144
            "targets": [tgt.to_json_dict() for tgt in self.targets],
145
        }
146

147

148
@dataclass(frozen=True)
1✔
149
class DependencySourcesItem:
1✔
150
    target: BuildTargetIdentifier
1✔
151
    # List of resources containing source files of the
152
    # target's dependencies.
153
    # Can be source files, jar files, zip files, or directories.
154
    sources: tuple[Uri, ...]
1✔
155

156
    def to_json_dict(self) -> dict[str, Any]:
1✔
UNCOV
157
        return {
×
158
            "target": self.target.to_json_dict(),
159
            "sources": self.sources,
160
        }
161

162

163
@dataclass(frozen=True)
1✔
164
class DependencySourcesResult:
1✔
165
    items: tuple[DependencySourcesItem, ...]
1✔
166

167
    def to_json_dict(self):
1✔
UNCOV
168
        return {"items": [item.to_json_dict() for item in self.items]}
×
169

170

171
# -----------------------------------------------------------------------------------------------
172
# Dependency Modules Request
173
# See https://build-server-protocol.github.io/docs/specification.html#dependency-modules-request
174
# -----------------------------------------------------------------------------------------------
175

176

177
@dataclass(frozen=True)
1✔
178
class DependencyModulesParams:
1✔
179
    targets: tuple[BuildTargetIdentifier, ...]
1✔
180

181
    @classmethod
1✔
182
    def from_json_dict(cls, d):
1✔
183
        return cls(
×
184
            targets=tuple(BuildTargetIdentifier.from_json_dict(x) for x in d["targets"]),
185
        )
186

187
    def to_json_dict(self):
1✔
188
        return {
×
189
            "targets": [tgt.to_json_dict() for tgt in self.targets],
190
        }
191

192

193
@dataclass(frozen=True)
1✔
194
class DependencyModule:
1✔
195
    # Module name
196
    name: str
1✔
197

198
    # Module version
199
    version: str
1✔
200

201
    # Language-specific metadata about this module.
202
    # See MavenDependencyModule as an example.
203
    data: BSPData | None
1✔
204

205
    def to_json_dict(self) -> dict[str, Any]:
1✔
206
        result: dict[str, Any] = {
×
207
            "name": self.name,
208
            "version": self.version,
209
        }
210
        if self.data is not None:
×
211
            result["dataKind"] = self.data.DATA_KIND
×
212
            result["data"] = self.data.to_json_dict()
×
213
        return result
×
214

215

216
@dataclass(frozen=True)
1✔
217
class DependencyModulesItem:
1✔
218
    target: BuildTargetIdentifier
1✔
219
    modules: tuple[DependencyModule, ...]
1✔
220

221
    def to_json_dict(self) -> dict[str, Any]:
1✔
222
        return {
×
223
            "target": self.target.to_json_dict(),
224
            "modules": [m.to_json_dict() for m in self.modules],
225
        }
226

227

228
@dataclass(frozen=True)
1✔
229
class DependencyModulesResult:
1✔
230
    items: tuple[DependencyModulesItem, ...]
1✔
231

232
    def to_json_dict(self):
1✔
233
        return {"items": [item.to_json_dict() for item in self.items]}
×
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