RPM packager's cheat sheet

Published on ; updated on

These are some random notes on creating RPM packages that I wrote some time ago for my own use.

The RPM build tree

On Fedora, the default rpm build tree is ~/rpmbuild.

ls ~/rpmbuild 
BUILD/
BUILDROOT/
RPMS/
SOURCES/
SPECS/
SRPMS/

You can create this directory structure with rpmdev-setuptree from the rpmdevtools package.

The purpose of each directory is:

To change the location of the build tree, put this into ~/.rpmmacros:

%_topdir %{getenv:HOME}/myrpmbuild

Build stages

The -b option selects the stage at which to stop. Except for -bs, each option runs its stage and all preceding stages.

-bp: %prep

Unpack the source tarballs and apply patches. If you have any sources to unpack, try to find a %setup invocation that does the trick. The flags to %setup are described elsewhere.

-bc: %build

Run %prep and then build the software, usually with make or another build tool.

-bi: %install and %check

Run %prep and %build, install the built files into %{buildroot}, and run the test suite, if there is one. Do not install files directly into the system.

-bl: %files

Run all preceding stages, expand the %files section, and verify that every listed file exists.

-bb: binary package assembly

Run all preceding stages and create the binary RPMs.

-bs: source package assembly

Create the source RPM without running any of the build stages.

-ba: source and binary package assembly

Run all stages and create both the source and binary RPMs.

SRPMs

To customize a package that already exists in Fedora (for instance, to update to a newer upstream version), it is convenient to download and install an srpm (also known as src.rpm).

First, download an srpm from dl.fedoraproject.org (substitute your release number) to ~/rpmbuild/SRPMS. Alternatively, use the program yumdownloader from the dnf-utils package:

yumdownloader --destdir ~/rpmbuild/SRPMS --source packagename

Next, install it into your build tree:

rpm -i ~/rpmbuild/SRPMS/packagename.src.rpm

Now the spec file should appear under ~/rpmbuild/SPECS and sources under ~/rpmbuild/SOURCES.

Extract dependencies from a spec file and install them

dnf builddep package.spec

If you’re getting an error Unknown argument "builddep" for command "dnf5", install the dnf5-plugins package.

RPM macros

Evaluate an RPM macro expression:

rpm -E '%_topdir'

Show all macro definitions:

rpm --showrc

It will also show where the definitions are located: look for something like Macro path: /usr/lib/rpm/macros:/usr/lib/rpm/macros.d/macros.*:/usr/lib/rpm/platform/%{_target}/macros:/usr/lib/rpm/fileattrs/*.attr:/usr/lib/rpm/redhat/macros:/etc/rpm/macros.*:/etc/rpm/macros:/etc/rpm/%{_target}/macros:~/.rpmmacros.

Unfortunately, it doesn’t seem to show where each particular macro comes from.

Defining your own

It’s often useful to define variables in the SPEC file.

This can be done with either %define or %global, and %global is preferred:

%define commit a0a746442daf8dfca01859f1efcc8663b47b3705
%global commit a0a746442daf8dfca01859f1efcc8663b47b3705

Minimal RPM spec file

I usually start with the minimal spec file:

Name:       minimal
Version:    1
Release:    1%{?dist}
Summary:    Minimal package
License:    MIT

%description
...

%prep

%build

%install

%files
/*

RPM has many defaults and convenient macros suited for traditional ./configure && make && make install tarballs, but nowadays those are actually rare.

Sources

If you have any source files, they should be declared (usually above %description) like this:

URL:        ftp://ftp.ncbi.nlm.nih.gov/entrez/entrezdirect/edirect.zip
Source0:    edirect.zip

You can then download the sources automatically using spectool:

spectool -C ~/rpmbuild/SOURCES -g package.spec

Sometimes I don’t declare any sources and instead use a tool (such as pypi or gem) to download and install the software. This is not reproducible, and therefore unacceptable when creating public packages, but is usually fine for personal use.

GitHub sources

Here’s an example of using a specific commit from a github repo.

See also the pages on SourceURL for git and versioning snapshots.

%global   commit a0a746442daf8dfca01859f1efcc8663b47b3705
%global   shortcommit %(c=%{commit}; echo ${c:0:7})
Release:  1.%{shortcommit}
Source0:  https://github.com/lwindolf/%{name}/archive/%{commit}/%{name}-%{commit}.tar.gz

%prep
%setup -n %{name}-%{commit}

Auto-provides

A likely reason why your shared library is not detected by the “automatic provides” mechanism is that it is not executable.

Add something like this to your %install section:

find %buildroot -type f \( -name '*.so' -o -name '*.so.*' \) -exec chmod 755 {} +

Disable debuginfo

Put this in the spec file:

%global debug_package %{nil}

rpmbuild invocation tips

Incremental builds

--build-in-place skips %prep and builds the sources in the current working tree. --noclean retains build artifacts after the build. Together, they make iterative local builds faster:

rpmbuild -bb --build-in-place --noclean package.spec

Use a clean build without these options before publishing a package.

Limiting parallelism

%make_build uses %_smp_build_ncpus to decide how many jobs to run. To cap parallelism for one build, override it on the command line:

rpmbuild -bb --define '_smp_build_ncpus 2' package.spec

To apply the cap to every build of a package, add this to its spec file:

%global _smp_build_ncpus 2