Building RPM packages for Linux distributions 1/4

Building RPM packages for Linux distributions 1/4

This article is going to present an explanation on how to build a custom RPM package, and some best practices.

RPM packages

An RPM package is a binary package which can be installed in various Linux distributions: all the RedHat-family distributions (RedHat Enterprise Linux, its community counterpart CentOS, and the more cutting edge Fedora), and the OpenSUSE/SUSE Enterprise distributions. Some other less widespread distributions use natively RPM packages.

An RPM package can be installed using the rpm command, though it is better to install it using the proper command for the specific distribution: yum for RHEL/CentOS and Fedora<22, dnf from Fedora 22 and on, and zypper for OpenSUSE/SUSE. The nice thing with these commands is that they allow you installing all transitive requirements for the installed package automatically.

RPM Build best practices

We have chosen to clearly separate all RPM builds in distinct folders, in order to have them committed to clearly distinct SCM (Git) repositories. This also allows to run builds separately in cleanly isolated build jobs (run for example in Jenkins).
Sources archives and Binaries are also kept out the build process, in order to avoid committing them in the SCM. It’s better to have the build process download sources or packaged binaries at build time, if only to avoid using SCM as a storage for binaries.

It also a good thing because the same sources and binaries could be used to build other types of packages (e.g. Deb packages for Debian-based distributions).

Build folder structure

a good RPM build folder structure contains at least the following folders:

And that’s it.
Other folders will be created by the build process, but should never be kept (never committed to a SCM), such as BUILD, BUILDROOT, RPMS, SRPMS, and TEMP.

The SOURCES folder contains the original source archives, binaries, and files which are required in order to build the target RPM package(s).

The SPECS folder contains the RPM spec (specification file) which is the central and unique element to elaborate in an RPM build.

Spec file

Variables definition

A SPEC file should start with variable definitions.
If some variables are to be injected from the environment, or build is to be parameterized, it is good practice to at least define sensible default values, in order to allow building, notably from Source RPMs when they are retrieved without the surrounding context.

Basic package information

SPEC file should then define some package information:

Versioning

In a spec file, you are able to define the Version and Release of the package to be produced.
This should be chosen carefully as this is directly linked to the kind of lifecycle you intend to use.
For example, if you package a software built from its sources and barely customize it, and your plan is to automatically replace it with its future versions (in place) when they are available, you should use the original software version as Version and use the Release for fixes/adaptations related to your specific setup/installation of this software in the target OS.
On the other hand, if your package is meant to provide a software in a given version, which should NOT be replaced by the next version, then you should use a distinct versionning for the RPM package, and set your software’s original version in the package Name .
Note that it is a convention to start release number at number 0.

Distribution-specific definitions

Any element in the spec can be surrounded by distribution conditionals.
These conditional checks allow setting different build behaviours depending on the distribution.
The different conditionals are using the %if macro, and distribution-specific variables.
for example:

Distribution-tagged release number

The conditional {?dist} macro should be used in release definition for cross-distribution packages:

If the dist variable is provided by the context, it will add a suffix to the release such as:

  • .el6 for RHEL/CentOS 6,
  • .el7 for RHEL/CentOS 7,
  • .fx for Fedora x (.f22, .f23, .f24…etc)

Note that in SUSE/OpenSuse, this dist variable is not defined.
So, for a cross-distribution build compatible with SUSE/OpenSuse, You should make it available by
adding the following into your spec, in order to have your RPM binaries suffixed properly, for
clarity.

This will add e.g. a .suse12 suffix to the release version for suse version 12. This makes it easier to manage binaries in repositories or build pipelines, by showing their intended target distribution explicitely in the package file name.
For more information on cross-distribution builds, see below.

Package sources

In order to be able to use files from the SOURCES/ folder, they must have an alias defined in the SPEC file. This alias should have the Sourcen format where n is a different number for each source file. Source files must then be referenced further in the SPEC file through these specific variables:

In this example, package build expects to find the file referenced by Source0 in the SOURCES/ folder.

Package dependencies

A SPEC file can define package requirements on other packages or features. The Requires tag is used for that purpose.

A package can also provide a given “feature”. As a default a package “provides” its package name (allowing other packages to require it directly). But it can also provide something else, through the Provides tag.  For example if you re-package OpenJDK (Java) with the name my-openjdk, you could say that it provides “java” and/or “jdk”, or if appropriate “jre”.

Requires and Provides can be defined on multiple cumulative lines, contain multiple elements, and even define version specs.

Other tags are available to provide additional information, such as:

  • Obsoletes: package is a newer version of another package (convenient if you need to rename a package). Usually, you need to specify that the newer package also provides the former one (in order to ensure a smooth transition for existing systems).
  • Conflicts: means that the package cannot be installed in the same system as another package.

If some packages are required (or should not be used) at package-build time, they can also be specified using the BuildRequires tag or the BuildConflicts tag:

 

Description

In the %description section, you should put a multi-line description of the target package. You can (and should) use variables in this description for things which will change in each version, therefore making the package easier to maintain.

Prepare/Build/Install/File sections/macros

In the %prepare, %build, %install, %file, the actual build is described, very much like a shell script.

In the next blog entries, we’ll describe how to define these sections for different build scenarios.

Changelog

The changelog in a package is important for traceability.
This is mostly important for custom-build packages.
If you repack well-known binaries, it may be redundant to add changelog information, though if you have the time
to indicate at least the motivation for the release, it is very interesting bit of information.

Notice that the changelog format is very formal and must be respected.

Pre-build steps

Downloading required source archives or binaries

As mentioned before, it is good not to include Source archives and binaries (big files to sum it up) in the build, it is therefore necessary to do a lazy or systematic download of these resources before the build. Note that the rpm build can do it if you specify URLs for source files. Such a pre-build step can be interesting if you need additional checks or if download must be done using credentials, and/or specific protocols.

This ensures that file is available for the remainder of the build process. An additional file hash check (e.g. MD5 or SHA1) could be done additionally to guarantee file is not altered, if the original source provides such a hash.

Running the build

Build is launched by running the rpmbuild command from the top of the tree (in the parent folder of SOURCES and SPECS folder).

–target: allows specifying the target architecture for the package, such as noarch (no architecture-specific
bindings, for example for Java, Python, …etc)
–define: allows defining variables to inject in the SPEC file.
_topdir variable allows setting the root of the build to current folder (this is what allows for an isolated build).
_tmppath variable allows relocating temp folder in the current folder, in order to avoid collision of temporary files, and easier cleanup of temporary files.
-bb: specifies to build the binary only. Can be replaced with -ba (build all: binary rpms and source rpms), or -bs to build source rpm only. Other options allow to achieve only a part of the build: -bp (build up to the %prepare section) -bc (build up to the %build section) -bi (build up to the %install section) -bl (do a list check on files defined in the %files section)
For more options and details, see Online rpmbuild documentation, or the manpage/documentation in target distributions.

Post-build steps

Signing the RPM packages

using expect, and custom signing scripts, you may then sign your RPM packages using a gpg key.

Deploying the RPM packages/Creating a repository

Repository managers such as Sonatype Nexus or JFrog artifactory allow you to deploy and manage RPM packages using a rest API. You can refer to their documentation in order to see how. It is usually as easy as a wget/curl http(s) request.

Though it is also quite simple to create repositories. Just move/copy your rpm package binaries to a well known folder, and run the following command in that folder:

Wrapping up:

If you have some questions, don’t hesitate and add a comment to this blog post.

For some examples of RPM packages, head here, or simply search the web for existing SPECs 😉 .

In the next blog entries (links will be available as soon as they are published), we’ll see:

  1. how to package a platform-independent software (e.g. Java-based) easily, and specific gotchas for that scenario.
  2. how to package a platform-dependent software, built from source (if starting from binary, the first scenario should be relevant)
  3. how to create a SPEC file which produces multiple different packages (and why it is useful)
Pierre-Antoine Grégoire
No Comments

Post a Comment

Comment
Name
Email
Website