Basics of RPM packaging


One of the core concepts to Linux Distributions is that of the package manager: instead of forcing the user to compile everything from source – or even worse, downloading untrusted binaries from shady third party websites – the distro provides the user with a wide assortment of pre-compiled software that can be easily installed. Package management comes with heaps of various benefits, such as dependency resolution: the package manager automatically installs any libraries or programs that might be required for the user's desired program to work correctly.

In this blog post, I'm going to describe the basics of creating an RPM package. The article assumes you're running Fedora, but save for the parts governing the use of a package manager, any other RPM-based distro should also work.

Installing the packaging tools

user $ su root # dnf install fedora-packager @development-tools

Well, this one's rather obvious. Where we're going, we need proper tooling.

Creating a user for packaging

This step is optional, but highly recommended – when you get started with packaging, you may want to purge some directories, or include a "remove bundled stuff" command in the package build process. A badly written rm -rf or find -exec rm '{}' call might end in a disaster, so in case it ever happens, it's better for the apocalypse to happen on a different user account than the one you use daily.

user $ su root # useradd rpmbuilder root # passwd rpmbuilder

Setting up the environment

This step is fairly simple, as it involves only a single command.

user $ su --login rpmbuilder rpmbuilder $ rpmdev-setuptree rpmbuilder $ ls -p rpmbuild/ rpmbuilder $ ls -p rpmbuild/ BUILD/ BUILDROOT/ RPMS/ SOURCES/ SPECS/ SRPMS/

The names of the directories in the resulting tree are quite self-explanatory, but either way, some commentary might be useful.

Writing a spec file

So, we've set up our build environment and we feel ready to build our first package. In order to do that, we need to write a description of our package – akin to a compiler generating a program based on the code we write, the RPM toolchain needs a description of our package that'll be used to generate it. The package description – or should we say, specification – is provided by a spec file.

Since there's quite a bit of stuff to cover regarding spec files, we'll go through it bit by bit.


The spec file usually begins with a metadata section, which describes the package.
Each part of this metadata is called a tag.

Name: helloworld Version: 1.0 Release: 2%{?dist} Summary: Display a "Hello World!" message   License: zlib URL: Source0: %{URL}/snippets/basics-of-rpm-packaging:helloworld-%{version}.zip # Patch0:   BuildRequires: gcc make # Requires:

Though the tag names are rather self-explanatory, it won't hurt to tell a little about each of them.


You probably noticed the weird %{something} constructs used in the above snippet. What they are is RPM macros. Macros can generally be divided into two groups:

Build instructions

Following the metadata is the description and the build instructions.

%description Display a "Hello world!" message.   %prep %setup -q -n %{name}   %build make %{?_smp_mflags}

The %description section is the place where we can put a longer, multi-line (even multi-paragraph!) description of the package. Basically everything until the next %section is considered part of the description (excessive whitespace at the ends gets removed, obviously).

The %prep section lists steps required to prepare the software for building. Most often this will only unpack the archives and apply the patch files, though we can also perform other steps here, such as removing some unnecessary files or extracting a licence from the source file. In this example, we employ the %setup macro, which is a commonly-used shorthand that extracts all the archives and cds into the proper directory.

The %build section describes how to build the software. This will usually just be a serie of ./configure, make, cmake and other build scripts called in order.

Installing and specifying the list of files

After our software is properly built, we need to specify which files go where.

%install install -m 755 -d %{buildroot}%{_bindir} install -m 755 -d %{buildroot}%{_mandir}/man1/   install -m 755 -p ./helloworld %{buildroot}%{_bindir}/%{name} install -m 644 -p ./ %{buildroot}%{_mandir}/man1/%{name}.1   %files %{_bindir}/%{name} %{_mandir}/man1/%{name}.1* %license LICENCE.txt

The %install section contains the script used to install our software inside the buildroot (remember? the directory that gets zipped). If we're lucky and upstream provides an install script, we can use that. Here we wrote our own script – we created the _bindir and _mandir inside the build root, and then copied the executable and the man page to said directories.

The %files section lists the files belonging to the package. You may be wondering "why not just take all the files from the buildroot?" – and that's a fair question! The simple answer is: stray files. You don't want to package more than what's needed. If a file remains in the buildroot, but doesn't appear in %files, it's either superfluous and should be removed at the end of %install, or you forgot about it and should add it to %files. It also serves as a basic defence mechanism against possible mistakes when upgrading the package to a new upstream version – if the program now installs more files (or removes some of them), the mismatch between the contents of buildroot and %files will cause a build failure, alerting you of the issue.

It's recommended to use macros for directories, instead of specifying absolute paths, as that makes managing a large package collection easier. Say, if the distro you're using decides one day that program files should no longer go inside /usr/share, but rather /data – it's a matter of changing a single macro definition, instead of modifying thousands of spec files. And if you think this is a bogus example, then consider that some distros keep a separate /bin and /usr/bin, while some have fused these two directories together. Again, if paths are specified as macros, you can use the same spec in both cases and it should work okay.

That being said, a list of commonly used directory macros could be useful.


We end the file with a changelog. Remember that this is a changelog of the package, not the upstream software! Don't copy the upstream changes here.

%changelog * Wed Nov 01 2017 suve <> 1.0-2 - Fix typo in description - Install manpage   * Wed Nov 01 2017 suve <> 1.0-1 - Initial packaging

...and we're finally done with the spec file. Obviously, there's quite a lot more that can be said about packaging, as the spec file presented here was rather simple. I'll get to that in a future article.

Validating the spec file

One more thing that we can do before building our package is making sure the spec file is top-notch. To do that, we can use the rpmlint utility.

rpmbuilder $ rpmlint ~/rpmbuild/SPECS/helloworld.spec 0 packages and 1 specfiles checked; 0 errors, 0 warnings.

Building the package

It looks like we're finally getting somewhere! Now that our spec file is ready, let's try building the package. Remember to put the sources and the spec file in the proper directories, first.

rpmbuilder $ cp ~/downloads/ ~/rpmbuild/SOURCES/ rpmbuilder $ cp ~/downloads/helloworld.spec ~/rpmbuild/SPECS/ rpmbuilder $ rpmbuild -ba ~/rpmbuild/SPECS/helloworld.spec

If anything failed along the way, we should get an error message somewhere among the output of rpmbuild. If everything went correct, then hurray! Our package is built. We can take a look in the RPMS/ directory.

rpmbuilder $ ls -p ~/rpmbuild/RPMS/ x86_64/ rpmbuilder $ ls -p ~/rpmbuild/RPMS/x86_64/ helloworld-1.0-2.x86_64.rpm

There's our baby. We can go ahead and redistribute that to whoever we want to.


Share with friends

e-mail Google+ Hacker News LinkedIn Reddit Tumblr VKontakte Wykop Xing


Do you have some interesting thoughts to share? You can comment by sending an e-mail to