Packaging software for RPM-based distros

Packaging software for RPM-based distros

Welcome to the world of RPMs! The RPM Package Manager is a powerful package management system utilized by modern Linux distros such as Fedora and OpenSUSE. Despite the vast repositories of RPM packages readily available, you may desire to use some software so niche or novel that it requires packaging by your very own hand, so let's learn how! I wrote previously about my project, toolboxcutter, which facilitates the creation of RPM packages using the nifty rpkg tool, and here I will describe how I use toolboxcutter to test RPM SPEC files locally before turning them into distributable RPM packages using the Fedora project's Copr build system. ✨

Writing SPEC files

RPM packages are produced using SPEC files as input, which specify the build and installation processes of some software in addition to metadata such as its name and version. Let's look at Clight as a case study. Here's its SPEC file:

Given such a SPEC file, it's possible to generate an RPM package using the rpmbuild tool, and this is a valid way of going about it. However, I use toolboxcutter because it builds packages in a podman container, meaning:

  • Build dependencies won't litter your base system

  • You won't miss a dependency specification that is coincidentally already installed

I wrote about using toolboxcutter for this purpose here!

The general procedure begins like this:

git clone https://github.com/FedeDP/Clight
cd Clight
tb init rpkg-toolbox

This tb command seen above is toolboxcutter, and calling init will create and open a Dockerfile.toolbox file in this repo's directory, built from rpkg-toolbox, which is a local podman image with rpkg and other packages installed. This is its Dockerfile:

FROM registry.fedoraproject.org/fedora-toolbox:37

RUN dnf install -y neovim
RUN dnf install -y rpkg
RUN dnf install -y rpmdevtools
RUN dnf install -y zsh

RUN ln -s /usr/bin/nvim /usr/local/bin/vim

CMD /usr/bin/zsh

toolboxcutter will eventually build Clight in the container specified by the Dockerfile in its directory, so let's add the required build dependencies:

FROM rpkg-toolbox

RUN dnf install -y cmake
RUN dnf install -y g++
RUN dnf install -y systemd-devel
RUN dnf install -y popt-devel
RUN dnf install -y libconfig-devel
RUN dnf install -y gsl-devel
RUN dnf install -y dbus-devel
RUN dnf copr enable -y jcrd/libmodule
RUN dnf install -y libmodule

Thankfully, Clight's dependencies are recorded in its documentation. For other software, some trial and error may be necessary to determine what's required. The next step is to specify these dependencies in the rpkg SPEC template file. rpkg's documentation describes how to get started with such a template.

In the directory containing the to-be-packaged software's source, let's create a subdirectory and the rpkg SPEC template file:

mkdir spec
touch spec/clight.rpkg.spec

Now, edit the created file, filling in the SPEC template details. Clight's looks like this:

As you can see, it resembles the final SPEC file but contains some rpkg macros enclosed in {{{ }}}. These allow the production of an RPM package directly from the source repository where this template resides. The installed files listed under the %files section may not be obvious without first attempting to build the package.

From outside the toolbox container, run this command:

tb rpkg-install

Without the proper installed files list, you will receive an error such as:

error: Installed (but unpackaged) file(s) found:
   /etc/default/clight.conf
   /usr/bin/clight
   /usr/etc/xdg/autostart/clight.desktop
   /usr/include/clight/public.h
   /usr/share/applications/clightc.desktop
   /usr/share/bash-completion/completions/clight
   /usr/share/clight/inhibit_bl.skel
   /usr/share/clight/nightmode.skel
   /usr/share/clight/synctemp_bumblebee.skel
   /usr/share/dbus-1/services/org.clight.clight.service
   /usr/share/icons/hicolor/scalable/apps/clight.svg
   /usr/share/man/man1/clight.1.gz
   /usr/share/zsh/site-functions/_clight

You can fill in the %files section with these paths using the appropriate RPM macros as seen above. Rerunning toolboxcutter's rpkg-install command should now produce and install an RPM package.

Finally, convert the rpkg SPEC template into a standalone spec file by replacing rpkg's {{{ }}} macros with their concrete values. rpkg can handle this automatically to an extent (with rpkg spec), with the caveat being: the package Source will be directly derived from the software's hosted source code repository, whereas you probably want to specify a source code archive associated with a released version of this software.

Compare the aforementioned SPEC file with the above rpkg SPEC template if you need help determining which values belong where. Once this is complete, you have a working SPEC file capable of producing an installable RPM package! 💪

Building distributable RPM packages

With the SPEC file in hand, building a distributable RPM package is easy using Copr. It's similar in theory to Ubuntu's ppas. You can find more information about the Copr build system here. An alternative might be OBS (Open Build Service) but I have not yet used it myself.

This tutorial illustrates the process of creating a new Copr project and initiating a build. However, instead of specifying SRPM files by URL, you can provide the URL of a SPEC file. I host my SPEC files on gist.github.com for revision tracking and ease of editing. Provide the URL to the raw .spec file, start the build, and if everything goes according to plan, you'll see that it succeeded! Check out Clight's project to see what this looks like.

Now, on your local machine, enable your newly created Copr repository and install the RPM package:

dnf copr enable jcrd/clight
dnf install Clight

Voilà! This package is now distributable: it can be installed on any system for which it was built by enabling its Copr repository. 👏

This pairs especially well with reproducible distro configurations!