I got to work on a fun project yesterday, I needed to build a script that could generate a Debian package from a python module.
Yes, that does sound easy enough… Except that it’s not!
First of all, how does one build a .deb package? There’s lots of documentation on that, probably way too much, even. The main idea is that you will have to create a debian folder and add a few files in there that will define your package and how to build it, and then by running debuild, it will run a somewhat typical ./configure; make clean; make; make install
Issues
One of the problems with that is that you generally don’t have nor want to write a Makefile for your Python project… I mean, what would it even do?
The other issue I had was that Debian doesn’t want us to install just eggs through a deb package, which is understandable, you don’t want to install a package of a package.
Let’s get started!
I will assume you’re working on a project named ninja, and that it has this structure:
ninja/
core/
ninjalib.py
something.py
ninja.py
readme.txt
Now, the first things you’ll need to do is to create (directly in the ninja folder) the Makefile and the debian folder, which will contain at least the following files:
Makefile
debian/
changelog
control
dirs
docs
postinst
rules
Each of these files needs to follow a strict format, and contains very specific information:
Makefile
Since python doesn’t require us to actually build anything, we’re going to use the Makefile only to put our files in place.
Example
LIBDIR = $(DESTDIR)/usr/share/pyshared/ninja
BINDIR = $(DESTDIR)/usr/bin
clean:
rm -f *.py[co] */*.py[co]
install:
mkdir -p $(LIBDIR)
mkdir -p $(BINDIR)
cp -r core $(LIBDIR)/
cp ninja.py $(BINDIR)/ninja
uninstall:
rm -rf $(LIBDIR)
rm -f $(BINDIR)/ninja
A few things to note here:
$(DESTDIR) is an environment variable, defined in your rules file, which represents the root of the build environment (think chroot).
clean removes the dirty python bytecode.
install should put in place all required code, and uninstall should remove it.
Makefiles allow only tabs, be careful.
changelog
This file should be your project’s changelog, but it must comply with the debian format, see example:
ninja (0.1) unstable; urgency=low
* Initial version
-- Cyril Ninja <ninja@...> Wed, 20 Jan 2010 18:47:11 -0500
The entire file should consist of these blocks, but be careful:
(0.1) is the version number for the package you’re building. debuild will always use the top-most version number.
- You should have as many lines starting with
* as there are changes in this version.
- It is an actual tab before the
*, no spaces are allowed.
- The last line starts with a space, and there are 2 spaces between the email and the date.
control
In this file, you have to define what your packages actually are, and what they depend on (also what other packages they suggest, if you wish to do that).
Let’s see an example:
Source: ninja
Section: python
Priority: extra
Maintainer: Cyril Ninja
Build-Depends: debhelper (>= 7), python-support
Standards-Version: 3.8.0
Homepage: http://ninja
Package: ninja
Architecture: all
Depends: python (>= 2.6), python-ninja-foundation
Description: This is powerful ninja stuff
This example defines two packages: a source one, and a binary one. This is pretty much straightforward, so I’ll leave it like that.
dirs
In the Makefile earlier, we defined a couple of directories (LIBDIR, BINDIR) where our code will be installed, and this file should contain the directory names we want to include in our package.
For this project, it should look like this:
usr/bin/
usr/share/pyshared/ninja
docs
You should define in this file all documentation that should be included in the package.
Let’s have a look at the awesome ninja docs file:
readme.txt
It will be installed in /usr/share/doc/ninja
postinst
The postinst file is used to run commands when the package is installed / upgraded / removed. In our case, we only need to add a symlink so that python can find our module.
Example file
#!/bin/sh
set -e
case "$1" in
configure)
ln -s /usr/share/pyshared/ninja /usr/lib/python2.6/dist-packages/
;;
abort-upgrade|abort-remove|abort-deconfigure)
rm -f /usr/lib/python2.6/dist-packages/ninja
;;
*)
echo "postinst called with unknown argument \`$1'" >&2
exit 1
;;
esac
exit 0
Feel free to add any command you wish in that file.
rules
If you’re anything like me, you’re going to hate that file… Fortunately for us, there is virtually nothing to change from the example below:
#!/usr/bin/make -f
# -*- makefile -*-
configure: configure-stamp
configure-stamp:
dh_testdir
touch configure-stamp
build: build-stamp
build-stamp: configure-stamp
dh_testdir
$(MAKE)
touch $@
clean:
dh_testdir
dh_testroot
rm -f build-stamp configure-stamp
dh_clean
install: build
dh_testdir
dh_testroot
dh_prep
dh_installdirs
$(MAKE) DESTDIR=$(CURDIR)/debian/ninja install
binary-arch: build install
# emptyness
binary-indep: build install
dh_testdir
dh_testroot
dh_installchangelogs
dh_installdocs
dh_installexamples
dh_pysupport
dh_link
dh_fixperms
dh_installdeb
dh_shlibdeps
dh_gencontrol
dh_md5sums
dh_builddeb
binary: binary-indep
.PHONY: build clean binary-indep binary install configure
- Simply replace ninja with your project name, and you should be all set.
- Same here: tabs only.
Ready?
Now that everything seems to be in place, it’s time to build our magnificent Debian package!
From your ninja folder, run
debuild
At some point, if you do have a gpg key mathing the author email address you defined, it should ask for your password twice, to sign both packages.
When it stops, look in the parent folder, you should see
ninja_0.1_all.deb
ninja_0.1.orig.tar.gz
ninja_0.1.dsc
After that, I hope you know what you have to do