• 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

62.28
/src/python/pants/bsp/spec/lifecycle.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 typing import Any
1✔
7

8
from pants.bsp.spec.base import Uri
1✔
9
from pants.bsp.utils import freeze_json
1✔
10

11

12
@dataclass(frozen=True)
1✔
13
class BuildClientCapabilities:
1✔
14
    # The languages that this client supports.
15
    # The ID strings for each language are defined in the LSP.
16
    # The server must never respond with build targets for other
17
    # languages than those that appear in this list.
18
    language_ids: tuple[str, ...]
1✔
19

20
    @classmethod
1✔
21
    def from_json_dict(cls, d):
1✔
UNCOV
22
        return cls(language_ids=tuple(d.get("languageIds", [])))
×
23

24
    def to_json_dict(self):
1✔
UNCOV
25
        return {
×
26
            "languageIds": self.language_ids,
27
        }
28

29

30
@dataclass(frozen=True)
1✔
31
class InitializeBuildParams:
1✔
32
    # Name of the client
33
    display_name: str
1✔
34

35
    # The version of the client
36
    version: str
1✔
37

38
    # The BSP version that the client speaks
39
    bsp_version: str
1✔
40

41
    # The rootUri of the workspace
42
    root_uri: Uri
1✔
43

44
    # The capabilities of the client
45
    capabilities: BuildClientCapabilities
1✔
46

47
    # Additional metadata about the client
48
    data: Any | None
1✔
49

50
    @classmethod
1✔
51
    def from_json_dict(cls, d):
1✔
UNCOV
52
        return cls(
×
53
            display_name=d["displayName"],
54
            version=d["version"],
55
            bsp_version=d["bspVersion"],
56
            root_uri=d["rootUri"],
57
            capabilities=BuildClientCapabilities.from_json_dict(d["capabilities"]),
58
            data=freeze_json(d.get("data")),
59
        )
60

61
    def to_json_dict(self):
1✔
UNCOV
62
        result = {
×
63
            "displayName": self.display_name,
64
            "version": self.version,
65
            "bspVersion": self.bsp_version,
66
            "rootUri": self.root_uri,
67
            "capabilities": self.capabilities.to_json_dict(),
68
        }
UNCOV
69
        if self.data is not None:
×
UNCOV
70
            result["data"] = self.data
×
UNCOV
71
        return result
×
72

73

74
@dataclass(frozen=True)
1✔
75
class CompileProvider:
1✔
76
    language_ids: tuple[str, ...]
1✔
77

78
    @classmethod
1✔
79
    def from_json_dict(cls, d):
1✔
UNCOV
80
        return cls(language_ids=tuple(d.get("languageIds", [])))
×
81

82
    def to_json_dict(self):
1✔
UNCOV
83
        return {
×
84
            "languageIds": self.language_ids,
85
        }
86

87

88
@dataclass(frozen=True)
1✔
89
class RunProvider:
1✔
90
    language_ids: tuple[str, ...]
1✔
91

92
    @classmethod
1✔
93
    def from_json_dict(cls, d):
1✔
UNCOV
94
        return cls(language_ids=tuple(d.get("languageIds", [])))
×
95

96
    def to_json_dict(self):
1✔
UNCOV
97
        return {
×
98
            "languageIds": self.language_ids,
99
        }
100

101

102
@dataclass(frozen=True)
1✔
103
class DebugProvider:
1✔
104
    language_ids: tuple[str, ...]
1✔
105

106
    @classmethod
1✔
107
    def from_json_dict(cls, d):
1✔
UNCOV
108
        return cls(language_ids=tuple(d.get("languageIds", [])))
×
109

110
    def to_json_dict(self):
1✔
UNCOV
111
        return {
×
112
            "languageIds": self.language_ids,
113
        }
114

115

116
@dataclass(frozen=True)
1✔
117
class TestProvider:
1✔
118
    language_ids: tuple[str, ...]
1✔
119

120
    @classmethod
1✔
121
    def from_json_dict(cls, d):
1✔
UNCOV
122
        return cls(language_ids=tuple(d.get("languageIds", [])))
×
123

124
    def to_json_dict(self):
1✔
UNCOV
125
        return {
×
126
            "languageIds": self.language_ids,
127
        }
128

129

130
@dataclass(frozen=True)
1✔
131
class BuildServerCapabilities:
1✔
132
    # The languages the server supports compilation via method buildTarget/compile.
133
    compile_provider: CompileProvider | None
1✔
134

135
    # The languages the server supports test execution via method buildTarget/test
136
    test_provider: TestProvider | None
1✔
137

138
    # The languages the server supports run via method buildTarget/run
139
    run_provider: RunProvider | None
1✔
140

141
    # The languages the server supports debugging via method debugSession/start
142
    debug_provider: DebugProvider | None
1✔
143

144
    # The server can provide a list of targets that contain a
145
    # single text document via the method buildTarget/inverseSources
146
    inverse_sources_provider: bool | None
1✔
147

148
    # The server provides sources for library dependencies
149
    # via method buildTarget/dependencySources
150
    dependency_sources_provider: bool | None
1✔
151

152
    # The server can provide a list of dependency modules (libraries with meta information)
153
    # via method buildTarget/dependencyModules
154
    dependency_modules_provider: bool | None
1✔
155

156
    # The server provides all the resource dependencies
157
    # via method buildTarget/resources
158
    resources_provider: bool | None
1✔
159

160
    # Reloading the build state through workspace/reload is supported
161
    can_reload: bool | None
1✔
162

163
    # The server sends notifications to the client on build
164
    # target change events via buildTarget/didChange
165
    build_target_changed_provider: bool | None
1✔
166

167
    @classmethod
1✔
168
    def from_json_dict(cls, d):
1✔
UNCOV
169
        return cls(
×
170
            compile_provider=(
171
                CompileProvider.from_json_dict(d["compileProvider"])
172
                if "compileProvider" in d
173
                else None
174
            ),
175
            test_provider=(
176
                TestProvider.from_json_dict(d["testProvider"]) if "testProvider" in d else None
177
            ),
178
            run_provider=(
179
                RunProvider.from_json_dict(d["runProvider"]) if "runProvider" in d else None
180
            ),
181
            debug_provider=(
182
                DebugProvider.from_json_dict(d["debugProvider"]) if "debugProvider" in d else None
183
            ),
184
            inverse_sources_provider=d.get("inverseSourcesProvider"),
185
            dependency_sources_provider=d.get("dependencySourcesProvider"),
186
            dependency_modules_provider=d.get("dependencyModulesProvider"),
187
            resources_provider=d.get("resourcesProvider"),
188
            can_reload=d.get("canReload"),
189
            build_target_changed_provider=d.get("buildTargetChangedProvider"),
190
        )
191

192
    def to_json_dict(self):
1✔
UNCOV
193
        result = {}
×
UNCOV
194
        if self.compile_provider is not None:
×
UNCOV
195
            result["compileProvider"] = self.compile_provider.to_json_dict()
×
UNCOV
196
        if self.test_provider is not None:
×
UNCOV
197
            result["testProvider"] = self.test_provider.to_json_dict()
×
UNCOV
198
        if self.run_provider is not None:
×
UNCOV
199
            result["runProvider"] = self.run_provider.to_json_dict()
×
UNCOV
200
        if self.debug_provider is not None:
×
UNCOV
201
            result["debugProvider"] = self.debug_provider.to_json_dict()
×
UNCOV
202
        if self.inverse_sources_provider is not None:
×
203
            result["inverseSourcesProvider"] = self.inverse_sources_provider
×
UNCOV
204
        if self.dependency_sources_provider is not None:
×
UNCOV
205
            result["dependencySourcesProvider"] = self.dependency_sources_provider
×
UNCOV
206
        if self.dependency_modules_provider is not None:
×
UNCOV
207
            result["dependencyModulesProvider"] = self.dependency_modules_provider
×
UNCOV
208
        if self.resources_provider is not None:
×
UNCOV
209
            result["resourcesProvider"] = self.resources_provider
×
UNCOV
210
        if self.can_reload is not None:
×
211
            result["canReload"] = self.can_reload
×
UNCOV
212
        if self.build_target_changed_provider is not None:
×
213
            result["buildTargetChangedProvider"] = self.build_target_changed_provider
×
UNCOV
214
        return result
×
215

216

217
@dataclass(frozen=True)
1✔
218
class InitializeBuildResult:
1✔
219
    # Name of the server
220
    display_name: str
1✔
221

222
    # The version of the server
223
    version: str
1✔
224

225
    # The BSP version that the server speaks
226
    bsp_version: str
1✔
227

228
    # The capabilities of the build server
229
    capabilities: BuildServerCapabilities
1✔
230

231
    # Additional metadata about the server
232
    data: Any | None
1✔
233

234
    @classmethod
1✔
235
    def from_json_dict(cls, d):
1✔
UNCOV
236
        return cls(
×
237
            display_name=d["displayName"],
238
            version=d["version"],
239
            bsp_version=d["bspVersion"],
240
            capabilities=BuildServerCapabilities.from_json_dict(d["capabilities"]),
241
            data=d.get("data"),
242
        )
243

244
    def to_json_dict(self):
1✔
UNCOV
245
        result = {
×
246
            "displayName": self.display_name,
247
            "version": self.version,
248
            "bspVersion": self.bsp_version,
249
            "capabilities": self.capabilities.to_json_dict(),
250
        }
UNCOV
251
        if self.data is not None:
×
252
            # TODO: Figure out whether to encode/decode data in a generic manner.
253
            result["data"] = self.data
×
UNCOV
254
        return result
×
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