Creating Python Rpm

How to create a rpm for python application

This is a mini demo structure output by tree command, color_print is the package name and directory

.
├── color_print
│   ├── color_print.py
│   └── __init__.py
├── __init__.py
└── setup.py

Here is an example setup.py for demo

from setuptools import setup

setup(name='color_print',
version='0.1',
description='Color String',
url='http://github/xxxx/color_print/',
author='Joe Bob',
author_email='joe.bob@gmail.com',
license='MIT',
packages=['color_print'],
zip_safe=False)

There is no need to change directory, run this one command to build rpms

python setup.py bdist_rpm

Here is the output, it is that easy:

-bash-4.1$ find . -name "*.spec"
./build/bdist.linux-x86_64/rpm/SPECS/color_print.spec
-bash-4.1$ find . -name "*.rpm"
./dist/color_print-0.1-1.noarch.rpm
./dist/color_print-0.1-1.src.rpm

In reality, you will definitely need to modify the spec files manually. and run

rpmbuild -ba ./build/bdist.linux-x86_64/rpm/SPECS/color_print.spec

Creating Python RPM

  1. This command has to be typed wherever your setup.py is located.
  2. It packages everything that would show up in a bdist tarball.
  3. Err... sort of. While it works, the package it creates is not of very high quality. It's better to use sdist_rpm, then unpack the resulting SRPM and then apply your distro's Python packaging guidelines to the generated spec file.
  4. Get it to work via bdist first. That way any issues that crop up will be more manageable.

Python 3.5 create .rpm with pyinstaller generated executable

First of all, forget about bdist_rpm. It's for a distutils/setuptools project, so you would need a setup.py script that invokes pyinstaller under the hood to bundle the executable, somehow redefines the install_scripts command to be able to package binary executables and also handles the packaging of the systemd unit files. Instead, write a spec file which is the instruction manual for rpm to build and install your package.

setup

This is the example project to play with.

so-51640995
├── bacon.service
├── bacon.spec
├── bacon.timer
└── spam.py

spam.py

No magic here - prints eggs once called. Will be bundled via pyinstaller to a binary named bacon. I didn't call the project spam to avoid ambiguity, because pyinstaller also creates a file with .spec extension, so that running it does not overwrite the rpm spec file.

#!/usr/bin/env python3

def eggs():
print('eggs!')

if __name__ == '__main__':
eggs()

bacon.service

Simple service calling the binary bacon.

[Unit]
Description=Bacon emitting eggs

[Service]
ExecStart=/usr/bin/bacon
Restart=always

bacon.timer

Will call bacon every ten seconds.

[Unit]
Description=Timer for bacon to emit eggs from time to time

[Timer]
OnUnitInactiveSec=10s
OnBootSec=10s
Unit=bacon.service

[Install]
WantedBy=timers.target

bacon.spec

The instruction for the package. In %build section, we bundle spam.py, then install the bundled executable dist/spam to /usr/bin/bacon along with the systemd unit files.

Name: bacon
Version: 1
Release: 1
Summary: bacon that shouts 'eggs!' from time to time
License: MIT
Requires: systemd

%description
bacon that shouts 'eggs!' from time to time

%build
pyinstaller --onefile %{_sourcedir}/spam.py

%install
mkdir -p %{buildroot}%{_bindir}
mkdir -p %{buildroot}%{_unitdir}
install -m 755 dist/spam %{buildroot}%{_bindir}/bacon
install -m 755 %{_sourcedir}/bacon.service %{buildroot}%{_unitdir}/bacon.service
install -m 755 %{_sourcedir}/bacon.timer %{buildroot}%{_unitdir}/bacon.timer

%files
%{_bindir}/bacon
%{_unitdir}/bacon.service
%{_unitdir}/bacon.timer

build the package

There are lots of tutorials out there that explain building rpm packages in-depth, for example Fedora Packaging Guidelines, so just listing the minimal sequence of commands here:

$ # install the bare minimum of required packages
$ sudo dnf install rpm-build rpm-devel rpmdevtools
$ # first-time setup of build dirs
$ rpmdev-setuptree
$ # copy the source files
$ cp * $HOME/rpmbuild/SOURCES/
$ # invoke the build
$ rpmbuild -ba bacon.spec

test the package

$ sudo rpm -ivp $HOME/rpmbuild/RPMS/x86_64/bacon-1-1.x86_64.rpm

Edit: as mentioned in the comments, use -U in favor of -i. Quote from the rpm mans:

The general form of an rpm upgrade command is

 rpm {-U|--upgrade} [install-options] PACKAGE_FILE ...

This upgrades or installs the package currently installed to a newer version. This is the same as install, except all other version(s) of the package are removed after the new package is installed.

So use

$ sudo rpm -Uvp $HOME/rpmbuild/RPMS/x86_64/bacon-1-1.x86_64.rpm

for test installation.

Now bacon should be available from command line:

$ bacon
eggs!

Start the timer:

$ sudo systemctl start bacon.timer
$ systemctl status bacon.timer
● bacon.timer - Timer for bacon to emit eggs from time to time
Loaded: loaded (/usr/lib/systemd/system/bacon.timer; disabled; vendor preset: disabled)
Active: active (waiting) since Tue 2018-08-07 15:36:28 CEST; 29s ago
Trigger: Tue 2018-08-07 15:36:58 CEST; 979ms left

Check the logs:

$ sudo journalctl -u bacon
-- Logs begin at Mon 2017-07-03 12:49:51 CEST, end at Tue 2018-08-07 15:37:02 CEST. --
Aug 07 15:36:28 XXX systemd[1]: Started Bacon emitting eggs.
Aug 07 15:36:28 XXX bacon[128222]: eggs!
Aug 07 15:36:28 XXX systemd[1]: bacon.service: Service hold-off time over, scheduling restart.
Aug 07 15:36:28 XXX systemd[1]: Stopped Bacon emitting eggs.
Aug 07 15:36:28 XXX systemd[1]: Started Bacon emitting eggs.
Aug 07 15:36:28 XXX bacon[128224]: eggs!
Aug 07 15:36:28 XXX systemd[1]: bacon.service: Service hold-off time over, scheduling restart.
Aug 07 15:36:28 XXX systemd[1]: Stopped Bacon emitting eggs.
Aug 07 15:36:28 XXX systemd[1]: Started Bacon emitting eggs.
Aug 07 15:36:29 XXX bacon[128226]: eggs!
...

Once verified things work, stop the timer and uninstall bacon:

$ sudo systemctl stop bacon.timer
$ sudo rpm -e bacon
$ sudo systemctl daemon-reload
$ sudo systemctl reset-failed

How to use setuptools to create rpm packages for linux

How to build RPM package using bdist directly from setup.py http://jeromebelleman.gitlab.io/posts/devops/setuppy/
Note that this method is easy and can produce just simply RPM packages. And for example, you cannot put requires (or build requires) in metadata, you have to remember to put them on the command line all the times.

I would say that bdist is suitable just for initial work. If you want to ship and support it then creating SPEC file is a must.

One more example - AFAIK you cannot specify %post or %pre scriptlets using bdist and setup.py.

Here is an example of python SPEC file: https://fedoraproject.org/wiki/Packaging:Python#Example_common_spec_file

How to create a RPM which install python dependencies?

bdist_rpm lacks of a lot of functionality and IMO is not very well maintained. E.g. pyp2rpm is much better for converting existing PyPI modules. But your module does not seem to be on PyPI, so you need to specify it to bdist_rpm manually because it cannot retrieve this information from setup.py.

Run:

python setup.py bdist_rpm --requires python-flask

This will produce an rpm file which requires the python-flask package. For more recent RHEL/Fedora it would be python3-flask.



Related Topics



Leave a reply



Submit