For more than a decade, the setup.py
file was the cornerstone of Python packages. When packaging RPM packages for Red Hat Enterprise Linux (RHEL), RPM spec files invoked the setup.py
script in %build
and %install
sections, typically using the %py3_build
and %py3_install
RPM macros. Every Python project relied on either the standard library's distutils
module or the more advanced and widely used setuptools
package.
Python RPM packaging for RHEL 9 and 10 has evolved beyond the traditional setup.py
approach. This article explores how the new pyproject RPM macros simplify packaging modern Python projects by supporting diverse build backends and reusing upstream metadata. Whether you're maintaining legacy packages or adopting the latest Python standards, you can use the pyproject RPM macros from the RHEL CRB repository.
How Python packaging has improved
Python packaging has undergone a massive overhaul by standardizing a build-system independent format for source trees and storing project metadata in pyproject.toml
. The distutils module has been deprecated and removed from the Python standard library. The setup.py
script has been deprecated in setuptools
. Upstream projects now use various build backends, including flit-core
, hatchling
, poetry-core
, and even the traditional setuptools
.
As a result, it is often not possible to RPM-package upstream Python projects—whether from the Python Package Index (PyPI) or internal package indexes the same way as in RHEL 7 or RHEL 8.
In Fedora, we created pyproject-rpm-macros, a set of RPM macros designed to package standards-based Python projects into RPM packages. These macros are available in the RHEL 9 and 10 CodeReady Linux Builder (CRB) repository. This allows us to frequently update them to newer versions, often following emerging Python packaging standards—most recently, support for dependency groups in pyproject.toml
.
The pyproject RPM macros are designed with the following principles in mind:
- Support standards over specific tools.
- Reuse upstream metadata whenever possible instead of duplicating it in the spec file.
For example, the old way of RPM-packaging Python invoked setup.py build
, which only supported distutils
/setuptools
-based projects. The pyproject RPM macros instead use a standardized protocol to invoke any standard-compliant Python build backend. This approach remains compatible with traditional projects still using setup.py
by falling back to setuptools
.
Another example of metadata reuse is dynamically generating BuildRequires
based on upstream metadata instead of listing them manually in the spec file.
The following is an example spec file.
Name: python-example Version: 1.2.3 Release: 1%{?dist} Summary: Example Python library License: MIT URL: https://.com/fedora-python/example Source: %{url}/archive/v%{version}/example-%{version}.tar.gz BuildArch: noarch %description %_description ... %package -n python3-example Summary: %{summary} %description -n python3-example ... %prep %autosetup -p1 -n example-%{version} # Dynamically generates BuildRequires, including: # - Build backend dependencies (PEP 518) # - Runtime dependencies for tests # - Dependencies from a dependency group (PEP 735) %generate_buildrequires %pyproject_buildrequires -g test # Build the wheel package using the upstream-specified build backend (PEP 517) %build %pyproject_wheel # Install the wheel package and save the file list %install %pyproject_install # Explicitly list the installed importable module to avoid accidental installations. # Use -l to assert a %%license file is found (PEP 639). %pyproject_save_files -l example # Test importability and run pytest # There is no standard yet for running tests %check %pyproject_check_import %pytest # Use the upstream-generated file list in the %%files section %files -n python3-example -f %{pyproject_files} %changelog ...
This approach also works seamlessly with alternate Python versions by setting the following:
%global python3_pkgversion 3.12
Next steps
The new pyproject RPM macros makes packaging modern Python projects easier by supporting diverse build backends and reusing upstream metadata. For more details about the macros, refer to the Fedora Python packaging guidelines or the pyproject-rpm-macros README.