• 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

61.4
/src/python/pants/backend/nfpm/native_libs/rules.py
1
# Copyright 2025 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
from __future__ import annotations
1✔
5

6
from collections.abc import Iterable
1✔
7
from dataclasses import dataclass
1✔
8

9
from pants.backend.nfpm.field_sets import NfpmRpmPackageFieldSet
1✔
10
from pants.backend.nfpm.fields.rpm import NfpmRpmDependsField, NfpmRpmProvidesField
1✔
11
from pants.backend.nfpm.native_libs.elfdeps.rules import RequestPexELFInfo, elfdeps_analyze_pex
1✔
12
from pants.backend.nfpm.native_libs.elfdeps.rules import rules as elfdeps_rules
1✔
13
from pants.backend.nfpm.util_rules.contents import (
1✔
14
    GetPackageFieldSetsForNfpmContentFileDepsRequest,
15
    get_package_field_sets_for_nfpm_content_file_deps,
16
)
17
from pants.backend.nfpm.util_rules.inject_config import (
1✔
18
    InjectedNfpmPackageFields,
19
    InjectNfpmPackageFieldsRequest,
20
)
21
from pants.backend.python.goals.package_pex_binary import PexBinaryFieldSet, package_pex_binary
1✔
22
from pants.backend.python.util_rules.pex import Pex, create_pex
1✔
23
from pants.backend.python.util_rules.pex_from_targets import PexFromTargetsRequest
1✔
24
from pants.engine.addresses import Address
1✔
25
from pants.engine.internals.selectors import concurrently
1✔
26
from pants.engine.rules import Rule, collect_rules, implicitly, rule
1✔
27
from pants.engine.target import Field, Target
1✔
28
from pants.engine.unions import UnionMembership, UnionRule
1✔
29

30

31
@dataclass(frozen=True)
1✔
32
class RpmDependsFromPexRequest:
1✔
33
    target_pex: Pex
1✔
34

35

36
@dataclass(frozen=True)
1✔
37
class RpmDependsInfo:
1✔
38
    provides: tuple[str, ...]
1✔
39
    requires: tuple[str, ...]
1✔
40

41

42
@rule
1✔
43
async def rpm_depends_from_pex(request: RpmDependsFromPexRequest) -> RpmDependsInfo:
1✔
44
    # This rule provides a platform-agnostic replacement for `rpmdeps` in native rpm builds.
NEW
45
    pex_elf_info = await elfdeps_analyze_pex(RequestPexELFInfo(request.target_pex), **implicitly())
×
NEW
46
    return RpmDependsInfo(
×
47
        provides=tuple(provided.so_info for provided in pex_elf_info.provides),
48
        requires=tuple(required.so_info for required in pex_elf_info.requires),
49
    )
50

51

52
async def _get_pex_deps_of_content_file_targets(
1✔
53
    address: Address, union_membership: UnionMembership
54
) -> tuple[Pex, ...]:
55
    """Get all pex_binary targets that are (transitive) deps of nfpm_content_file targets."""
NEW
56
    package_field_sets_for_nfpm_content_files = (
×
57
        await get_package_field_sets_for_nfpm_content_file_deps(
58
            GetPackageFieldSetsForNfpmContentFileDepsRequest([address], [PexBinaryFieldSet]),
59
            **implicitly(),
60
        )
61
    )
NEW
62
    pex_binary_field_sets = package_field_sets_for_nfpm_content_files.package_field_sets
×
NEW
63
    build_pex_requests = await concurrently(
×
64
        package_pex_binary(field_set, **implicitly())
65
        for field_set in pex_binary_field_sets.field_sets
66
    )
NEW
67
    pex_binaries = await concurrently(
×
68
        create_pex(**implicitly({pex_request.request: PexFromTargetsRequest}))
69
        for pex_request in build_pex_requests
70
    )
NEW
71
    return pex_binaries
×
72

73

74
class NativeLibsNfpmPackageFieldsRequest(InjectNfpmPackageFieldsRequest):
1✔
75
    # low priority to allow in-repo plugins to override/correct injected dependencies.
76
    priority = 2
1✔
77

78
    @classmethod
1✔
79
    def is_applicable(cls, target: Target) -> bool:
1✔
NEW
80
        return NfpmRpmPackageFieldSet.is_applicable(target)
×
81

82

83
@rule
1✔
84
async def inject_native_libs_dependencies_in_package_fields(
1✔
85
    request: NativeLibsNfpmPackageFieldsRequest,
86
    union_membership: UnionMembership,
87
) -> InjectedNfpmPackageFields:
NEW
88
    address = request.target.address
×
89

NEW
90
    fields: list[Field] = list(request.injected_fields.values())
×
91

92
    # TODO: support scanning more packaged binaries than just pex_binaries.
93

NEW
94
    pex_binaries = await _get_pex_deps_of_content_file_targets(address, union_membership)
×
NEW
95
    if not pex_binaries:
×
NEW
96
        return InjectedNfpmPackageFields(fields, address=address)
×
97

NEW
98
    if NfpmRpmPackageFieldSet.is_applicable(request.target):
×
99
        # This is like running rpmdeps -> elfdeps.
NEW
100
        rpm_depends_infos = await concurrently(
×
101
            rpm_depends_from_pex(RpmDependsFromPexRequest(pex)) for pex in pex_binaries
102
        )
NEW
103
        provides = list(request.get_field(NfpmRpmProvidesField).value or ())
×
NEW
104
        depends = list(request.get_field(NfpmRpmDependsField).value or ())
×
NEW
105
        for rpm_depends_info in rpm_depends_infos:
×
NEW
106
            provides.extend(rpm_depends_info.provides)
×
NEW
107
            depends.extend(rpm_depends_info.requires)
×
NEW
108
        fields.extend(
×
109
            [
110
                NfpmRpmProvidesField(provides, address=address),
111
                NfpmRpmDependsField(depends, address=address),
112
            ]
113
        )
114

NEW
115
    return InjectedNfpmPackageFields(fields, address=address)
×
116

117

118
def rules() -> Iterable[Rule | UnionRule]:
1✔
119
    return (
1✔
120
        *elfdeps_rules(),
121
        *collect_rules(),
122
        UnionRule(InjectNfpmPackageFieldsRequest, NativeLibsNfpmPackageFieldsRequest),
123
    )
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