• 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

58.46
/src/python/pants/bsp/spec/compile.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 BuildTargetIdentifier
1✔
9

10
# -----------------------------------------------------------------------------------------------
11
# Compile Request
12
# See https://build-server-protocol.github.io/docs/specification.html#compile-request
13
# -----------------------------------------------------------------------------------------------
14

15

16
@dataclass(frozen=True)
1✔
17
class CompileParams:
1✔
18
    # A sequence of build targets to compile.
19
    targets: tuple[BuildTargetIdentifier, ...]
1✔
20

21
    # A unique identifier generated by the client to identify this request.
22
    # The server may include this id in triggered notifications or responses.
23
    origin_id: str | None = None
1✔
24

25
    # Optional arguments to the compilation process.
26
    arguments: tuple[str, ...] | None = ()
1✔
27

28
    @classmethod
1✔
29
    def from_json_dict(cls, d: dict[str, Any]) -> Any:
1✔
UNCOV
30
        return cls(
×
31
            targets=tuple(BuildTargetIdentifier.from_json_dict(x) for x in d["targets"]),
32
            origin_id=d.get("originId"),
33
            arguments=tuple(d["arguments"]) if "arguments" in d else None,
34
        )
35

36
    def to_json_dict(self) -> dict[str, Any]:
1✔
UNCOV
37
        result: dict[str, Any] = {"targets": [tgt.to_json_dict() for tgt in self.targets]}
×
UNCOV
38
        if self.origin_id is not None:
×
39
            result["originId"] = self.origin_id
×
UNCOV
40
        if self.arguments is not None:
×
UNCOV
41
            result["arguments"] = self.arguments
×
UNCOV
42
        return result
×
43

44

45
@dataclass(frozen=True)
1✔
46
class CompileResult:
1✔
47
    # An optional request id to know the origin of this report.
48
    origin_id: str | None
1✔
49

50
    # A status code for the execution.
51
    status_code: int
1✔
52

53
    # Kind of data to expect in the `data` field. If this field is not set, the kind of data is not specified.
54
    data_kind: str | None = None
1✔
55

56
    # A field containing language-specific information, like products
57
    # of compilation or compiler-specific metadata the client needs to know.
58
    data: Any | None = None
1✔
59

60
    @classmethod
1✔
61
    def from_json_dict(cls, d: dict[str, Any]) -> Any:
1✔
UNCOV
62
        return cls(
×
63
            origin_id=d.get("originId"),
64
            status_code=d["statusCode"],
65
            data_kind=d.get("dataKind"),
66
            data=d.get("data"),
67
        )
68

69
    def to_json_dict(self) -> dict[str, Any]:
1✔
UNCOV
70
        result: dict[str, Any] = {
×
71
            "statusCode": self.status_code,
72
        }
UNCOV
73
        if self.origin_id is not None:
×
74
            result["originId"] = self.origin_id
×
UNCOV
75
        if self.data_kind is not None:
×
76
            result["dataKind"] = self.data_kind
×
UNCOV
77
        if self.data is not None:
×
78
            result["data"] = self.data  # TODO: Enforce to_json_dict available
×
UNCOV
79
        return result
×
80

81

82
@dataclass(frozen=True)
1✔
83
class CompileTask:
1✔
84
    target: BuildTargetIdentifier
1✔
85

86
    @classmethod
1✔
87
    def from_json_dict(cls, d: dict[str, Any]) -> Any:
1✔
88
        return cls(target=BuildTargetIdentifier.from_json_dict(d["target"]))
×
89

90
    def to_json_dict(self) -> dict[str, Any]:
1✔
91
        return {"target": self.target.to_json_dict()}
×
92

93

94
@dataclass(frozen=True)
1✔
95
class CompileReport:
1✔
96
    # The build target that was compiled
97
    target: BuildTargetIdentifier
1✔
98

99
    # An optional request id to know the origin of this report.
100
    origin_id: str | None
1✔
101

102
    # The total number of reported errors compiling this target.
103
    errors: int
1✔
104

105
    # The total number of reported warnings compiling the target.
106
    warnings: int
1✔
107

108
    # The total number of milliseconds it took to compile the target.
109
    time: int | None = None
1✔
110

111
    # The compilation was a noOp compilation.
112
    no_op: bool | None = None
1✔
113

114
    @classmethod
1✔
115
    def from_json_dict(cls, d: dict[str, Any]) -> Any:
1✔
116
        return cls(
×
117
            target=BuildTargetIdentifier.from_json_dict(d["target"]),
118
            origin_id=d.get("originId"),
119
            errors=d["errors"],
120
            warnings=d["warnings"],
121
            time=d.get("time"),
122
            no_op=d.get("noOp"),
123
        )
124

125
    def to_json_dict(self) -> dict[str, Any]:
1✔
126
        result = {
×
127
            "target": self.target.to_json_dict(),
128
            "errors": self.errors,
129
            "warnings": self.warnings,
130
        }
131
        if self.origin_id is not None:
×
132
            result["originId"] = self.origin_id
×
133
        if self.time is not None:
×
134
            result["time"] = self.time
×
135
        if self.no_op is not None:
×
136
            result["noOp"] = self.no_op
×
137
        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