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, including more detailed tips, refer to Creating RPM Packages. 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.

Development Environment

To build RPMs for Fedora we need a set of development tools.

This is a one-time-only setup, installed by running these commands from a system administration (root) account:

# dnf install fedora-packager

To be able to test the build procedure in a clean chroot you need to configure your non-privileged account to be a member of the mock group:

# usermod -a -G mock <your username>

Those are the only commands requiring root privileges. All the remaining work should be done from your regular, non-privileged account, or even from a separate account created just for development work. Modern RPM-based systems, including Fedora, are set up to build and test RPM packages purely from within a non-privileged account.

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 http://ftp.gnu.org/gnu/hello/hello-2.10.tar.gz

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 Creating RPM Packages 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+
URL:      https://www.gnu.org/software/hello/
Source0:  https://ftp.gnu.org/gnu/hello/hello-%{version}.tar.gz

%description
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.

%prep
%autosetup

%build
%configure
%make_build

%install
%make_install

%files

%changelog
* Sat Oct 23 2021 The Coon of Ty <Ty@coon.org> - 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 --changelog -q 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 rawhide 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 Source0:

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.

Executable

Installed (but unpackaged) file(s) found:
/usr/bin/hello

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:

%files
%{_bindir}/hello

Man pages

Installed (but unpackaged) file(s) found:
/usr/share/man/man1/hello.1.gz

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

%{_mandir}/man1/hello.1.*

Texinfo pages

Installed (but unpackaged) file(s) found:
/usr/share/info/dir
/usr/share/info/hello.info.gz

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:

%{_infodir}/hello.info.*

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 is done by calling rm in %install. However, note that files are installed in the buildroot directory, and thus the removal is done like this:

%install
rm %{buildroot}/%{_infodir}/dir

Translations

Installed (but unpackaged) file(s) found:
/usr/share/locale/bg/LC_MESSAGES/hello.mo
/usr/share/locale/ca/LC_MESSAGES/hello.mo
/usr/share/locale/da/LC_MESSAGES/hello.mo
...

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 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:

%doc AUTHORS ChangeLog NEWS README THANKS TODO

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:

%check
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 should do this, but as of version 1.41, it suffers from a bug causing it not to find the rpms created by fedpkg mockbuild. So instead, rpmlint needs to be called directly. Pass files to check as arguments:

$ rpmlint hello.spec results_hello/2.10/1.fc35/hello-2.10*.{x86_64,src}.rpm

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:

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

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:            http://ftp.gnu.org/gnu/%{name}
Source0:        http://ftp.gnu.org/gnu/%{name}/%{name}-%{version}.tar.gz

BuildRequires:  gcc
BuildRequires:  gettext
BuildRequires:  make

%description
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.

%prep
%autosetup
mv THANKS THANKS.old
iconv --from-code=ISO-8859-1 --to-code=UTF-8 --output=THANKS THANKS.old

%build
%configure
%make_build

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

%check
make check

%files -f %{name}.lang
%{_mandir}/man1/hello.1.*
%{_infodir}/hello.info.*
%{_bindir}/hello
%doc AUTHORS ChangeLog NEWS README THANKS TODO
%license COPYING

%changelog
* Sat Oct 23 2021 The Coon of Ty <Ty@coon.org> 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.