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
Follow 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 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 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+
URL: https://www.gnu.org/software/hello/
Source: 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 Patricia Packager <patricia@example.com> - 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.
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 a 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 can be done by removing it from the buildroot at the end of the %install
section:
%install
%make_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:
-
Add the required build dependency with
BuildRequires: gettext
. -
Find the filenames in the
%install
step with%find_lang %{name}
. -
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:
%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
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:
mv THANKS THANKS.old 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: http://ftp.gnu.org/gnu/%{name}
Source: 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 Patricia Packager <patricia@example.com> - 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.
Files
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 /usr/bin/hello /usr/lib/.build-id /usr/lib/.build-id/39 /usr/lib/.build-id/39/c97ecb15c6292ce23e8b00e15e6e72a61e5072 /usr/share/doc/hello /usr/share/doc/hello/AUTHORS /usr/share/doc/hello/ChangeLog /usr/share/doc/hello/NEWS /usr/share/doc/hello/README /usr/share/doc/hello/THANKS /usr/share/doc/hello/TODO /usr/share/info/hello.info.gz /usr/share/licenses/hello /usr/share/licenses/hello/COPYING /usr/share/locale/bg/LC_MESSAGES/hello.mo ... /usr/share/locale/zh_TW/LC_MESSAGES/hello.mo /usr/share/man/man1/hello.1.gz
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.
Requires
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 libc.so.6()(64bit) libc.so.6(GLIBC_2.14)(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) libc.so.6(GLIBC_2.7)(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.34-11.fc36.x86_64 glibc-0:2.34-7.fc36.x86_64
You will see that the only dependency of GNU Hello 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_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.
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: 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 (f36-candidate, hello-2.10-1.fc36.src.rpm): free 92465688 build (f36-candidate, hello-2.10-1.fc36.src.rpm): free -> open (buildvm-ppc64le-25.iad2.fedoraproject.org) 92465698 rebuildSRPM (noarch): open (buildvm-s390x-24.s390.fedoraproject.org) 92465745 buildArch (hello-2.10-1.fc36.src.rpm, x86_64): free 92465748 buildArch (hello-2.10-1.fc36.src.rpm, s390x): open (buildvm-s390x-19.s390.fedoraproject.org) 92465746 buildArch (hello-2.10-1.fc36.src.rpm, aarch64): open (buildvm-a64-26.iad2.fedoraproject.org) 92465747 buildArch (hello-2.10-1.fc36.src.rpm, ppc64le): open (buildvm-ppc64le-11.iad2.fedoraproject.org) 92465744 buildArch (hello-2.10-1.fc36.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 (hello-2.10-1.fc36.src.rpm, x86_64): free -> open (buildhw-x86-06.iad2.fedoraproject.org) 92465745 buildArch (hello-2.10-1.fc36.src.rpm, x86_64): open (buildhw-x86-06.iad2.fedoraproject.org) -> closed 0 free 5 open 2 done 0 failed 92465748 buildArch (hello-2.10-1.fc36.src.rpm, s390x): open (buildvm-s390x-19.s390.fedoraproject.org) -> closed 0 free 4 open 3 done 0 failed 92465746 buildArch (hello-2.10-1.fc36.src.rpm, aarch64): open (buildvm-a64-26.iad2.fedoraproject.org) -> closed 0 free 3 open 4 done 0 failed 92465744 buildArch (hello-2.10-1.fc36.src.rpm, i686): open (buildhw-x86-12.iad2.fedoraproject.org) -> closed 0 free 2 open 5 done 0 failed 92465688 build (f37-candidate, hello-2.10-1.fc36.src.rpm): open (buildvm-ppc64le-25.iad2.fedoraproject.org) -> closed 0 free 1 open 6 done 0 failed 92465747 buildArch (hello-2.10-1.fc36.src.rpm, ppc64le): open (buildvm-ppc64le-11.iad2.fedoraproject.org) -> closed 0 free 0 open 7 done 0 failed 92465688 build (f36-candidate, hello-2.10-1.fc36.src.rpm) completed successfully
Want to help? Learn how to contribute to Fedora Docs ›