Building ARM Debian packages with pbuilder
The goal of this post is to help myself reproduce this pbuilder setup in the future. If you find it useful too, then that’s great.
Building ARM packages on older ARM devices like the Raspberry Pi 1 is slow, so I want to use a single amd64 machine to build Debian packages for all my target platforms:
Preferably, I want to build the packages for the current Debian stable, which at the time of writing is wheezy, so that they’ll work for as many users as possible.
Instead of managing a bunch of virtual machines by hand, we can use pbuilder. pbuilder creates chroots with debootstrap, keeps them updated with changes to the base system, and keeps the chroots clean of dependencies installed when you build packages within them.
There is an introduction to pbuilder and lots of information on various ways of using it in the Ubuntu wiki.
I’m doing the following on an amd64 system running Ubuntu 14.10.
I start out with a ~/.pbuilderrc
to help simplify the later commands:
#!/bin/sh
set -e
if [ "$OS" == "debian" ]; then
MIRRORSITE="http://ftp.no.debian.org/debian/"
COMPONENTS="main contrib non-free"
DEBOOTSTRAPOPTS=("${DEBOOTSTRAPOPTS[@]}"
"--keyring=/usr/share/keyrings/debian-archive-keyring.gpg")
: ${DIST:="wheezy"}
: ${ARCH:="amd64"}
if [ "$DIST" == "wheezy" ]; then
#EXTRAPACKAGES="$EXTRAPACKAGES debian-backports-keyring"
OTHERMIRROR="$OTHERMIRROR | deb $MIRRORSITE wheezy-backports $COMPONENTS"
fi
elif [ "$OS" == "raspbian" ]; then
MIRRORSITE="http://ftp.acc.umu.se/mirror/raspbian/raspbian/"
COMPONENTS="main contrib non-free"
DEBOOTSTRAPOPTS=("${DEBOOTSTRAPOPTS[@]}"
"--keyring=/usr/share/keyrings/raspbian-archive-keyring.gpg")
: ${DIST:="wheezy"}
: ${ARCH:="armhf"}
elif [ "$OS" == "ubuntu" ]; then
MIRRORSITE="http://no.archive.ubuntu.com/ubuntu/"
COMPONENTS="main restricted universe multiverse"
DEBOOTSTRAPOPTS=("${DEBOOTSTRAPOPTS[@]}"
"--keyring=/usr/share/keyrings/ubuntu-archive-keyring.gpg")
else
echo "Unknown OS: $OS"
exit 1
fi
if [ "$DIST" == "" ]; then
echo "DIST is not set"
exit 1
fi
if [ "$ARCH" == "" ]; then
echo "ARCH is not set"
exit 1
fi
NAME="$OS-$DIST-$ARCH"
if [ "$ARCH" == "armel" ] && [ "$(dpkg --print-architecture)" != "armel" ]; then
DEBOOTSTRAP="qemu-debootstrap"
fi
if [ "$ARCH" == "armhf" ] && [ "$(dpkg --print-architecture)" != "armhf" ]; then
DEBOOTSTRAP="qemu-debootstrap"
fi
DEBOOTSTRAPOPTS=("${DEBOOTSTRAPOPTS[@]}" "--arch=$ARCH")
BASETGZ="/var/cache/pbuilder/$NAME-base.tgz"
DISTRIBUTION="$DIST"
BUILDRESULT="/var/cache/pbuilder/$NAME/result/"
APTCACHE="/var/cache/pbuilder/$NAME/aptcache/"
BUILDPLACE="/var/cache/pbuilder/build"
HOOKDIR="/var/cache/pbuilder/hook.d/"
The .pbuilderrc
file cares about three environment variables:
OS
: Either debian
, raspbian
, or ubuntu
DIST
: For example wheezy
, jessie
, or trusty
ARCH
: For example amd64
, i386
, armel
, or armhf
Based on these three variables, the .pbuilderrc
script selects the proper APT mirror, distribution components, and keyring to verify the downloaded packages. If the architecture is armel
or armhf
, and we’re not currently on that architecture, it uses qemu-debootstrap
to bootstrap the chroot and copies a static qemu emulator for ARM into the chroot.
For more details on the qemu part, see the Ports page in the Ubuntu wiki.
Before running pbuilder, we have to install pbuilder and qemu-debootstrap:
sudo apt install pbuilder qemu-user-static
And we also need the keyrings. Ubuntu includes packages with the Ubuntu and Debian keyrings:
sudo apt install ubuntu-keyring debian-archive-keyring
While we have to download the Raspbian keyring package and install it by hand:
wget http://archive.raspbian.org/raspbian/pool/main/r/raspbian-archive-keyring/raspbian-archive-keyring_20120528.2_all.deb
sudo dpkg -i raspbian-archive-keyring_20120528.2_all.deb
You should now have all keyrings referenced from the .pbuilderrc
:
$ ls /usr/share/keyrings
...
/usr/share/keyrings/debian-archive-keyring.gpg
/usr/share/keyrings/raspbian-archive-keyring.gpg
/usr/share/keyrings/ubuntu-archive-keyring.gpg
...
$
Then we can bootstrap the five chroots. This takes a bit of time as it downloads all packages needed to bootstrap a fully functional OS. Five times.
sudo OS=debian DIST=wheezy ARCH=amd64 pbuilder --create
sudo OS=debian DIST=wheezy ARCH=i386 pbuilder --create
sudo OS=debian DIST=wheezy ARCH=armel pbuilder --create
sudo OS=raspbian DIST=wheezy ARCH=armhf pbuilder --create
sudo OS=debian DIST=wheezy ARCH=armhf pbuilder --create
This will create tarballs with the pristine chroots:
$ ls /var/cache/pbuilder
...
/var/cache/pbuilder/debian-wheezy-amd64-base.tgz
/var/cache/pbuilder/debian-wheezy-armel-base.tgz
/var/cache/pbuilder/debian-wheezy-armhf-base.tgz
/var/cache/pbuilder/debian-wheezy-i386-base.tgz
/var/cache/pbuilder/raspbian-wheezy-armhf-base.tgz
...
Now we’re ready to build packages. Basically, you just get the source of a Debian package, change into its directory, and run:
OS=raspbian DIST=wheezy ARCH=armhf pdebuild
If the pdebuild
command completes successfully, you’ll find the resulting Debian source and binary packages for armhf in e.g. /var/cache/pbuilder/raspbian-wheezy-armhf/result
. All without using the rather slow Raspberry Pi 1 I used to build packages with.
Since I build packages for Mopidy, to be hosted at apt.mopidy.com, they often depend on other packages hosted there, and not just packages from the official repos. Thus I need to add an extra APT repository to the chroots. As I already have five chroots, I don’t want to change each chroot manually with pbuilder --login --save-after-login
.
The solution is to add a hook script to be executed every time the chroot is being set up, before the Debian packaging begins, which adds apt.mopidy.com as an APT repo and runs apt-get update
.
Create the directory /var/cache/pbuilder/hook.d
and make a file /var/cache/pbuilder/hook.d/D10add-apt.mopidy.com
:
#!/bin/sh
cat << EOF | apt-key add -
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1.4.10 (GNU/Linux)
mQINBEzXBRYBEADGWsXEGa+WcoVMv6SevKPJQsFNbp/CbK6J8xJ0wVdf5LdfWF46
rNFMUa6DPwVOjp7wdY6mwTnW7OkpHLakP9DzaUXLGZ/9Bku7O+Aw+NDLC2W4IbdJ
... key data removed for brevity ...
0ax2VWX0487MYQcU8e2fEPOi3QkPc7zMr7wWDeB2JNBf2y/FnJxcbTLS6CEWr8mf
nXSGfezg+nzdC8eags3zCmQ5dlIKFLi7QautytYDA32vl09JcLl7mA8cxST8LyGw
rNNPwkCckaxcrWfSUso6saWj5j8y+OVn
=kcoa
-----END PGP PUBLIC KEY BLOCK-----
EOF
echo "deb http://apt.mopidy.com/ stable main contrib non-free" >> /etc/apt/sources.list
apt-get update
Remember to make the hook script executable, or else it will have no effect:
sudo chmod a+x /var/cache/pbuilder/hook.d/D10add-apt.mopidy.com
And that should be it. The hook script will now be executed before any package build and all packages from the APT repo will be available to pdebuild
.