Tutorial de Empaquetamiento: GNU Hello

Este tutorial demuestra el empaquetamiento RPM empaquetando el programa GNU Hello. Si bien el programa es sencillo, viene con la mayoría de los componentes periféricos de un proyecto FOSS: entorno de configuración/compilación/instalación, documentación, internacionalización, etc. Sin embargo, no incluye información de empaquetamiento RPM, por lo tanto es un vehículo razonable para practicar la construcción de RPMs.

Para una información completa sobre como crear archivos RPM, vea el Manual de Referencia RPM. Si planea crear un paquete RPM para el repositorio Fedora, siga el proceso para Unirse a los Mantenedores de Paquetes, incluyendo seguir las diversas pautas de Fedora.

Este tutorial está diseñado para ejecutarse en un sistema 38. Debería, sin embargo, trabajar también para otras versiones de Fedora. Solo reemplace las cadenas f38 con su número de versión. Es poco probable que funcione en los software downstream de Fedora como CentOS Stream o Red Hat Enterprise Linux, puesto que este tutorial usa herramientas y funciones específicas y muy recientes de Fedora. En concreto, rpmautospec es poco probable que esté disponible.

El tutorial sigue una forma de paso a paso, con la mayoría de los pasos editando el archivo de especificaciones del paquete. El archivo de especificaciones resultante se lista al final, de modo que, en caso de que no quede claro cómo se debe aplicar un cambio en particular, puede echar un vistazo.

Instalar Herramientas de Empaquetador

Crear el directorio del paquete

En Fedora, las instrucciones de compilación del paquete están organizadas en los llamados repositorios dist-git. Hay un repositorio separado para cada paquete. Imitamos este sistema creando un nuevo directorio para este tutorial. En dist-git, el nombre del repositorio coincide con el nombre del paquete. Las reglas de Fedora para la denominación de los paquetes están escritas en Directrices de Denominación. Para GNU Hello, el paquete puede llamarse simplemente hello. Este es también el nombre oficial del paquete GNU Hello de Fedora.

$ mkdir hello && cd hello

Dentro de un Archivo de Especificaciones

Los paquetes RPM están configurados por archivos .spec. Herramientas como rpmdev-newspec se pueden usar para generar archivos de especificaciones vacíos para diferentes propósitos. Para este tutorial, solo cree un archivo llamado hello.spec y pegue el siguiente archivo de especificaciones mínimo. No funciona todavía, pero intentaremos construirlo y corregir los errores según los vayamos encontrando.

Name:     hello
Version:  2.10
Release:  %autorelease
Summary:  Produces a familiar, friendly greeting
License:  GPL-3.0-or-later
URL:      https://www.gnu.org/software/hello/
Source:   http://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
%autochangelog

El archivo de especificaciones comienza con un conjunto de etiquetas, como Name: y Version:, seguido de secciones como %description y %prep. Cada etiqueta se ajusta a un única línea, mientras que cada sección continúa hasta que comienza la siguiente.

Tenga en cuenta que, de manera confusa, además de marcar los nombres de sección el signo porcentaje % marca también las macros RPM. Así, por ejemplo %autosetup, %configure y %make_build no son secciones.

Etiquetas

Version contiene el número de versión del software empaquetado.

Release numera las actualizaciones del fichero de especificaciones, las reconstrucciones del paquete y otro trabajo dentro de Fedora. El valor usado aquí, %autorelease, es parte de rpmautospec, que es el recomendado para los paquetes de Fedora. Se vincula Release al histórico Git del paquete. Como no tenemos un repositorio Git, %autorelease se evaluará al valor predeterminado de 1.

Con frecuencia, Summary puede ser copiado del README de arriba. La primera letra debe ir en mayúscula para evitar quejas sobre rpmlint.

License describe la licencia del paquete binario resultante usando un identificador de licencia SPDX. Debe seguir las directrices de licenciamiento de Fedora. En la práctica, determinar el valor correcto significa, con frecuencia, inspeccionar las notificaciones de licencias en los archivos fuente individuales. También se puede necesitar preguntar a los desarrolladores upstream para lograr clarificaciones o correcciones. En este tutorial, simplemente tomamos la palabra de la fuente de que la licencia es la GNU Public License, versión 3 o posterior.

URL apunta al sitio web de desarrollo del proyecto.

Source define las fuentes de desarrollo usadas cuando se construye el paquete. Usualmente, como en este caso, es una url apuntando a un tarball lanzado por el desarrollador, pero también puede ser un archivo local. Puede haber múltiples etiquetas Source si es necesario.

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. GNU Hello uses Autotools as its buildsystem, so building 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 GNU Hello 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 hello.spec

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

$ ls *.tar.gz
hello-2.10.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/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

For reasons that are too complex to explain in a tutorial, in some situations texinfo package is also needed. To avoid a possible error, add one more row:

BuildRequires:   texinfo

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 dir file generated by GNU Hello build script indexes all texinfo pages in your system. Because the installed pages differ among systems, the file cannot be prebuilt and packaged. Instead it needs to be created and updated when the package is installed. The update is automatically performed by rpm triggers in info binary package of texinfo source package.

To prevent from installing the dir file, remove it from the buildroot at the end of the %install section with rm command.

However, GNU Hello build script only generates the dir file if info package is installed during the build. Blindly removing the file would raise an error if the hello package were built on a system without info package. To deal with both cases, delete the file if it exists:

%install
%make_install
test -f %{buildroot}/%{_infodir}/dir && 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 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
============================================================================

Fixing automagic

Now the package succefully builds. But that does not mean that the .spec file is correct.

Listing all build-time dependencies

If you carefully read a build output, you can discover lines which mention sed command:

+ /usr/bin/make -O -j4 V=1 VERBOSE=1
rm -f lib/arg-nonnull.h-t lib/arg-nonnull.h && \
sed -n -e '/GL_ARG_NONNULL/,$p' \

Therefore you need to add this line close to other BuildRequires lines:

BuildRequires:  sed

Smilarly, studying configure script in the unpackaged sources, which is executed by %configure macro, reveals it’s a /bin/sh script:

$ head configure
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.69 for GNU Hello 2.10.

Thus you also should record this dependency on bash:

BuildRequires:  bash

Why on bash? Because /bin/sh program is provided by bash package:

rpm --qf '%{name}\n' -qf /bin/sh
bash

Specifying all used dependencies helps to make the .spec file resilient against changes in the build environment. If e.g. sed package were removed from the environment, this GNU Hello package would fail to build.

Listing all build options

The GNU Hello build script, configure has many build options which enables or disables optional features. Their nondefault forms can be listed with --help option:

$ ./configure --help
`configure' configures GNU Hello 2.10 to adapt to many kinds of systems.

Usage: ./configure [OPTION]... [VAR=VALUE]...
⋮
Optional Features:
  --disable-option-checking  ignore unrecognized --enable/--with options
  --disable-FEATURE       do not include FEATURE (same as --enable-FEATURE=no)
  --enable-FEATURE[=ARG]  include FEATURE [ARG=yes]
  --enable-silent-rules   less verbose build output (undo: "make V=1")
  --disable-silent-rules  verbose build output (undo: "make V=0")
  --enable-dependency-tracking
                          do not reject slow dependency extractors
  --disable-dependency-tracking
                          speeds up one-time build
  --disable-nls           do not use Native Language Support
  --disable-rpath         do not hardcode runtime library paths

Some of them are already specified within %configure and %make_build macros. The rest of the options, if they are important for the built package, should be explicitly written in the .spec file to prevent from their sudden and unnoticed changes. Either because a new Hello version changes the default, or because a package which they depend on appears of disappears from the build environment.

Therefore modify %configure invocation in %build section like this:

%configure \
    --enable-nls \
    --disable-rpath

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 f38 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 which is provided by glibc-common package, and mv tool from coreutils:

BuildRequires:  coreutils
BuildRequires:  glibc-common
⋮
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 final version of hello.spec:

Name:           hello
Version:        2.10
Release:        %autorelease
Summary:        Produces a familiar, friendly greeting

License:        GPL-3.0-or-later
URL:            http://ftp.gnu.org/gnu/%{name}
Source:         http://ftp.gnu.org/gnu/%{name}/%{name}-%{version}.tar.gz

BuildRequires:  bash
BuildRequires:  coreutils
BuildRequires:  gcc
BuildRequires:  gettext
BuildRequires:  glibc-common
BuildRequires:  make
BuildRequires:  sed
BuildRequires:  texinfo

%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 \
    --enable-nls \
    --disable-rpath
%make_build

%install
%make_install
test -f %{buildroot}/%{_infodir}/dir && 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
%autochangelog

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.fc38/hello-2.10-1.fc38.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 a 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.fc38/hello-2.10-1.fc38.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.fc38.x86_64
glibc-0:2.34-7.fc38.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.fc38/hello-2.10-1.fc38.x86_64.rpm
hello = 2.10-1.fc38
hello(x86-64) = 2.10-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_hello/2.10/1.fc38/hello-2.10-1.fc38.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 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_hello/2.10/1.fc38/hello-2.10-1.fc38.src.rpm
Building hello-2.10-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, hello-2.10-1.fc38.src.rpm): free
92465688 build (f38-candidate, hello-2.10-1.fc38.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.fc38.src.rpm, x86_64): free
  92465748 buildArch (hello-2.10-1.fc38.src.rpm, s390x): open (buildvm-s390x-19.s390.fedoraproject.org)
  92465746 buildArch (hello-2.10-1.fc38.src.rpm, aarch64): open (buildvm-a64-26.iad2.fedoraproject.org)
  92465747 buildArch (hello-2.10-1.fc38.src.rpm, ppc64le): open (buildvm-ppc64le-11.iad2.fedoraproject.org)
  92465744 buildArch (hello-2.10-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 (hello-2.10-1.fc38.src.rpm, x86_64): free -> open (buildhw-x86-06.iad2.fedoraproject.org)
  92465745 buildArch (hello-2.10-1.fc38.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.fc38.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.fc38.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.fc38.src.rpm, i686): open (buildhw-x86-12.iad2.fedoraproject.org) -> closed
  0 free  2 open  5 done  0 failed
92465688 build (f38-candidate, hello-2.10-1.fc38.src.rpm): open (buildvm-ppc64le-25.iad2.fedoraproject.org) -> closed
  0 free  1 open  6 done  0 failed
  92465747 buildArch (hello-2.10-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, hello-2.10-1.fc38.src.rpm) completed successfully