Save the ions!

Packaging python applications/modules for Debian

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 ;)

3 Responses

  1. Antonis says:

    There are only two things I want to say to you

    1) THANK YOU
    2) I LOVE YOU

    I’ve been trying to create a deb package out of a minimal python application I’ve written but have failed again and again until I found your tutorial!!

    Thank you, it really helped me!!

  2. cyril says:

    I love you too!

  3. Your guide really did save my life, thanks so much, I love you!

    Some suggestions:

    * Under “binary-indep:”, please add “dh_compress”. This option ensures that stuff life changelog.Debian and manpages are compressed. Otherwise you’ll have lintian (Debian package sanity tester) errors, even though your package will work without fault.
    * Please add a manpage in the project as well.
    * changelog does allow spaces before ‘*’

Leave a Reply