Chapter 6. Advanced Topics
This chapter covers topics that are beyond the scope of the introductory tutorial but are often useful in real-world RPM packaging.
6.1. Signing Packages
Signing a package is a way to secure the package for an end user. Secure transport can be achieved with implementation of the HTTPS protocol, which can be done when the package is downloaded just before installing. However, the packages are often downloaded in advance and stored in local repositories before they are used. The packages are signed to make sure no third party can alter the content of a package.
There are three ways to sign a package:
6.1.1. Adding a Signature to a Package
In most cases packages are built without a signature. The signature is added just before the release of the package.
In order to add another signature to the package package, use the --addsign option. Having more than one signature makes it possible to record the package’s path of ownership from the package builder to the end-user.
As an example, a division of a company creates a package and signs it with the division’s key. The company’s headquarters then checks the package’s signature and adds the corporate signature to the package, stating that the signed package is authentic.
With two signatures, the package makes its way to a retailer. The retailer checks the signatures and, if they check out, adds their signature as well.
The package now makes its way to a company that wishes to deploy the package. After checking every signature on the package, they know that it is an authentic copy, unchanged since it was first created. Depending on the deploying company’s internal controls, they may choose to add their own signature, to reassure their employees that the package has received their corporate approval.
The output from the --addsign option:
$ rpm --addsign blather-7.9-1.i386.rpm
Enter pass phrase:
Pass phrase is good.
blather-7.9-1.i386.rpm:To check the signatures of a package with multiple signatures:
$ rpm --checksig blather-7.9-1.i386.rpm blather-7.9-1.i386.rpm: size pgp pgp md5 OK
The two pgp strings in the output of the rpm --checksig command show that the package has been signed twice.
RPM makes it possible to add the same signature multiple times. The --addsign option does not check for multiple identical signatures.
$ rpm --addsig blather-7.9-1.i386.rpm
Enter pass phrase:
Pass phrase is good.
blather-7.9-1.i386.rpm:
$ rpm --addsig blather-7.9-1.i386.rpm
Enter pass phrase:
Pass phrase is good.
blather-7.9-1.i386.rpm:
$ rpm --addsig blather-7.9-1.i386.rpm
Enter pass phrase:
Pass phrase is good.
blather-7.9-1.i386.rpm:
$ rpm --checksig blather-7.9-1.i386.rpm
blather-7.9-1.i386.rpm: size pgp pgp pgp pgp md5 OK
The output of the rpm --checksig command displays four signatures.
6.1.2. Replacing a Package Signature
To change the public key without having to rebuild each package, use the --resign option.
$ rpm --resign blather-7.9-1.i386.rpm
Enter pass phrase:
Pass phrase is good.
blather-7.9-1.i386.rpm:To use the --resign option on multiple package files:
$ rpm --resign b*.rpm
Enter pass phrase:
Pass phrase is good.
blather-7.9-1.i386.rpm:
bother-3.5-1.i386.rpm:6.1.3. Build-time Signing
To sign a package at build-time, use the rpmbuild command with the --sign option. This requires entering the PGP passphrase.
For example:
$ rpmbuild -ba --sign blather-7.9.spec
Enter pass phrase:
Pass phrase is good.
* Package: blather
…
Binary Packaging: blather-7.9-1
Finding dependencies...
…
Generating signature: 1002
Wrote: /usr/src/redhat/RPMS/i386/blather-7.9-1.i386.rpm
…
Source Packaging: blather-7.9-1
…
Generating signature: 1002
Wrote: /usr/src/redhat/SRPMS/blather-7.9-1.src.rpmThe "Generating signature" message appears in both the binary and source packaging sections. The number following the message indicates that the signature added was created using PGP.
When using the --sign option for rpmbuild, use only -bb or -ba options for package building.
To verify the signature of a package, use the rpm command with --checksig option. For example:
$ rpm --checksig blather-7.9-1.i386.rpm blather-7.9-1.i386.rpm: size pgp md5 OK
6.1.3.1. Building Multiple Packages
When building multiple packages, use the following syntax to avoid entering the PGP passphrase multiple times. For example when building the blather and bother packages, sign them by using the following:
$ rpmbuild -ba --sign b*.spec
Enter pass phrase:
Pass phrase is good.
* Package: blather
…
Binary Packaging: blather-7.9-1
…
Generating signature: 1002
Wrote: /usr/src/redhat/RPMS/i386/blather-7.9-1.i386.rpm
…
Source Packaging: blather-7.9-1
…
Generating signature: 1002
Wrote: /usr/src/redhat/SRPMS/blather-7.9-1.src.rpm
…
* Package: bother
…
Binary Packaging: bother-3.5-1
…
Generating signature: 1002
Wrote: /usr/src/redhat/RPMS/i386/bother-3.5-1.i386.rpm
…
Source Packaging: bother-3.5-1
…
Generating signature: 1002
Wrote: /usr/src/redhat/SRPMS/bother-3.5-1.src.rpm6.2. More on Macros
There are many built-in RPM Macros and we will cover a few in the following section, however an exhaustive list can be found at the rpm.org rpm macro official documentation.
There are also macros that are provided by Red Hat Enterprise Linux, some of which we cover in this section. We also see how to inspect your system to learn about other macros.
6.2.1. Defining Your Own Macros
You can define your own Macros. Below is an excerpt from the RPM Official Documentation, which provides a comprehensive reference on macros capabilities.
To define a macro, use:
%global <name>[(opts)] <body>
All whitespace surrounding \ is removed. Name may be composed of alphanumeric characters, and the character _ and must be at least 3 characters in length. A macro without an (opts) field is “simple” in that only recursive macro expansion is performed. A parameterized macro contains an (opts) field. The opts (the string between parentheses) is passed exactly as is to getopt(3) for argc/argv processing at the beginning of a macro invocation.
Older RPM SPEC files may use the %define <name> <body> macro pattern. The differences between %define and %global macros are as follows:
-
%definehas local scope, which means that it applies only to a specified part of a SPEC file. In addition, the body of a%definemacro is expanded when used—it is lazily evaluated. -
%globalhas global scope, which means that it applies to an entire SPEC file. In addition, the body of a%globalmacro is expanded at definition time.
Examples:
%global githash 0ec4e58
%global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")
Macros are always evaluated, even in comments. Sometimes it is harmless. But in the second example, we are executing python command to get the content of a macro. This command will be executed even when you comment out the macro. Or when you put the name of the macro into %changelog. To comment out macro, use %%. For example: %%global.
6.2.2. %setup
Macro %setup can be used to build the package with source code tarballs. Standard behavior of the %setup macro can be seen in the rpmbuild output. At the beginning of each phase macro outputs Executing(%something). For example:
Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.DhddsG
The shell output is set with set -x enabled. To see the content of /var/tmp/rpm-tmp.DhddsG use --debug option, since rpmbuild deletes temporary files after successful build. This displays the setup of environment variables followed by for example:
cd '/builddir/build/BUILD' rm -rf 'cello-1.0' /usr/bin/gzip -dc '/builddir/build/SOURCES/cello-1.0.tar.gz' | /usr/bin/tar -xof - STATUS=$? if [ $STATUS -ne 0 ]; then exit $STATUS fi cd 'cello-1.0' /usr/bin/chmod -Rf a+rX,u+w,g-w,o-w .
The %setup ensures that we are working in the right directory, removes residues of previous builds, unpacks the source tarball, and sets up some default privileges. There are multiple options to adjust the behavior of the %setup macro.
6.2.2.1. %setup -q
Option -q limits verbosity of %setup macro. Only tar -xof is executed instead of tar -xvvof. This option has to be used as first.
6.2.2.2. %setup -n
In some cases, the directory from expanded tarball has a different name than expected %{name}-%{version}. This can lead to an error of the %setup macro. The name of a directory has to be specified by -n directory_name option.
For example, if the package name is cello, but the source code is archived in hello-1.0.tgz and contained hello/ directory, the spec file content needs to be:
Name: cello
Source0: https://example.com/%{name}/release/hello-%{version}.tar.gz
…
%prep
%setup -n hello6.2.2.3. %setup -c
The -c option can be used if the source code tarball does not contain any subdirectories and after unpacking, files from an archive fill the current directory. The -c option creates the directory and steps into the archive expansion. An illustrative example:
/usr/bin/mkdir -p cello-1.0 cd 'cello-1.0'
The directory is not changed after archive expansion.
6.2.2.4. %setup -D and -T
-D option disables deleting of source code directory. This option is useful if %setup macro is used several times. Essentially, -D option means that following lines are not used:
rm -rf 'cello-1.0'
The -T option disables expansion of the source code tarball by removing the following line from the script:
/usr/bin/gzip -dc '/builddir/build/SOURCES/cello-1.0.tar.gz' | /usr/bin/tar -xvvof -
6.2.2.5. %setup -a and -b
Options -a and -b expand specific sources.
-
Option
-b(which stands forbefore) expands specific sources before entering the working directory. -
Option
-a(which stands forafter) expands those sources after entering. Their arguments are source numbers from the spec file preamble.
For example, let’s say the cello-1.0.tar.gz archive contains empty examples directory, and the examples are shipped in separate examples.tar.gz tarball and they expand into the directory of the same name. In this case use -a 1, as we want to expand Source1 after entering the working directory:
Source0: https://example.com/%{name}/release/%{name}-%{version}.tar.gz
Source0: examples.tar.gz
…
%prep
%setup -a 1
But if the examples were in the separate cello-1.0-examples.tar.gz tarball, which expands into cello-1.0/examples, use -b 1 options, since the Source1 should be expanded before entering the working directory:
Source0: https://example.com/%{name}/release/%{name}-%{version}.tar.gz
Source0: %{name}-%{version}-examples.tar.gz
…
%prep
%setup -b 1You can also use a combination of all these options.
6.2.3. %files
Common “advanced” RPM Macros needed in the %files section are as follows:
| Macro | Definition |
| %license |
This identifies the file listed as a LICENSE file and it will be installed and labeled as such by RPM. Example: |
| %doc |
This identifies the file listed as documentation and it will be installed and labeled as such by RPM. This is often used not only for documentation about the software being packaged but also code examples and various items that should accompany documentation. In the event code examples are included, care should be taken to remove executable mode from the file. Example: |
| %dir |
Identifies that the path is a directory that should be owned by this RPM. This is important so that the rpm file manifest accurately knows what directories to clean up on uninstall. Example: |
| %config(noreplace) |
Specifies that the following file is a configuration file and therefore should not be overwritten (or replaced) on a package install or update if the file has been modified from the original installation checksum. In the event that there is a change, the file will be created with |
6.2.4. Built-In Macros
Your system has many built-in RPM Macros and the fastest way to view them all is to simply run the rpm --showrc command. Note that this will contain a lot of output so it is often used in combination with a pipe to grep.
You can also find information about the RPMs macros that come directly with your system’s version of RPM by looking at the output of the rpm -ql rpm taking note of the files titled macros in the directory structure.
6.2.5. RPM Distribution Macros
Different distributions will supply different sets of recommended RPM Macros based on the language implementation of the software being packaged or the specific Guidelines of the distribution in question.
These are often provided as RPM Packages themselves and can be installed with the distribution package manager, such as yum. The macro files themselves once installed can be found in /usr/lib/rpm/macros.d/ and will be included in the rpm --showrc output by default once installed.
One primary example of this is the Fedora Packaging Guidelines section pertaining specifically to Application Specific Guidelines which at the time of this writing has over 60 different sets of guidelines along with associated RPM Macro sets for subject matter specific RPM Packaging.
One example of this kind of RPMs would be for Python version 2.x and if we have the python2-rpm-macros package installed (available in EPEL for RHEL 7), we have a number of python2 specific macros available to us.
$ rpm -ql python2-rpm-macros
/usr/lib/rpm/macros.d/macros.python2
$ rpm --showrc | grep python2
-14: __python2 /usr/bin/python2
CFLAGS="%{optflags}" %{__python2} %{py_setup} %{?py_setup_args} build --executable="%{__python2} %{py2_shbang_opts}" %{?1}
CFLAGS="%{optflags}" %{__python2} %{py_setup} %{?py_setup_args} install -O1 --skip-build --root %{buildroot} %{?1}
-14: python2_sitearch %(%{__python2} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(1))")
-14: python2_sitelib %(%{__python2} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")
-14: python2_version %(%{__python2} -c "import sys; sys.stdout.write('{0.major}.{0.minor}'.format(sys.version_info))")
-14: python2_version_nodots %(%{__python2} -c "import sys; sys.stdout.write('{0.major}{0.minor}'.format(sys.version_info))")
The above output displays the raw RPM Macro definitions, but we are likely more interested in what these will evaluate to which we can do with rpm --eval in order to determine what they do as well as how they may be helpful to us when packaging RPMs.
$ rpm --eval %{__python2}
/usr/bin/python2
$ rpm --eval %{python2_sitearch}
/usr/lib64/python2.7/site-packages
$ rpm --eval %{python2_sitelib}
/usr/lib/python2.7/site-packages
$ rpm --eval %{python2_version}
2.7
$ rpm --eval %{python2_version_nodots}
276.3. Custom Macros
You can override the distribution macros in the ~/.rpmmacros file. Any changes you make will affect every build on your machine.
There are several macros you can use to override:
%_topdir /opt/some/working/directory/rpmbuild-
You can create this directory, including all subdirectories using the
rpmdev-setuptreeutility. The value of this macro is by default~/rpmbuild. %_smp_mflags -l3-
This macro is often used to pass to Makefile, for example
make %{?_smp_mflags}, and to set a number of concurrent processes during the build phase. By default, it is set to-jX, whereXis a number of cores. If you alter the number of cores, you can speed up or slow down a build of packages.
While you can define any new macros in the ~/.rpmmacros file, this is discouraged, because those macros would not be present on other machines, where users may want to try to rebuild your package.
6.4. Epoch, Scriptlets, and Triggers
There are various topics in the world of RPM spec files that are considered advanced because they have implications on not only the spec file, how the package is built, but also on the end machine that the resulting RPM is installed upon. In this section we will cover the most common of these such as Epoch, Scriptlets, and Triggers.
6.4.1. Epoch
First on the list is Epoch, epoch is a way to define weighted dependencies based on version numbers. It’s default value is 0 and this is assumed if an Epoch directive is not listed in the RPM spec file. This was not covered in the spec File section of this guide because it is almost always a bad idea to introduce an Epoch value as it will skew what you would normally otherwise expect RPM to do when comparing versions of packages.
For example if a package foobar with Epoch: 1 and Version: 1.0 was installed and someone else packaged foobar with Version: 2.0 but simply omitted the Epoch directive either because they were unaware of it’s necessity or simply forgot, that new version would never be considered an update because the Epoch version would win out over the traditional Name-Version-Release marker that signifies versioning for RPM Packages.
This approach is generally only used when absolutely necessary (as a last resort) to resolve an upgrade ordering issue which can come up as a side effect of upstream software changing versioning number schemes or versions incorporating alphabetical characters that can not always be compared reliably based on encoding.
6.4.2. Triggers and Scriptlets
In RPM Packages, there are a series of directives that can be used to inflict necessary or desired change on a system during install time of the RPM. These are called scriptlets.
One primary example of when and why you’d want to do this is when a system service RPM is installed and it provides a systemd unit file. At install time we will need to notify systemd that there is a new unit so that the system administrator can run a command similar to systemctl start foo.service after the fictional RPM foo (which provides some service daemon in this example) has been installed. Similarly, we would need to inverse of this action upon uninstallation so that an administrator would not get errors due to the daemon’s binary no longer being installed but the unit file still existing in systemd’s running configuration.
There are a small handful of common scriptlet directives, they are similar to the “section headers” like %build or %install in that they are defined by multi-line segments of code, often written as standard POSIX shell script but can be a few different programming languages such that RPM for the target machine’s distribution is configured to allow them. An exhaustive list of these available languages can be found in the RPM Official Documentation.
Scriptlet directives are as follows:
| Directive | Definition |
|
| Scriptlet that is executed just before the package is installed on the target system. |
|
| Scriptlet that is executed just after the package is installed on the target system. |
|
| Scriptlet that is executed just before the package is uninstalled from the target system. |
|
| Scriptlet that is executed just after the package is uninstalled from the target system. |
Is is also common for RPM Macros to exist for this function. In our previous example we discussed systemd needing to be notified about a new unit file, this is easily handled by the systemd scriptlet macros as we can see from the below example output. More information on this can be found in the Fedora systemd Packaging Guidelines.
$ rpm --showrc | grep systemd
-14: __transaction_systemd_inhibit %{__plugindir}/systemd_inhibit.so
-14: _journalcatalogdir /usr/lib/systemd/catalog
-14: _presetdir /usr/lib/systemd/system-preset
-14: _unitdir /usr/lib/systemd/system
-14: _userunitdir /usr/lib/systemd/user
/usr/lib/systemd/systemd-binfmt %{?*} >/dev/null 2>&1 || :
/usr/lib/systemd/systemd-sysctl %{?*} >/dev/null 2>&1 || :
-14: systemd_post
-14: systemd_postun
-14: systemd_postun_with_restart
-14: systemd_preun
-14: systemd_requires
Requires(post): systemd
Requires(preun): systemd
Requires(postun): systemd
-14: systemd_user_post %systemd_post --user --global %{?*}
-14: systemd_user_postun %{nil}
-14: systemd_user_postun_with_restart %{nil}
-14: systemd_user_preun
systemd-sysusers %{?*} >/dev/null 2>&1 || :
echo %{?*} | systemd-sysusers - >/dev/null 2>&1 || :
systemd-tmpfiles --create %{?*} >/dev/null 2>&1 || :
$ rpm --eval %{systemd_post}
if [ $1 -eq 1 ] ; then
# Initial installation
systemctl preset >/dev/null 2>&1 || :
fi
$ rpm --eval %{systemd_postun}
systemctl daemon-reload >/dev/null 2>&1 || :
$ rpm --eval %{systemd_preun}
if [ $1 -eq 0 ] ; then
# Package removal, not upgrade
systemctl --no-reload disable > /dev/null 2>&1 || :
systemctl stop > /dev/null 2>&1 || :
fiAnother item that provides even more fine grained control over the RPM Transaction as a whole is what is known as triggers. These are effectively the same thing as a scriptlet but are executed in a very specific order of operations during the RPM install or upgrade transaction allowing for a more fine grained control over the entire process.
The order in which each is executed and the details of which are provided below.
all-%pretrans
...
any-%triggerprein (%triggerprein from other packages set off by new install)
new-%triggerprein
new-%pre for new version of package being installed
... (all new files are installed)
new-%post for new version of package being installed
any-%triggerin (%triggerin from other packages set off by new install)
new-%triggerin
old-%triggerun
any-%triggerun (%triggerun from other packages set off by old uninstall)
old-%preun for old version of package being removed
... (all old files are removed)
old-%postun for old version of package being removed
old-%triggerpostun
any-%triggerpostun (%triggerpostun from other packages set off by old un
install)
...
all-%posttrans
The above items are from the included RPM documentation found in /usr/share/doc/rpm-4.*/triggers.
6.4.2.1. Using Non-Shell Scripts in spec File
A scriptlet option, -p, in a spec file allows to invoke a specific interpreter instead of the default -p /bin/sh. An illustrative example is a script, which prints out a message after the installation of pello.py.
-
Open the
pello.specfile. Find the following line:
install -m 0644 %{name}.py* %{buildroot}/usr/lib/%{name}/Under this line, insert the following code:
%post -p /usr/bin/python3 print("This is {} code".format("python"))- Build your package according to the Section 5.2, “Building RPMS” chapter.
Install your package:
# yum install /home/<username>/rpmbuild/RPMS/noarch/pello-0.1.1-1.fc27.noarch.rpm
The output of this command is the following message after the installation:
Installing : pello-0.1.1-1.fc27.noarch 1/1 Running scriptlet: pello-0.1.1-1.fc27.noarch 1/1 This is python code
-
To use a Python 3 script: Write a line
%post -p /usr/bin/python3under the lineinstall -min a spec file. -
To use a Lua script: Write a line
%post -p <lua>under the lineinstall -min a spec file. - This way any interpreter can be specified in the spec file.
6.5. RPM Conditionals
RPM Conditionals enable the conditional inclusion of various sections of the spec file.
Most commonly, conditional inclusions deal with:
- architecture-specific sections
- operating system-specific sections
- compatibility issues between various versions of operating systems
- existence and definition of macros
6.5.1. RPM Conditionals Syntax
If expression is true, then do some action:
%if expression ... %endif
If expression is true, then do some action, in other case, do another action:
%if expression ... %else ... %endif
6.5.2. RPM Conditionals Examples
6.5.2.1. The %if Conditional
%if 0%{?rhel} == 6
sed -i '/AS_FUNCTION_DESCRIBE/ s/^/#/' configure.in
sed -i '/AS_FUNCTION_DESCRIBE/ s/^/#/' acinclude.m4
%endif
This conditional handles compatibility between RHEL6 and other operating systems in terms of support of the AS_FUNCTION_DESCRIBE macro. When the package is build for RHEL, the %rhel macro is defined and it is expanded to RHEL version. If its value is 6, meaning the package is build for RHEL 6, then the references to AS_FUNCTION_DESCRIBE, which is not supported by RHEL6, are deleted from autoconfig scripts.
%define ruby_archive %{name}-%{ruby_version}
%if 0%{?milestone:1}%{?revision:1} != 0
%define ruby_archive %{ruby_archive}-%{?milestone}%{?!milestone:%{?revision:r%{revision}}}
%endif
This conditional handles definition of the macros. If the %milestone or the %revision macros are set, the %ruby_archive macro, which defines the name of the upstream tarball, is redefined.
6.5.2.2. Specialized variants of %if Conditional
The %ifarch conditional, %ifnarch conditional and %ifos conditional are specialized variants of the %if conditionals. These variants are commonly used, so they have their own macros.
6.5.2.2.1. The %ifarch Conditional
The %ifarch conditional is used to begin a block of the SPEC file that is architecture-specific. It is followed by one or more architecture specifiers, each separated by commas or whitespace.
%ifarch i386 sparc ... %endif
All the contents of the spec file between %ifarch and %endif are processed only on the 32-bit AMD and Intel architectures or Sun SPARC-based systems.
6.5.2.2.2. The %ifnarch Conditional
The %ifnarch conditional has a reverse logic than %ifarch conditional.
%ifnarch alpha ... %endif
All the contents of the spec file between %ifnarch and %endif are processed only if not being done on a Digital Alpha/AXP-based system.
6.5.2.2.3. The %ifos Conditional
The %ifos conditional is used to control processing based on the operating system of the build. It can be followed by one or more operating system names.
%ifos linux ... %endif
All the contents of the spec file between %ifos and %endif are processed only if the build was done on a Linux system.

Where did the comment section go?
Red Hat's documentation publication system recently went through an upgrade to enable speedier, more mobile-friendly content. We decided to re-evaluate our commenting platform to ensure that it meets your expectations and serves as an optimal feedback mechanism. During this redesign, we invite your input on providing feedback on Red Hat documentation via the discussion platform.