• 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

0.0
/src/python/pants/backend/python/packaging/pyoxidizer/config.py
1
# Copyright 2022 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

UNCOV
4
from __future__ import annotations
×
5

UNCOV
6
from dataclasses import dataclass
×
UNCOV
7
from string import Template
×
UNCOV
8
from textwrap import indent
×
9

UNCOV
10
DEFAULT_TEMPLATE = """
×
11
def make_exe():
12
    dist = default_python_distribution()
13
    policy = dist.make_python_packaging_policy()
14
    policy.extension_module_filter = "no-copyleft"
15

16
    # Note: Adding this for pydantic and libs that have the "unable to load from memory" error
17
    # https://github.com/indygreg/PyOxidizer/issues/438
18
    policy.resources_location_fallback = "filesystem-relative:lib"
19

20
    python_config = dist.make_python_interpreter_config()
21

22
    $RUN_MODULE
23

24
    exe = dist.to_python_executable(
25
        name="$NAME",
26
        packaging_policy=policy,
27
        config=python_config,
28
    )
29

30
    exe.add_python_resources(exe.pip_install($WHEELS))
31
    $UNCLASSIFIED_RESOURCE_INSTALLATION
32

33
    return exe
34

35
def make_embedded_resources(exe):
36
    return exe.to_embedded_resources()
37

38
def make_install(exe):
39
    # Create an object that represents our installed application file layout.
40
    files = FileManifest()
41
    # Add the generated executable to our install layout in the root directory.
42
    files.add_python_resource(".", exe)
43
    return files
44

45
register_target("exe", make_exe)
46
register_target("resources", make_embedded_resources, depends=["exe"], default_build_script=True)
47
register_target("install", make_install, depends=["exe"], default=True)
48
resolve_targets()
49
"""
50

UNCOV
51
UNCLASSIFIED_RESOURCES_TEMPLATE = """
×
52
for resource in exe.pip_install($UNCLASSIFIED_RESOURCES):
53
    resource.add_location = "filesystem-relative:lib"
54
    exe.add_python_resource(resource)
55
"""
56

57

UNCOV
58
@dataclass(frozen=True)
×
UNCOV
59
class PyOxidizerConfig:
×
UNCOV
60
    executable_name: str
×
UNCOV
61
    wheels: list[str]
×
UNCOV
62
    entry_point: str | None = None
×
UNCOV
63
    template: str | None = None
×
UNCOV
64
    unclassified_resources: list[str] | None = None
×
65

UNCOV
66
    @property
×
UNCOV
67
    def run_module(self) -> str:
×
UNCOV
68
        return (
×
69
            f"python_config.run_module = '{self.entry_point}'"
70
            if self.entry_point is not None
71
            else ""
72
        )
73

UNCOV
74
    def render(self) -> str:
×
UNCOV
75
        unclassified_resource_snippet = ""
×
UNCOV
76
        if self.unclassified_resources is not None:
×
UNCOV
77
            unclassified_resource_snippet = Template(
×
78
                UNCLASSIFIED_RESOURCES_TEMPLATE
79
            ).safe_substitute(UNCLASSIFIED_RESOURCES=self.unclassified_resources)
80

UNCOV
81
            unclassified_resource_snippet = indent(unclassified_resource_snippet, "    ")
×
82

UNCOV
83
        template = Template(self.template or DEFAULT_TEMPLATE)
×
UNCOV
84
        return template.safe_substitute(
×
85
            NAME=self.executable_name,
86
            WHEELS=self.wheels,
87
            RUN_MODULE=self.run_module,
88
            UNCLASSIFIED_RESOURCE_INSTALLATION=unclassified_resource_snippet,
89
        )
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