Red Hat Training

A Red Hat training course is available for RHEL 8

Chapter 5. Advanced topics

This section covers topics that are beyond the scope of the introductory tutorial but are useful in real-world RPM packaging.

5.1. Signing RPM packages

You can sign RPM packages to ensure that no third party can alter their content. To add an additional layer of security, use the HTTPS protocol when downloading the package.

You can sign a package by using the --addsign option provided by the rpm-sign package.

Prerequisites

5.1.1. Creating a GPG key

Use the following procedure to create a GNU Privacy Guard (GPG) key required for signing packages.

Procedure

  1. Generate a GPG key pair:

    # gpg --gen-key
  2. Check the generated key pair:

    # gpg --list-keys
  3. Export the public key:

    # gpg --export -a '<Key_name>' > RPM-GPG-KEY-pmanager

    Replace <Key_name> with the real key name that you have selected.

  4. Import the exported public key into an RPM database:

    # rpm --import RPM-GPG-KEY-pmanager

5.1.2. Configuring RPM to sign a package

To be able to sign an RPM package, you need to specify the %_gpg_name RPM macro.

The following procedure describes how to configure RPM for signing a package.

Procedure

  • Define the %_gpg_name macro in your $HOME/.rpmmacros file as follows:

    %_gpg_name Key ID

    Replace Key ID with the GNU Privacy Guard (GPG) key ID that you will use to sign a package. A valid GPG key ID value is either a full name or email address of the user who created the key.

5.1.3. Adding a signature to an RPM package

The most usual case is when a package is built without a signature. The signature is added just before the release of the package.

To add a signature to an RPM package, use the --addsign option provided by the rpm-sign package.

Procedure

  • Add a signature to a package:

    $ rpm --addsign package-name.rpm

    Replace package-name with the name of an RPM package you want to sign.

    Note

    You must enter the password to unlock the secret key for the signature.

5.2. More on macros

This section covers selected built-in RPM Macros. For an exhaustive list of such macros, see RPM Documentation.

5.2.1. Defining your own macros

The following section describes how to create a custom macro.

Procedure

  • Include the following line in the RPM SPEC file:

    %global <name>[(opts)] <body>

All whitespace surrounding <body> is removed. Name may be composed of alphanumeric characters, and the character _ and must be at least 3 characters in length. Inclusion of the (opts) field is optional:

  • Simple macros do not contain the (opts) field. In this case, only recursive macro expansion is performed.
  • Parametrized macros contain the (opts) field. The opts string between parentheses is passed to getopt(3) for argc/argv processing at the beginning of a macro invocation.
Note

Older RPM SPEC files use the %define <name> <body> macro pattern instead. The differences between %define and %global macros are as follows:

  • %define has local scope. It applies to a specific part of a SPEC file. The body of a %define macro is expanded when used.
  • %global has global scope. It applies to an entire SPEC file. The body of a %global macro is expanded at definition time.
Important

Macros are evaluated even if they are commented out or the name of the macro is given into the %changelog section of the SPEC file. To comment out a macro, use %%. For example: %%global.

Additional resources

5.2.2. Using the %setup macro

This section describes how to build packages with source code tarballs using different variants of the %setup macro. Note that the macro variants can be combined. The rpmbuild output illustrates standard behavior of the %setup macro. At the beginning of each phase, the macro outputs Executing(%…​), as shown in the below example.

Example 5.1. Example %setup macro output

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 the --debug option because rpmbuild deletes temporary files after a 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 macro:

  • Ensures that we are working in the correct directory.
  • Removes residues of previous builds.
  • Unpacks the source tarball.
  • Sets up some default privileges.

5.2.2.1. Using the %setup -q macro

The -q option limits the verbosity of the %setup macro. Only tar -xof is executed instead of tar -xvvof. Use this option as the first option.

5.2.2.2. Using the %setup -n macro

The -n option is used to specify the name of the directory from expanded tarball.

This is used in cases when the directory from expanded tarball has a different name from what is expected (%{name}-%{version}), which can lead to an error of the %setup macro.

For example, if the package name is cello, but the source code is archived in hello-1.0.tgz and contains the hello/ directory, the SPEC file content needs to be as follows:

Name: cello
Source0: https://example.com/%{name}/release/hello-%{version}.tar.gz
…
%prep
%setup -n hello

5.2.2.3. Using the %setup -c macro

The -c option is used if the source code tarball does not contain any subdirectories and after unpacking, files from an archive fills the current directory.

The -c option then creates the directory and steps into the archive expansion as shown below:

/usr/bin/mkdir -p cello-1.0
cd 'cello-1.0'

The directory is not changed after archive expansion.

5.2.2.4. Using the %setup -D and %setup -T macros

The -D option disables deleting of source code directory, and is particularly useful if the %setup macro is used several times. With the -D option, the 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 -

5.2.2.5. Using the %setup -a and %setup -b macros

The -a and -b options expand specific sources:

  • The -b option stands for before. This option expands specific sources before entering the working directory.
  • The -a option stands for after. This option expands those sources after entering. Their arguments are source numbers from the SPEC file preamble.

In the following example, the cello-1.0.tar.gz archive contains an empty examples directory. The examples are shipped in a separate examples.tar.gz tarball and they expand into the directory of the same name. In this case, use -a 1 if you want to expand Source1 after entering the working directory:

Source0: https://example.com/%{name}/release/%{name}-%{version}.tar.gz
Source1: examples.tar.gz
…
%prep
%setup -a 1

In the following example, examples are provided in a separate cello-1.0-examples.tar.gz tarball, which expands into cello-1.0/examples. In this case, use -b 1 to expand Source1 before entering the working directory:

Source0: https://example.com/%{name}/release/%{name}-%{version}.tar.gz
Source1: %{name}-%{version}-examples.tar.gz
…
%prep
%setup -b 1

5.2.3. Common RPM macros in the %files section

The following table lists advanced RPM Macros that are needed in the %files section of a SPEC file.

Table 5.1. Advanced RPM Macros in the %files section

MacroDefinition

%license

The %license macro identifies the file listed as a LICENSE file and it will be installed and labeled as such by RPM. Example: %license LICENSE.

%doc

The %doc macro identifies a file listed as documentation and it will be installed and labeled as such by RPM. The %doc macro is used for documentation about the packaged software and also for code examples and various accompanying items. If code examples are included, care must be taken to remove executable mode from the file. Example: %doc README

%dir

The %dir macro ensures that the path is a directory owned by this RPM. This is important so that the RPM file manifest accurately knows what directories to clean up on uninstall. Example: %dir %{_libdir}/%{name}

%config(noreplace)

The %config(noreplace) macro ensures 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. If there is a change, the file will be created with .rpmnew appended to the end of the filename upon upgrade or install so that the pre-existing or modified file on the target system is not modified. Example: %config(noreplace) %{_sysconfdir}/%{name}/%{name}.conf

5.2.4. Displaying the built-in macros

Red Hat Enterprise Linux provides multiple built-in RPM macros.

Procedure

  1. To display all built-in RPM macros, run:

    rpm --showrc
    Note

    The output is quite sizeable. To narrow the result, use the command above with the grep command.

  2. To find information about the RPMs macros for your system’s version of RPM, run:

    rpm -ql rpm
    Note

    RPM macros are the files titled macros in the output directory structure.

5.2.5. RPM distribution macros

Different distributions provide different sets of recommended RPM macros based on the language implementation of the software being packaged or the specific guidelines of the distribution.

The sets of recommended RPM macros are often provided as RPM packages, ready to be installed with the yum package manager.

Once installed, the macro files can be found in the /usr/lib/rpm/macros.d/ directory.

Procedure

  • To display the raw RPM macro definitions, run:

    rpm --showrc

The above output displays the raw RPM macro definitions.

  • To determine what a macro does and how it can be helpful when packaging RPMs, run the rpm --eval command with the name of the macro used as its argument:

    rpm --eval %{_MACRO}

Additional resources

  • rpm man page

5.2.6. Creating custom macros

You can override the distribution macros in the ~/.rpmmacros file with your custom macros. Any changes that you make affect every build on your machine.

Warning

Defining any new macros in the ~/.rpmmacros file is not recommended. Such macros would not be present on other machines, where users may want to try to rebuild your package.

Procedure

  • To override a macro, run:

    %_topdir /opt/some/working/directory/rpmbuild

You can create the directory from the example above, including all subdirectories through the rpmdev-setuptree utility. The value of this macro is by default ~/rpmbuild.

%_smp_mflags -l3

The macro above 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, where X is a number of cores. If you alter the number of cores, you can speed up or slow down a build of packages.

5.3. Epoch, Scriptlets and Triggers

This section covers Epoch, Scriptlets, and Triggers, which represent advanced directives for RMP SPEC files.

All these directives influence not only the SPEC file, but also the end machine on which the resulting RPM is installed.

5.3.1. The Epoch directive

The Epoch directive enables to define weighted dependencies based on version numbers.

If this directive is not listed in the RPM SPEC file, the Epoch directive is not set at all. This is contrary to common belief that not setting Epoch results in an Epoch of 0. However, the yum utility treats an unset Epoch as the same as an Epoch of 0 for the purposes of depsolving.

However, listing Epoch in a SPEC file is usually omitted because in majority of cases introducing an Epoch value skews the expected RPM behavior when comparing versions of packages.

Example 5.2. Using Epoch

If you install the foobar package with Epoch: 1 and Version: 1.0, and someone else packages foobar with Version: 2.0 but without the Epoch directive, the new version will never be considered an update. The reason being that the Epoch version is preferred over the traditional Name-Version-Release marker that signifies versioning for RPM Packages.

Using of Epoch is thus quite rare. However, Epoch is typically used to resolve an upgrade ordering issue. The issue can appear as a side effect of upstream change in software version number schemes or versions incorporating alphabetical characters that cannot always be compared reliably based on encoding.

5.3.2. Scriptlets directives

Scriptlets are a series of RPM directives that are executed before or after packages are installed or deleted.

Use Scriptlets only for tasks that cannot be done at build time or in an start up script.

A set of common Scriptlet directives exists. They are similar to the SPEC file section headers, such as %build or %install. They are defined by multi-line segments of code, which are often written as a standard POSIX shell script. However, they can also be written in other programming languages that RPM for the target machine’s distribution accepts. RPM Documentation includes an exhaustive list of available languages.

The following table includes Scriptlet directives listed in their execution order. Note that a package containing the scripts is installed between the %pre and %post directive, and it is uninstalled between the %preun and %postun directive.

Table 5.2. Scriptlet directives

DirectiveDefinition

%pretrans

Scriptlet that is executed just before installing or removing any package.

%pre

Scriptlet that is executed just before installing the package on the target system.

%post

Scriptlet that is executed just after the package was installed on the target system.

%preun

Scriptlet that is executed just before uninstalling the package from the target system.

%postun

Scriptlet that is executed just after the package was uninstalled from the target system.

%posttrans

Scriptlet that is executed at the end of the transaction.

5.3.3. Turning off a scriptlet execution

The following procedure describes how to turn off the execution of any scriptlet using the rpm command together with the --no_scriptlet_name_ option.

Procedure

  • For example, to turn off the execution of the %pretrans scriptlets, run:

    # rpm --nopretrans

    You can also use the -- noscripts option, which is equivalent to all of the following:

    • --nopre
    • --nopost
    • --nopreun
    • --nopostun
    • --nopretrans
    • --noposttrans

Additional resources

  • rpm(8) man page.

5.3.4. Scriptlets macros

The Scriptlets directives also work with RPM macros.

The following example shows the use of systemd scriptlet macro, which ensures that systemd is notified about a new unit file.

$ 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 || :
fi

5.3.5. The Triggers directives

Triggers are RPM directives which provide a method for interaction during package installation and uninstallation.

Warning

Triggers may be executed at an unexpected time, for example on update of the containing package. Triggers are difficult to debug, therefore they need to be implemented in a robust way so that they do not break anything when executed unexpectedly. For these reasons, Red Hat recommends to minimize the use of Triggers.

The order of execution on a single package upgrade and the details for each existing Triggers are listed 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 found in the /usr/share/doc/rpm-4.*/triggers file.

5.3.6. Using non-shell scripts in a SPEC file

The -p scriptlet option in a SPEC file enables the user to invoke a specific interpreter instead of the default shell scripts interpreter (-p /bin/sh).

The following procedure describes how to create a script, which prints out a message after installation of the pello.py program:

Procedure

  1. Open the pello.spec file.
  2. Find the following line:

    install -m 0644 %{name}.py* %{buildroot}/usr/lib/%{name}/
  3. Under the above line, insert:

    %post -p /usr/bin/python3
    print("This is {} code".format("python"))
  4. Build your package as described in Building RPMs.
  5. Install your package:

    # yum install /home/<username>/rpmbuild/RPMS/noarch/pello-0.1.2-1.el8.noarch.rpm
  6. Check the output message after the installation:

    Installing       : pello-0.1.2-1.el8.noarch                              1/1
    Running scriptlet: pello-0.1.2-1.el8.noarch                              1/1
    This is python code
Note

To use a Python 3 script, include the following line under install -m in a SPEC file:

%post -p /usr/bin/python3

To use a Lua script, include the following line under install -m in a SPEC file:

%post -p <lua>

This way, you can specify any interpreter in a SPEC file.

5.4. RPM conditionals

RPM Conditionals enable conditional inclusion of various sections of the SPEC file.

Conditional inclusions usually deal with:

  • Architecture-specific sections
  • Operating system-specific sections
  • Compatibility issues between various versions of operating systems
  • Existence and definition of macros

5.4.1. RPM conditionals syntax

RPM conditionals use the following 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

5.4.2. The %if conditionals

The following examples shows the usage of %if RPM conditionals.

Example 5.3. Using the %if conditional to handle compatibility between Red Hat Enterprise Linux 8 and other operating systems

%if 0%{?rhel} == 8
sed -i '/AS_FUNCTION_DESCRIBE/ s/^/#/' configure.in
sed -i '/AS_FUNCTION_DESCRIBE/ s/^/#/' acinclude.m4
%endif

This conditional handles compatibility between RHEL 8 and other operating systems in terms of support of the AS_FUNCTION_DESCRIBE macro. If the package is built for RHEL, the %rhel macro is defined, and it is expanded to RHEL version. If its value is 8, meaning the package is build for RHEL 8, then the references to AS_FUNCTION_DESCRIBE, which is not supported by RHEL 8, are deleted from autoconfig scripts.

Example 5.4. Using the %if conditional to handle definition of macros

%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 macros. If the %milestone or the %revision macros are set, the %ruby_archive macro, which defines the name of the upstream tarball, is redefined.

5.4.3. Specialized variants of %if conditionals

The %ifarch conditional, %ifnarch conditional and %ifos conditional are specialized variants of the %if conditionals. These variants are commonly used, hence they have their own macros.

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.

Example 5.5. An example use of the %ifarch conditional

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

The %ifnarch conditional

The %ifnarch conditional has a reverse logic than %ifarch conditional.

Example 5.6. An example use of the %ifnarch conditional

%ifnarch alpha
…​
%endif

All the contents of the SPEC file between %ifnarch and %endif are processed only if not done on a Digital Alpha/AXP-based system.

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.

Example 5.7. An example use of the %ifos conditional

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

5.5. Packaging Python 3 RPMs

Most Python projects use Setuptools for packaging, and define package information in the setup.py file. For more information about Setuptools packaging, see the Setuptools documentation.

You can also package your Python project into an RPM package, which provides the following advantages compared to Setuptools packaging:

  • Specification of dependencies of a package on other RPMs (even non-Python)
  • Cryptographic signing

    With cryptographic signing, content of RPM packages can be verified, integrated, and tested with the rest of the operating system.

5.5.1. SPEC file description for a Python package

A SPEC file contains instructions that the rpmbuild utility uses to build an RPM. The instructions are included in a series of sections. A SPEC file has two main parts in which the sections are defined:

  • Preamble (contains a series of metadata items that are used in the Body)
  • Body (contains the main part of the instructions)

An RPM SPEC file for Python projects has some specifics compared to non-Python RPM SPEC files. Most notably, a name of any RPM package of a Python library must always include the prefix determining the version, for example, python3 for Python 3.6, python38 for Python 3.8, python39 for Python 3.9, or python3.11 for Python 3.11.

Other specifics are shown in the following SPEC file example for the python3-detox package. For description of such specifics, see the notes below the example.

%global modname detox                                                1

Name:           python3-detox                                        2
Version:        0.12
Release:        4%{?dist}
Summary:        Distributing activities of the tox tool
License:        MIT
URL:            https://pypi.io/project/detox
Source0:        https://pypi.io/packages/source/d/%{modname}/%{modname}-%{version}.tar.gz

BuildArch:      noarch

BuildRequires:  python36-devel                                       3
BuildRequires:  python3-setuptools
BuildRequires:  python36-rpm-macros
BuildRequires:  python3-six
BuildRequires:  python3-tox
BuildRequires:  python3-py
BuildRequires:  python3-eventlet

%?python_enable_dependency_generator                                 4

%description

Detox is the distributed version of the tox python testing tool. It makes efficient use of multiple CPUs by running all possible activities in parallel.
Detox has the same options and configuration that tox has, so after installation you can run it in the same way and with the same options that you use for tox.

    $ detox

%prep
%autosetup -n %{modname}-%{version}

%build
%py3_build                                                           5

%install
%py3_install

%check
%{__python3} setup.py test                                           6

%files -n python3-%{modname}
%doc CHANGELOG
%license LICENSE
%{_bindir}/detox
%{python3_sitelib}/%{modname}/
%{python3_sitelib}/%{modname}-%{version}*

%changelog
...
1
The modname macro contains the name of the Python project. In this example it is detox.
2
When packaging a Python project into RPM, the python3 prefix always needs to be added to the original name of the project. The original name here is detox and the name of the RPM is python3-detox.
3
BuildRequires specifies what packages are required to build and test this package. In BuildRequires, always include items providing tools necessary for building Python packages: python36-devel and python3-setuptools. The python36-rpm-macros package is required so that files with /usr/bin/python3 interpreter directives are automatically changed to /usr/bin/python3.6.
4
Every Python package requires some other packages to work correctly. Such packages need to be specified in the SPEC file as well. To specify the dependencies, you can use the %python_enable_dependency_generator macro to automatically use dependencies defined in the setup.py file. If a package has dependencies that are not specified using Setuptools, specify them within additional Requires directives.
5
The %py3_build and %py3_install macros run the setup.py build and setup.py install commands, respectively, with additional arguments to specify installation locations, the interpreter to use, and other details.
6
The check section provides a macro that runs the correct version of Python. The %{__python3} macro contains a path for the Python 3 interpreter, for example /usr/bin/python3. We recommend to always use the macro rather than a literal path.

5.5.2. Common macros for Python 3 RPMs

In a SPEC file, always use the macros that are described in the following Macros for Python 3 RPMs table rather than hardcoding their values.

In macro names, always use python3 or python2 instead of unversioned python. Configure the particular Python 3 version in the BuildRequires section of the SPEC file to python36-rpm-macros, python38-rpm-macros, python39-rpm-macros, or python3.11-rpm-macros.

Table 5.3. Macros for Python 3 RPMs

MacroNormal DefinitionDescription

%{__python3}

/usr/bin/python3

Python 3 interpreter

%{python3_version}

3.6

The full version of the Python 3 interpreter.

%{python3_sitelib}

/usr/lib/python3.6/site-packages

Where pure-Python modules are installed.

%{python3_sitearch}

/usr/lib64/python3.6/site-packages

Where modules containing architecture-specific extensions are installed.

%py3_build

 

Runs the setup.py build command with arguments suitable for a system package.

%py3_install

 

Runs the setup.py install command with arguments suitable for a system package.

5.5.3. Automatic provides for Python RPMs

When packaging a Python project, make sure that the following directories are included in the resulting RPM if these directories are present:

  • .dist-info
  • .egg-info
  • .egg-link

From these directories, the RPM build process automatically generates virtual pythonX.Ydist provides, for example, python3.6dist(detox). These virtual provides are used by packages that are specified by the %python_enable_dependency_generator macro.

5.6. Handling interpreter directives in Python scripts

In Red Hat Enterprise Linux 8, executable Python scripts are expected to use interpreter directives (also known as hashbangs or shebangs) that explicitly specify at a minimum the major Python version. For example:

#!/usr/bin/python3
#!/usr/bin/python3.6
#!/usr/bin/python3.8
#!/usr/bin/python3.9
#!/usr/bin/python3.11
#!/usr/bin/python2

The /usr/lib/rpm/redhat/brp-mangle-shebangs buildroot policy (BRP) script is run automatically when building any RPM package, and attempts to correct interpreter directives in all executable files.

The BRP script generates errors when encountering a Python script with an ambiguous interpreter directive, such as:

#!/usr/bin/python

or

#!/usr/bin/env python

5.6.1. Modifying interpreter directives in Python scripts

Modify interpreter directives in the Python scripts that cause the build errors at RPM build time.

Prerequisites

  • Some of the interpreter directives in your Python scripts cause a build error.

Procedure

To modify interpreter directives, complete one of the following tasks:

  • Apply the pathfix.py script from the platform-python-devel package:

    # pathfix.py -pn -i %{__python3} PATH …​

    Note that multiple PATHs can be specified. If a PATH is a directory, pathfix.py recursively scans for any Python scripts matching the pattern ^[a-zA-Z0-9_]+\.py$, not only those with an ambiguous interpreter directive. Add this command to the %prep section or at the end of the %install section.

  • Modify the packaged Python scripts so that they conform to the expected format. For this purpose, pathfix.py can be used outside the RPM build process, too. When running pathfix.py outside an RPM build, replace %{__python3} from the example above with a path for the interpreter directive, such as /usr/bin/python3.

If the packaged Python scripts require a version other than Python 3.6, adjust the preceding commands to include the required version.

5.6.2. Changing /usr/bin/python3 interpreter directives in your custom packages

By default, interpreter directives in the form of /usr/bin/python3 are replaced with interpreter directives pointing to Python from the platform-python package, which is used for system tools with Red Hat Enterprise Linux. You can change the /usr/bin/python3 interpreter directives in your custom packages to point to a specific version of Python that you have installed from the AppStream repository.

Procedure

  • To build your package for a specific version of Python, add the python*-rpm-macros subpackage of the respective python package to the BuildRequires section of the SPEC file. For example, for Python 3.6, include the following line:

    BuildRequires:  python36-rpm-macros

    As a result, the /usr/bin/python3 interpreter directives in your custom package are automatically converted to /usr/bin/python3.6.

Note

To prevent the BRP script from checking and modifying interpreter directives, use the following RPM directive:

%undefine __brp_mangle_shebangs

5.7. RubyGems packages

This section explains what RubyGems packages are, and how to re-package them into RPM.

5.7.1. What RubyGems are

Ruby is a dynamic, interpreted, reflective, object-oriented, general-purpose programming language.

Programs written in Ruby are typically packaged using the RubyGems project, which provides a specific Ruby packaging format.

Packages created by RubyGems are called gems, and they can be re-packaged into RPM as well.

Note

This documentation refers to terms related to the RubyGems concept with the gem prefix, for example .gemspec is used for the gem specification, and terms related to RPM are unqualified.

5.7.2. How RubyGems relate to RPM

RubyGems represent Ruby’s own packaging format. However, RubyGems contain metadata similar to those needed by RPM, which enables the conversion from RubyGems to RPM.

According to Ruby Packaging Guidelines, it is possible to re-package RubyGems packages into RPM in this way:

  • Such RPMs fit with the rest of the distribution.
  • End users are able to satisfy dependencies of a gem by installing the appropriate RPM-packaged gem.

RubyGems use similar terminology as RPM, such as SPEC files, package names, dependencies and other items.

To fit into the rest of RHEL RPM distribution, packages created by RubyGems must follow the conventions listed below:

  • Names of gems must follow this pattern:

    rubygem-%{gem_name}
  • To implement a shebang line, the following string must be used:

    #!/usr/bin/ruby

5.7.3. Creating RPM packages from RubyGems packages

To create a source RPM for a RubyGems package, the following files are needed:

  • A gem file
  • An RPM SPEC file

The following sections describe how to create RPM packages from packages created by RubyGems.

5.7.3.1. RubyGems SPEC file conventions

A RubyGems SPEC file must meet the following conventions:

  • Contain a definition of %{gem_name}, which is the name from the gem’s specification.
  • The source of the package must be the full URL to the released gem archive; the version of the package must be the gem’s version.
  • Contain the BuildRequires: a directive defined as follows to be able to pull in the macros needed to build.

    BuildRequires:rubygems-devel
  • Not contain any RubyGems Requires or Provides, because those are autogenerated.
  • Not contain the BuildRequires: directive defined as follows, unless you want to explicitly specify Ruby version compatibility:

    Requires: ruby(release)

    The automatically generated dependency on RubyGems (Requires: ruby(rubygems)) is sufficient.

5.7.3.2. RubyGems macros

The following table lists macros useful for packages created by RubyGems. These macros are provided by the rubygems-devel packages.

Table 5.4. RubyGems' macros

Macro nameExtended pathUsage

%{gem_dir}

/usr/share/gems

Top directory for the gem structure.

%{gem_instdir}

%{gem_dir}/gems/%{gem_name}-%{version}

Directory with the actual content of the gem.

%{gem_libdir}

%{gem_instdir}/lib

The library directory of the gem.

%{gem_cache}

%{gem_dir}/cache/%{gem_name}-%{version}.gem

The cached gem.

%{gem_spec}

%{gem_dir}/specifications/%{gem_name}-%{version}.gemspec

The gem specification file.

%{gem_docdir}

%{gem_dir}/doc/%{gem_name}-%{version}

The RDoc documentation of the gem.

%{gem_extdir_mri}

%{_libdir}/gems/ruby/%{gem_name}-%{version}

The directory for gem extension.

5.7.3.3. RubyGems SPEC file example

Example SPEC file for building gems together with an explanation of its particular sections follows.

An example RubyGems SPEC file

%prep
%setup -q -n  %{gem_name}-%{version}

# Modify the gemspec if necessary
# Also apply patches to code if necessary
%patch0 -p1

%build
# Create the gem as gem install only works on a gem file
gem build ../%{gem_name}-%{version}.gemspec

# %%gem_install compiles any C extensions and installs the gem into ./%%gem_dir
# by default, so that we can move it into the buildroot in %%install
%gem_install

%install
mkdir -p %{buildroot}%{gem_dir}
cp -a ./%{gem_dir}/* %{buildroot}%{gem_dir}/

# If there were programs installed:
mkdir -p %{buildroot}%{_bindir}
cp -a ./%{_bindir}/* %{buildroot}%{_bindir}

# If there are C extensions, copy them to the extdir.
mkdir -p %{buildroot}%{gem_extdir_mri}
cp -a .%{gem_extdir_mri}/{gem.build_complete,*.so} %{buildroot}%{gem_extdir_mri}/

The following table explains the specifics of particular items in a RubyGems SPEC file:

Table 5.5. RubyGems' SPEC directives specifics

SPEC directiveRubyGems specifics

%prep

RPM can directly unpack gem archives, so you can run the gem unpack comamnd to extract the source from the gem. The %setup -n %{gem_name}-%{version} macro provides the directory into which the gem has been unpacked. At the same directory level, the %{gem_name}-%{version}.gemspec file is automatically created, which can be used to rebuild the gem later, to modify the .gemspec, or to apply patches to the code.

%build

This directive includes commands or series of commands for building the software into machine code. The %gem_install macro operates only on gem archives, and the gem is recreated with the next gem build. The gem file that is created is then used by %gem_install to build and install the code into the temporary directory, which is ./%{gem_dir} by default. The %gem_install macro both builds and installs the code in one step. Before being installed, the built sources are placed into a temporary directory that is created automatically.

The %gem_install macro accepts two additional options: -n <gem_file>, which allows to override gem used for installation, and -d <install_dir>, which might override the gem installation destination; using this option is not recommended.

The %gem_install macro must not be used to install into the %{buildroot}.

%install

The installation is performed into the %{buildroot} hierarchy. You can create the directories that you need and then copy what was installed in the temporary directories into the %{buildroot} hierarchy. If this gem creates shared objects, they are moved into the architecture-specific %{gem_extdir_mri} path.

Additional resources

5.7.3.4. Converting RubyGems packages to RPM SPEC files with gem2rpm

The gem2rpm utility converts RubyGems packages to RPM SPEC files.

The following sections describe how to:

  • Install the gem2rpm utility
  • Display all gem2rpm options
  • Use gem2rpm to covert RubyGems packages to RPM SPEC files
  • Edit gem2rpm templates
5.7.3.4.1. Installing gem2rpm

The following procedure describes how to install the gem2rpm utility.

Procedure

$ gem install gem2rpm
5.7.3.4.2. Displaying all options of gem2rpm

The following procedure describes how to display all options of the gem2rpm utility.

Procedure

  • To see all options of gem2rpm, run:

    $ gem2rpm --help
5.7.3.4.3. Using gem2rpm to covert RubyGems packages to RPM SPEC files

The following procedure describes how to use the gem2rpm utility to covert RubyGems packages to RPM SPEC files.

Procedure

  • Download a gem in its latest version, and generate the RPM SPEC file for this gem:

    $ gem2rpm --fetch <gem_name> > <gem_name>.spec

The described procedure creates an RPM SPEC file based on the information provided in the gem’s metadata. However, the gem misses some important information that is usually provided in RPMs, such as the license and the changelog. The generated SPEC file thus needs to be edited.

5.7.3.4.4. gem2rpm templates

The gem2rpm template is a standard Embedded Ruby (ERB) file, which includes variables listed in the following table.

Table 5.6. Variables in the gem2rpm template

VariableExplanation

package

The Gem::Package variable for the gem.

spec

The Gem::Specification variable for the gem (the same as format.spec).

config

The Gem2Rpm::Configuration variable that can redefine default macros or rules used in spec template helpers.

runtime_dependencies

The Gem2Rpm::RpmDependencyList variable providing a list of package runtime dependencies.

development_dependencies

The Gem2Rpm::RpmDependencyList variable providing a list of package development dependencies.

tests

The Gem2Rpm::TestSuite variable providing a list of test frameworks allowing their execution.

files

The Gem2Rpm::RpmFileList variable providing an unfiltered list of files in a package.

main_files

The Gem2Rpm::RpmFileList variable providing a list of files suitable for the main package.

doc_files

The Gem2Rpm::RpmFileList variable providing a list of files suitable for the -doc subpackage.

format

The Gem::Format variable for the gem. Note that this variable is now deprecated.

5.7.3.4.5. Listing available gem2rpm templates

Use the following procedure describes to list all available gem2rpm templates.

Procedure

  • To see all available templates, run:

    $ gem2rpm --templates
5.7.3.4.6. Editing gem2rpm templates

You can edit the template from which the RPM SPEC file is generated instead of editing the generated SPEC file.

Use the following procedure to edit the gem2rpm templates.

Procedure

  1. Save the default template:

    $ gem2rpm -T > rubygem-<gem_name>.spec.template
  2. Edit the template as needed.
  3. Generate the SPEC file by using the edited template:

    $ gem2rpm -t rubygem-<gem_name>.spec.template <gem_name>-<latest_version.gem > <gem_name>-GEM.spec

You can now build an RPM package by using the edited template as described in Building RPMs.

5.8. How to handle RPM packages with Perls scripts

Since RHEL 8, the Perl programming language is not included in the default buildroot. Therefore, the RPM packages that include Perl scripts must explicitly indicate the dependency on Perl using the BuildRequires: directive in RPM SPEC file.

5.8.2. Using a specific Perl module

If a specific Perl module is required at build time, use the following procedure:

Procedure

  • Apply the following syntax in your RPM SPEC file:

    BuildRequires: perl(MODULE)
    Note

    Apply this syntax to Perl core modules as well, because they can move in and out of the perl package over time.

5.8.3. Limiting a package to a specific Perl version

To limit your package to a specific Perl version, follow this procedure:

Procedure

  • Use the perl(:VERSION) dependency with the desired version constraint in your RPM SPEC file:

    For example, to limit a package to Perl version 5.22 and later, use:

    BuildRequires: perl(:VERSION) >= 5.22
Warning

Do not use a comparison against the version of the perl package because it includes an epoch number.

5.8.4. Ensuring that a package uses the correct Perl interpreter

Red Hat provides multiple Perl interpreters, which are not fully compatible. Therefore, any package that delivers a Perl module must use at run time the same Perl interpreter that was used at build time.

To ensure this, follow the procedure below:

Procedure

  • Include versioned MODULE_COMPAT Requires in RPM SPEC file for any package that delivers a Perl module:

    Requires:  perl(:MODULE_COMPAT_%(eval `perl -V:version`; echo $version))