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
- You have created a GNU Privacy Guard (GPG) key as described in Creating a GPG key.
5.1.1. Creating a GPG key
Use the following procedure to create a GNU Privacy Guard (GPG) key required for signing packages.
Procedure
Generate a GPG key pair:
# gpg --gen-key
Check the generated key pair:
# gpg --list-keys
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.
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.
NoteYou 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. Theopts
string between parentheses is passed togetopt(3)
forargc/argv
processing at the beginning of a macro invocation.
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 aspec
file. The body of a%define
macro is expanded when used. -
%global
has global scope. It applies to an entirespec
file. The body of a%global
macro is expanded at definition time.
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 forbefore
. This option expands specific sources before entering the working directory. -
The
-a
option stands forafter
. This option expands those sources after entering. Their arguments are source numbers from thespec
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.
Macro | Definition |
---|---|
%license |
The |
%doc |
The |
%dir |
The |
%config(noreplace) |
The |
5.2.4. Displaying the built-in macros
Red Hat Enterprise Linux provides multiple built-in RPM macros.
Procedure
To display all built-in RPM macros, run:
rpm --showrc
NoteThe output is quite sizeable. To narrow the result, use the command above with the
grep
command.To find information about the RPMs macros for your system’s version of RPM, run:
rpm -ql rpm
NoteRPM 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 dnf
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.
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 dnf
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.
Directive | Definition |
---|---|
| Scriptlet that is executed just before installing or removing any package. |
| Scriptlet that is executed just before installing the package on the target system. |
| Scriptlet that is executed just after the package was installed on the target system. |
| Scriptlet that is executed just before uninstalling the package from the target system. |
| Scriptlet that is executed just after the package was uninstalled from the target system. |
| 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.
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
-
Open the
pello.spec
file. Find the following line:
install -m 0644 %{name}.py* %{buildroot}/usr/lib/%{name}/
Under the above line, insert:
%post -p /usr/bin/python3 print("This is {} code".format("python"))
- Build your package as described in Building RPMs.
Install your package:
# dnf install /home/<username>/rpmbuild/RPMS/noarch/pello-0.1.2-1.el8.noarch.rpm
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
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
You can install Python packages on your system either from the upstream PyPI repository using the pip
installer, or using the DNF package manager. DNF uses the RPM package format, which offers more downstream control over the software.
The packaging format of native Python packages is defined by Python Packaging Authority (PyPA) Specifications. Most Python projects use the distutils
or setuptools
utilities for packaging, and defined package information in the setup.py
file. However, possibilities of creating native Python packages have evolved over time. For more information about emerging packaging standards, see pyproject-rpm-macros.
This chapter describes how to package a Python project that uses setup.py
into an RPM package. This approach provides the following advantages compared to native Python packages:
-
Dependencies on Python and non-Python packages are possible and strictly enforced by the
DNF
package manager. - You can cryptographically sign the packages. With cryptographic signing, you can verify, integrate, and test content of RPM packages with the rest of the operating system.
- You can execute tests during the build process.
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.
A name of any RPM package of a Python library must always include the python3-
, python3.11-
, or python3.12-
prefix.
Other specifics are shown in the following SPEC file example for the python3*-pello
package. For description of such specifics, see the notes below the example.
An example spec file for the pello program written in Python
%global python3_pkgversion 3.11 1 Name: python-pello 2 Version: 1.0.2 Release: 1%{?dist} Summary: Example Python library License: MIT URL: https://github.com/fedora-python/Pello Source: %{url}/archive/v%{version}/Pello-%{version}.tar.gz BuildArch: noarch BuildRequires: python%{python3_pkgversion}-devel 3 # Build dependencies needed to be specified manually BuildRequires: python%{python3_pkgversion}-setuptools # Test dependencies needed to be specified manually # Also runtime dependencies need to be BuildRequired manually to run tests during build BuildRequires: python%{python3_pkgversion}-pytest >= 3 %global _description %{expand: Pello is an example package with an executable that prints Hello World! on the command line.} %description %_description %package -n python%{python3_pkgversion}-pello 4 Summary: %{summary} %description -n python%{python3_pkgversion}-pello %_description %prep %autosetup -p1 -n Pello-%{version} %build # The macro only supported projects with setup.py %py3_build 5 %install # The macro only supported projects with setup.py %py3_install %check 6 %{pytest} # Note that there is no %%files section for the unversioned python module %files -n python%{python3_pkgversion}-pello %doc README.md %license LICENSE.txt %{_bindir}/pello_greeting # The library files needed to be listed manually %{python3_sitelib}/pello/ # The metadata files needed to be listed manually %{python3_sitelib}/Pello-*.egg-info/
- 1
- By defining the
python3_pkgversion
macro, you set which Python version this package will be built for. To build for the default Python version 3.9, either set the macro to its default value3
or remove the line entirely. - 2
- When packaging a Python project into RPM, always add the
python-
prefix to the original name of the project. The original name here ispello
and, therefore, the name of the Source RPM (SRPM) ispython-pello
. - 3
- The
BuildRequires
directive specifies what packages are required to build and test this package. InBuildRequires
, always include items providing tools necessary for building Python packages:python3-devel
(orpython3.11-devel
orpython3.12-devel
) and the relevant projects needed by the specific software that you package, for example,python3-setuptools
(orpython3.11-setuptools
orpython3.12-setuptools
) or the runtime and testing dependencies needed to run the tests in the%check
section. - 4
- When choosing a name for the binary RPM (the package that users will be able to install), add a versioned Python prefix. Use the
python3-
prefix for the default Python 3.9, thepython3.11-
prefix for Python 3.11, or thepython3.12-
prefix for Python 3.12. You can use the%{python3_pkgversion}
macro, which evaluates to3
for the default Python version 3.9 unless you set it to an explicit version, for example,3.11
(see footnote 1). - 5
- The
%py3_build
and%py3_install
macros run thesetup.py build
andsetup.py install
commands, respectively, with additional arguments to specify installation locations, the interpreter to use, and other details. - 6
- The
%check
section should run the tests of the packaged project. The exact command depends on the project itself, but it is possible to use the%pytest
macro to run thepytest
command in an RPM-friendly way.
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. You can redefine which Python 3 version is used in these macros by defining the python3_pkgversion
macro on top of your SPEC file (see Section 5.5.1, “SPEC file description for a Python package”). If you define the python3_pkgversion
macro, the values of the macros described in the following table will reflect the specified Python 3 version.
Macro | Normal Definition | Description |
---|---|---|
%{python3_pkgversion} | 3 |
The Python version that is used by all other macros. Can be redefined to |
%{python3} | /usr/bin/python3 | The Python 3 interpreter |
%{python3_version} | 3.9 | The major.minor version of the Python 3 interpreter |
%{python3_sitelib} | /usr/lib/python3.9/site-packages | The location where pure-Python modules are installed |
%{python3_sitearch} | /usr/lib64/python3.9/site-packages | The location where modules containing architecture-specific extension modules are installed |
%py3_build |
Runs the | |
%py3_install |
Runs the | |
%{py3_shebang_flags} | s |
The default set of flags for the Python interpreter directives macro, |
%py3_shebang_fix |
Changes Python interpreter directives to |
Additional resources
5.5.3. Using automatically generated dependencies for Python RPMs
The following procedure describes how to use automatically generated dependencies when packaging a Python project as an RPM.
Prerequisites
- A SPEC file for the RPM exists. For more information, see SPEC file description for a Python package.
Procedure
Make sure that one of the following directories containing upstream-provided metadata is included in the resulting RPM:
-
.dist-info
.egg-info
The RPM build process automatically generates virtual
pythonX.Ydist
provides from these directories, for example:python3.9dist(pello)
The Python dependency generator then reads the upstream metadata and generates runtime requirements for each RPM package using the generated
pythonX.Ydist
virtual provides. For example, a generated requirements tag might look as follows:Requires: python3.9dist(requests)
-
- Inspect the generated requires.
To remove some of the generated requires, use one of the following approaches:
-
Modify the upstream-provided metadata in the
%prep
section of the SPEC file. - Use automatic filtering of dependencies described in the upstream documentation.
-
Modify the upstream-provided metadata in the
-
To disable the automatic dependency generator, include the
%{?python_disable_dependency_generator}
macro above the main package’s%description
declaration.
Additional resources
5.6. Handling interpreter directives in Python scripts
In Red Hat Enterprise Linux 9, 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.9 #!/usr/bin/python3.11 #!/usr/bin/python3.12
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
Use the following procedure to modify interpreter directives in Python scripts that cause 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:
Use the following macro in the
%prep
section of your SPEC file:# %py3_shebang_fix SCRIPTNAME …
SCRIPTNAME can be any file, directory, or a list of files and directories.
As a result, all listed files and all
.py
files in listed directories will have their interpreter directives modified to point to%{python3}
. Existing flags from the original interpreter directive will be preserved and additional flags defined in the%{py3_shebang_flags}
macro will be added. You can redefine the%{py3_shebang_flags}
macro in your SPEC file to change the flags that will be added.Apply the
pathfix.py
script from thepython3-devel
package:# pathfix.py -pn -i %{python3} PATH …
You can specify multiple paths. 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 the command above 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, you can use the
pathfix.py
script outside the RPM build process, too. When runningpathfix.py
outside an RPM build, replace%{python3}
from the preceding example with a path for the interpreter directive, such as/usr/bin/python3
or/usr/bin/python3.11
.
Additional resources
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.
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
orProvides
, 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.
Macro name | Extended path | Usage |
---|---|---|
%{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:
Directive | RubyGems specifics |
---|---|
%prep |
RPM can directly unpack gem archives, so you can run the |
%build |
This directive includes commands or series of commands for building the software into machine code. The
The
The |
%install |
The installation is performed into the |
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 convert RubyGems packages to RPMspec
files -
Edit
gem2rpm
templates
5.7.3.4.1. Installing gem2rpm
The following procedure describes how to install the gem2rpm
utility.
Procedure
-
To install
gem2rpm
from RubyGems.org, run:
$ 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 convert RubyGems packages to RPM spec files
The following procedure describes how to use the gem2rpm
utility to convert 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.
Variable | Explanation |
---|---|
package |
The |
spec |
The |
config |
The |
runtime_dependencies |
The |
development_dependencies |
The |
tests |
The |
files |
The |
main_files |
The |
doc_files |
The |
format |
The |
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
Save the default template:
$ gem2rpm -T > rubygem-<gem_name>.spec.template
- Edit the template as needed.
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)
NoteApply 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 RPMspec
file:For example, to limit a package to Perl version 5.30 and higher, use:
BuildRequires: perl(:VERSION) >= 5.30
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 RPMspec
file for any package that delivers a Perl module:Requires: perl(:MODULE_COMPAT_%(eval `perl -V:version`; echo $version))