Packaging Tutorial: GNU Hello

This tutorial demonstrates RPM packaging by packaging the GNU Hello program. While the program itself is simple, it also comes with most of the usual peripheral components of a FOSS project: configuration/build/install environment, documentation, internationalization, etc. However, it does not include RPM packaging information, therefore it is a reasonable vehicle to practice building RPMs on.

For comprehensive information on how to create RPM files, refer to RPM Reference Manual. If you plan to create an RPM package for the Fedora repository, follow the process for Joining the Package Maintainers, including following the various Fedora guidance.

This tutorial is intended to be run on a Fedora 36 system. It should, however, work also for other releases. Just replace strings like f36 with your release number.

The tutorial proceeds in step by step manner, with most steps editing the package’s specfile. The final resulting specfile is listed in the end, so in case there is any unclarity how a particular change should be applied, you can peek there.

Installing Packager Tools

Building the RPM

We need the source code of the project we are packaging, often referred to as the upstream source. We will download it from the project’s website into a directory we create for packaging GNU Hello. We are getting the compressed tarball archive, which happens to be the preferred distribution form for most FOSS projects.

$ mkdir hello && cd hello
$ wget

The RPM package is configured by .spec files. We will create a template file hello.spec:

$ rpmdev-newspec hello

Inside a Spec File

The fields in our .spec file need slight editing. Please follow RPM Reference Manual’s section Spec file format for these fields. In our case, the file might start as follows:

Name:     hello
Version:  2.10
Release:  1%{?dist}
Summary:  Produces a familiar, friendly greeting
License:  GPLv3+

The GNU Hello program produces a familiar, friendly greeting. Yes, this is
another implementation of the classic program that prints “Hello, world!” when
you run it.





* Sat Oct 23 2021 Patricia Packager <> - 2.10-1
- Initial version of the package

The Version should mirror the upstream, while Release numbers our work within Fedora.

The first letter of the Summary should be uppercase to avoid rpmlint complaints.

Often, Summary and %description can be copied from the upstream README.

It is your responsibility to check the License status of the software. Inspect the source files and their LICENSE files, and talk to the authors as needed.

The %changelog should document the work on preparing the RPM, especially if there are security and bug patches included on top of the base upstream source. Changelog data can be displayed by rpm --query --changelog PACKAGE_NAME, which is very useful, for instance, to find out if specific bug and security patches were included in the installed software, thanks to the diligent Fedora packagers who include this info with the relevant CVE numbers.

The %changelog entry should include the version string to avoid rpmlint complaints.

Multi-line sections like %changelog or %description start on a line under the directive, and end when the next section starts or the file ends.

Lines which are not needed (e.g. BuildRequires and Requires) can be commented out with the hash # for now.

In many cases, many lines in the template do not need to be changed at all, at least for the initial attempt.

Building the Package

We are ready for the first run to build source, binary and debugging packages. This, and many other tasks, are done with the fedpkg tool. The production builds for Fedora are built in the Koji build system, which in turn uses Mock to manage isolated build environments. To get as close to a production build as is locally possible, we use the fedpkg mockbuild command which also invokes Mock:

$ fedpkg --release f36 mockbuild

The build environment created by Mock is very basic. It does not include a C compiler by default, so the build will fail. The reason is explained in the output:

checking whether the C compiler works... no
configure: error: in `/builddir/build/BUILD/hello-2.10':
configure: error: C compiler cannot create executables
See `config.log' for more details
error: Bad exit status from /var/tmp/rpm-tmp.D2nN0w (%build)
    Bad exit status from /var/tmp/rpm-tmp.D2nN0w (%build)

Additional build tools are defined by adding BuildRequires: rows to the specfile. In Fedora, GCC is the standard compiler, so we need to add a row for gcc. GNU Hello also uses make, so a row should be added for it, too. Add these lines after Source:

BuildRequires:   gcc
BuildRequires:   make

Run a mockbuild again. The earlier error should be gone.

Installing files

The next thing rpm will complain about are unpackaged files, i.e. the files that would be installed in the system, but were not declared as belonging to the package. We need to declare them in the %files section. Fixing these errors is an iterative process. After declaring a missing file in the .spec file, run fedpkg again, then declare the next missing file and so on.

We will go through the file list one by one.


Installed (but unpackaged) file(s) found:

This is the executable binary program. /usr/bin, like many other system directories, have a default rpm macro defined. The macros should always be used when available, so the executable is listed in %files as follows:


Man pages

Installed (but unpackaged) file(s) found:

The Packaging Guidelines have a dedicated section for Manpages. Following its instructions, manpages are list as follows:


Texinfo pages

Installed (but unpackaged) file(s) found:

Texinfo pages are handled much in the same way as man pages. The directory is defined by the default macro {_infodir}, so the Texinfo manual can be added as follows:


The texinfo package has rpm triggers that automatically generate the Texinfo dir file from all the texinfo pages in the system. Thus, the dir generated by GNU Hello build script must not be installed. This can be done by removing it from the buildroot at the end of the %install section:

rm %{buildroot}%{_infodir}/dir


Installed (but unpackaged) file(s) found:

Since our program uses translations and internationalization, we are seeing a lot of undeclared i18n files. The recommended method to declare them is:

  1. Add the required build dependency with BuildRequires: gettext.

  2. Find the filenames in the %install step with %find_lang %{name}.

  3. Install the files with %files -f %{name}.lang.

License file

Every package must install its license, tagged with %license directive. In GNU Hello’s case, as well as for many other projects, the license file is located at the source tarball’s top level, and perhaps not copied to the buildroot during installation at all. Regardless, it can be installed to the standard license directory by using a relative path:

%license COPYING

Additional documentation

Often, package sources contain documentation that could be useful for the end users as well. These can be installed and marked as documentation with the %doc directive. Similarly to %license, relative paths can be used to include files directly from the source tarball rather than from the buildroot:


Running tests

GNU Hello, like many other projects, includes an automated test suite in the sources. If at all possible, the test suite should be run during the rpm build. This helps ensuring that a working build was produced. This is done by adding the test suite invocation to specfile %check% section, which comes after %install in order. In GNU Hello’s case:

make check

Run a mockbuild again and check the output to ensure that the tests were actually run. Something like this should be somewhere in the output:

Testsuite summary for GNU Hello 2.10
# TOTAL: 5
# PASS:  4
# SKIP:  1
# XFAIL: 0
# FAIL:  0
# XPASS: 0
# ERROR: 0

Checking the result with rpmlint

Next you should check them for conformance with RPM design rules, by running rpmlint on specfile, source rpm and binary rpm. Command fedpkg lint does this:

$ fedpkg --release f36 lint

If all is good, there should be no warnings or errors. In the GNU Hello case, one warning can be expected:

hello.x86_64: W: file-not-utf8 /usr/share/doc/hello/THANKS

Descriptions of various error codes can be queried with rpmlint -e <error_code>. In this case, in order to ensure a pure utf-8 installation, the file needs to be converted in %prep. This can be done with the iconv utility:

iconv --from-code=ISO-8859-1 --to-code=UTF-8 --output=THANKS THANKS.old

Run fedpkg lint again and observe that the warning is fixed.

A Complete hello.spec File

Here is the initial version of hello.spec:

Name:           hello
Version:        2.10
Release:        1%{?dist}
Summary:        Produces a familiar, friendly greeting

License:        GPLv3+
URL:  {name}

BuildRequires:  gcc
BuildRequires:  gettext
BuildRequires:  make

The GNU Hello program produces a familiar, friendly greeting. Yes, this is
another implementation of the classic program that prints “Hello, world!” when
you run it.

iconv --from-code=ISO-8859-1 --to-code=UTF-8 --output=THANKS THANKS.old


rm %{buildroot}/%{_infodir}/dir
%find_lang %{name}

make check

%files -f %{name}.lang
%license COPYING

* Sat Oct 23 2021 Patricia Packager <> - 2.10-1
- Initial version of the package

With this .spec file, you should be able to successfully complete the build process, and create the source and binary RPM packages.

Checking the result

Having a working specfile and rpms built from it, the result can be checked. Before checking the result by installing the package, let us do some simple checks. The RPM Package Manager rpm can be used for this.


List the files contained in the package:

$ rpm --query --package --list results_hello/2.10/1.fc36/hello-2.10-1.fc36.x86_64.rpm

You can see that all the files listed in the specfile %files section are included, including the automatically processed locale files. Also, under /usr/lib/.build-id, there is an automatically generated file. It is actually a symlink, mapping an build id to the hello binary for debugging purposes.


List the package’s runtime dependencies with the following command:

$ rpm --query --package --requires results_hello/2.10/1.fc36/hello-2.10-1.fc36.x86_64.rpm
rpmlib(CompressedFileNames) <= 3.0.4-1
rpmlib(FileDigests) <= 4.6.0-1
rpmlib(PayloadFilesHavePrefix) <= 4.0-1
rpmlib(PayloadIsZstd) <= 5.4.18-1

To check which packages in Fedora repositories provide these dependencies, you can use dnf repoquery:

$ dnf -C repoquery --whatprovides ''

You will see that the only dependency of GNU Hello is glibc, which provides symbols in as well as rtld(GNU_HASH).

The rpmlib requires are special. These specify various rpm features used in the rpm package itself, constraining the version of rpm that can be used to install the package.


Conversely, to check what capabilities the package provides, you can do:

$ rpm --query --package --provides results_hello/2.10/1.fc36/hello-2.10-1.fc36.x86_64.rpm
hello = 2.10-1.fc36
hello(x86-64) = 2.10-1.fc36

The provides of this package are very simple. It simply provides its own name, in plain and architecture specific forms.


As a final check, the package can be installed and ran:

$ sudo dnf -C -y install ./results_hello/2.10/1.fc36/hello-2.10-1.fc36.x86_64.rpm
$ hello --greeting="Hello, rpm!"
Hello, rpm!

To clean up your system, undo the installation:

$ sudo dnf -C -y history undo last

Building in Fedora infrastructure

Even though the package is not part of Fedora distrubution yet, a scratch build can be performed to ensure that the package builds successfully in Fedora’s Koji build system, and that it builds successfully for all architectures supported by Fedora. Such build is started by passing a source rpm package to fedpkg scratch-build.

Note that Koji uses Kerberos for authentication. See Acquiring Kerberos Ticket for details.

$ fedpkg --release f36 scratch-build --srpm results_hello/2.10/1.fc36/hello-2.10-1.fc36.src.rpm
Building hello-2.10-1.fc36.src.rpm for f36-candidate
Created task: 92465688
Task info:
Watching tasks (this may be safely interrupted)...

You can open the task info link in a browser to view build progress, logs and results. The command line program also reports on progress as it happens. Successful execution looks something like this:

92465688 build (f36-candidate, hello-2.10-1.fc36.src.rpm): free
92465688 build (f36-candidate, hello-2.10-1.fc36.src.rpm): free -> open (
  92465698 rebuildSRPM (noarch): open (
  92465745 buildArch (hello-2.10-1.fc36.src.rpm, x86_64): free
  92465748 buildArch (hello-2.10-1.fc36.src.rpm, s390x): open (
  92465746 buildArch (hello-2.10-1.fc36.src.rpm, aarch64): open (
  92465747 buildArch (hello-2.10-1.fc36.src.rpm, ppc64le): open (
  92465744 buildArch (hello-2.10-1.fc36.src.rpm, i686): open (
  92465698 rebuildSRPM (noarch): open ( -> closed
  1 free  5 open  1 done  0 failed
  92465745 buildArch (hello-2.10-1.fc36.src.rpm, x86_64): free -> open (
  92465745 buildArch (hello-2.10-1.fc36.src.rpm, x86_64): open ( -> closed
  0 free  5 open  2 done  0 failed
  92465748 buildArch (hello-2.10-1.fc36.src.rpm, s390x): open ( -> closed
  0 free  4 open  3 done  0 failed
  92465746 buildArch (hello-2.10-1.fc36.src.rpm, aarch64): open ( -> closed
  0 free  3 open  4 done  0 failed
  92465744 buildArch (hello-2.10-1.fc36.src.rpm, i686): open ( -> closed
  0 free  2 open  5 done  0 failed
92465688 build (f37-candidate, hello-2.10-1.fc36.src.rpm): open ( -> closed
  0 free  1 open  6 done  0 failed
  92465747 buildArch (hello-2.10-1.fc36.src.rpm, ppc64le): open ( -> closed
  0 free  0 open  7 done  0 failed

92465688 build (f36-candidate, hello-2.10-1.fc36.src.rpm) completed successfully