Packaging Tutorial 1: banner

This tutorial demonstrates RPM packaging by packaging the banner program. It is a simple program with a simple GNU Autotools build script.

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 38 system. It should, however, work also for other Fedora releases. Just replace strings like f38 with your release number. Because this tutorial uses Fedora specific features that may not available in other environments, Fedora downstreams such as CentOS Stream or Red Hat Enterprise Linux may or may not work.

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

Creating the package directory

In Fedora, package build instructions are organized in so called dist-git repositories. There is a separate repository for each package. We mimic this system by creating a new directory for this tutorial. In dist-git, the repository name matches the package name. Fedora’s rules for naming packages are written in Naming Guidelines. For banner, package should simply be named banner. This is also the name of Fedora’s official banner package.

$ mkdir banner && cd banner

Inside a Spec File

RPM packages are configured by .spec files. Tools such was rpmdev-newspec can be used to generate empty specfiles for different purposes. For this tutorial, just create a file called banner.spec and paste the following minimal specfile. It does not work yet, but we will try to build it and fix errors as we encounter them.

Name:     banner
Version:  1.3.6
Release:  %autorelease
Summary:  Prints a short string to the console in very large letters
License:  GPL-2.0-only
URL:      https://github.com/pronovic/banner
Source:   https://github.com/pronovic/banner/releases/download/BANNER_V%{version}/banner-%{version}.tar.gz

%description
This is a classic-style banner program similar to the one found in Solaris or
AIX in the late 1990s. It prints a short string to the console in very large
letters.

%prep
%autosetup

%build
%configure
%make_build

%install
%make_install

%files

%changelog
%autochangelog

The specfile starts with a set of tags, such as Name: and Version:, followed by sections such as %description and %prep. Each tag fits into a single line, whereas each section continues until the next one starts.

Note that, confusingly, in addition to marking the section names, the percent sign % also marks RPM macros. Thus %autosetup, %configure, %make_build, %make_install and %autochangelog are not sections.

Tags

Version contains the version number of the packaged software.

Release numbers specfile updates, package rebuilds and other work within Fedora. The value used here, %autorelease, is part of rpmautospec, which is recommended for Fedora packages. It ties Release to package’s Git history. As we do not have a Git repository, %autorelease will evaluate to the default value of 1.

Often, Summary can be copied from the upstream README. The first letter should be uppercase to avoid rpmlint complaints.

License describes the license of the resulting binary package using a SPDX license identifier. It must follow Fedora’s licensing guidelines. In practice, determining the correct value often means inspecting the license notifications in individual source files. Upstream developers may also need to be asked for clarifications or corrections. In this tutorial, we just take the upstream’s word that the license is the GNU Public License, version 2.

URL points to upstream project’s website, which in this case is the GitHub repo’s page.

Source defines the upstream sources used when building the package. Usually, as in this case, it is a url pointing to a tarball released by the upstream, but it can also be a local file. There can be multiple Source tags if needed.

Sections

%description can often be copied from upstream README.

%prep contains a shell script for preparing the sources for building. It is often just the single macro %autosetup, which, in this case, simply extracts the source.

%build contains a shell script for the required build steps, such as compiling sources to binaries. Since banner’s buildsystem is Autotools, building it involves running configure and make. Macros %configure and %make_build invoke these commands using Fedora’s compilation flags and other configuration.

%install contains a shell script to copy the results from %build into an initially empty build root directory. As banner is using Autotools, macro %make_install is used.

%files lists the content of the resulting package. Mostly, the files come from the build root created in the %install, but documentation and license files can also be added directly from the sources. This section is left empty for now, to be filled later.

The %changelog documents the changes in each new package version and release. Changelog data can be displayed by rpm --query --changelog PACKAGE_NAME, which can be useful, for instance, to find out if specific bug and security patches are included. Its value,%autochangelog, also comes from rpmautospec. It populates the changelog from Git commit messages. As we do not have a Git repository, the changelog will be empty.

Lines which are not needed can be commented out with the hash #.

You can find more information in the RPM Reference Manual’s section Spec file format.

Downloading source

We need the source code defined by the Source tag, often referred to as the upstream source. This is most easily achieved by using the spectool command:

$ spectool -g banner.spec

You should now have the file listed in Source in your working directory:

$ ls *.tar.gz
banner-1.3.6.tar.gz

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 f38 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/banner-6':
configure: error: C compiler cannot create executables
See `config.log' for more details

RPM build errors:
error: Bad exit status from /var/tmp/rpm-tmp.R4Tf16 (%build)
    Bad exit status from /var/tmp/rpm-tmp.R4Tf16 (%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. Autotools 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.

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 specfile, 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/banner

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}/banner

Man pages

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

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

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

At this point, mockbuild completes successfully, but there are still more files that we should add to the package.

License file

Every package must install its license, tagged with %license directive. In banner’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:

%files
%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.md

Checking the result with rpmlint

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

$ fedpkg --release f38 lint

If all is good, there should be no warnings or errors. For tutorial’s sake, we intentionally left a mistake in the previous steps:

 banner.x86_64: E: zero-length /usr/share/doc/banner/NEWS

Descriptions of various error codes can be queried with rpmlint -e <error_code>. In this case, the unnecessary zero-length file must be removed. Change the docs line to

%doc AUTHORS ChangeLog README.md

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

Complete specfile

Here is the final version of banner.spec:

Name:           banner
Version:        1.3.6
Release:        %autorelease
Summary:        Prints a short string to the console in very large letters
License:        GPL-2.0-only
URL:            https://github.com/pronovic/banner
Source:         https://github.com/pronovic/banner/releases/download/BANNER_V%{version}/banner-%{version}.tar.gz
BuildRequires:  gcc
BuildRequires:  make

%description
This is a classic-style banner program similar to the one found in Solaris or
AIX in the late 1990s. It prints a short string to the console in very large
letters.

%prep
%autosetup

%build
%configure
%make_build

%install
%make_install

%files
%{_bindir}/banner
%{_mandir}/man1/banner.1.*
%license COPYING
%doc AUTHORS ChangeLog README

%changelog
%autochangelog

With this specfile, 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.

Files

List the files contained in the package:

$ rpm --query --package --list results_banner/6/1.fc38/banner-1.3.6-1.fc38.x86_64.rpm
/usr/bin/banner
/usr/lib/.build-id
/usr/lib/.build-id/01
/usr/lib/.build-id/01/360ae02508eaa0a77d216953b8b658a1e90b10
/usr/share/doc/banner
/usr/share/doc/banner/AUTHORS
/usr/share/doc/banner/ChangeLog
/usr/share/doc/banner/README
/usr/share/licenses/banner
/usr/share/licenses/banner/COPYING
/usr/share/man/man1/banner.1.gz

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

Requires

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

$ rpm --query --package --requires results_banner/1.3.6/1.fc38/banner-1.3.6-1.fc38.x86_64.rpm
libc.so.6()(64bit)
libc.so.6(GLIBC_2.2.5)(64bit)
libc.so.6(GLIBC_2.3)(64bit)
libc.so.6(GLIBC_2.3.4)(64bit)
libc.so.6(GLIBC_2.34)(64bit)
libc.so.6(GLIBC_2.4)(64bit)
rpmlib(CompressedFileNames) <= 3.0.4-1
rpmlib(FileDigests) <= 4.6.0-1
rpmlib(PayloadFilesHavePrefix) <= 4.0-1
rpmlib(PayloadIsZstd) <= 5.4.18-1
rtld(GNU_HASH)

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

$ dnf -C repoquery --whatprovides 'libc.so.6()(64bit)'
glibc-0:2.38-16.fc38.x86_64
glibc-0:2.38-7.fc38.x86_64

You will see that the only dependency of banner is glibc, which provides symbols in libc.so.6 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.

Provides

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

$ rpm --query --package --provides results_banner/1.3.6/1.fc38/banner-1.3.6-1.fc38.x86_64.rpm
banner = 1.3.6-1.fc38
banner(x86-64) = 1.3.6-1.fc38

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

Installing

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

$ sudo dnf -C -y install ./results_banner/1.3.6/1.fc38/banner-1.3.6-1.fc38.x86_64.rpm
$ banner success

 #####   #     #   #####    #####   #######   #####    #####
#     #  #     #  #     #  #     #  #        #     #  #     #
#        #     #  #        #        #        #        #
 #####   #     #  #        #        #####     #####    #####
      #  #     #  #        #        #              #        #
#     #  #     #  #     #  #     #  #        #     #  #     #
 #####    #####    #####    #####   #######   #####    ##### ..

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 distribution 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 f38 scratch-build --srpm results_banner/1.3.6/1.fc38/banner-1.3.6-1.fc38.src.rpm
Building banner-1.3.6-1.fc38.src.rpm for f38-candidate
Created task: 92465688
Task info: https://koji.fedoraproject.org/koji/taskinfo?taskID=92465688
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 (f38-candidate, banner-1.3.6-1.fc38.src.rpm): free
92465688 build (f38-candidate, banner-1.3.6-1.fc38.src.rpm): free -> open (buildvm-ppc64le-25.iad2.fedoraproject.org)
  92465698 rebuildSRPM (noarch): open (buildvm-s390x-24.s390.fedoraproject.org)
  92465745 buildArch (banner-1.3.6-1.fc38.src.rpm, x86_64): free
  92465748 buildArch (banner-1.3.6-1.fc38.src.rpm, s390x): open (buildvm-s390x-19.s390.fedoraproject.org)
  92465746 buildArch (banner-1.3.6-1.fc38.src.rpm, aarch64): open (buildvm-a64-26.iad2.fedoraproject.org)
  92465747 buildArch (banner-1.3.6-1.fc38.src.rpm, ppc64le): open (buildvm-ppc64le-11.iad2.fedoraproject.org)
  92465744 buildArch (banner-1.3.6-1.fc38.src.rpm, i686): open (buildhw-x86-12.iad2.fedoraproject.org)
  92465698 rebuildSRPM (noarch): open (buildvm-s390x-24.s390.fedoraproject.org) -> closed
  1 free  5 open  1 done  0 failed
  92465745 buildArch (banner-1.3.6-1.fc38.src.rpm, x86_64): free -> open (buildhw-x86-06.iad2.fedoraproject.org)
  92465745 buildArch (banner-1.3.6-1.fc38.src.rpm, x86_64): open (buildhw-x86-06.iad2.fedoraproject.org) -> closed
  0 free  5 open  2 done  0 failed
  92465748 buildArch (banner-1.3.6-1.fc38.src.rpm, s390x): open (buildvm-s390x-19.s390.fedoraproject.org) -> closed
  0 free  4 open  3 done  0 failed
  92465746 buildArch (banner-1.3.6-1.fc38.src.rpm, aarch64): open (buildvm-a64-26.iad2.fedoraproject.org) -> closed
  0 free  3 open  4 done  0 failed
  92465744 buildArch (banner-1.3.6-1.fc38.src.rpm, i686): open (buildhw-x86-12.iad2.fedoraproject.org) -> closed
  0 free  2 open  5 done  0 failed
92465688 build (f38-candidate, banner-1.3.6-1.fc38.src.rpm): open (buildvm-ppc64le-25.iad2.fedoraproject.org) -> closed
  0 free  1 open  6 done  0 failed
  92465747 buildArch (banner-1.3.6-1.fc38.src.rpm, ppc64le): open (buildvm-ppc64le-11.iad2.fedoraproject.org) -> closed
  0 free  0 open  7 done  0 failed

92465688 build (f38-candidate, banner-1.3.6-1.fc38.src.rpm) completed successfully