Python 3.5 create .rpm with pyinstaller generated executable
Asked Answered
S

1

11

I've got a build generated with a pyinstaller. I need to create .rpm package which will put the executable into the /usr/bin/ and create a systemd service which will run that executable.

I found this https://docs.python.org/3/distutils/builtdist.html and https://docs.python.org/2.0/dist/creating-rpms.html

However it doesn't give me a full picture.

  1. Is it possible to make it?

  2. What toolset do i need to use? (Basically, how to make it).

  3. If possible - sample code

Scientistic answered 1/8, 2018 at 19:30 Comment(0)
P
14

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
Passenger answered 7/8, 2018 at 13:57 Comment(2)
A very minor nitpick: rpm -U, not rpm -i, should always be suggested. The only known usage case for rpm -i is the linux kernel package. Using rpm -I can/will lead to multiply installed packages.Avrilavrit
@hoefling, how can I generate rpm for python3.7.3 itself..? https://mcmap.net/q/1017244/-python-3-7-rpm-creationKalle

© 2022 - 2024 — McMap. All rights reserved.