Each time I need to package some software as .deb, I am not able to do it in one go. Because it is always a quite complex procedure that requires quite a lot of knowledge and context and you have to learn a lot of tools (million of debhelper tools, dpkg, debuild etc). It is designed to be comfortable for “large-scale” debian package maintainers who maintain a lot of packages for the Debian project, but is not very friendly for “indie” developers working on their own projects. And the process may vary a lot depending on what kind of software you need to package (what language, is it a library or shell tool or GUI etc). Also it strictly recommends that debian packaging files are stored separately from the software repo which might be somewhat inconvenient for small projects. We are going to include the debian
directory into the project.
So in this article we are going to build a very specific kind of package: binary package for CMake project. However we are going to cross-compile it for arm64 from amd64 machine.
The application is PixelPilot.
Preparation
Let’s clone the source code
git clone https://github.com/OpenIPC/PixelPilot_rk.git
The app repo is named using camel-case notation, so let’s assume that our DEB package going to be named pixelpilot-rk
.
Generate the template files
We can ofcourse create all the files by hand, however there are quite a lot of them and some have rather complex structure. So, let’s use the dh_make
tool from debhelper
package:
cd PixelPilot_rk
dh_make --packagename pixelpilot-rk_1.3.0 --single
This will generate the debian/
directory with a lot of files in it (most have .ex extension because they are examples and if you plan to use it, the .ex extension needs to be removed). Most of those files have names that follow a special convention and debhelper tools would look for those files and change the way the final package is built depending on the existance of various files and their contents. Some of the files are not generated and we’d need to create them from scratch.
- control – is the file that contains the metadata of the package: its short description, list of dependencies. When you type
apt show <package>
, this is more or less what you would see in this file. However onecontrol
file can contain definitions of several DEB packages, if you want to build several packages from the same repo. Make sure to add at least the description and the list of build- and runtime-dependencies. rules
– is a makefile that is going to be used to build your project and various its artifacts. By-default it is just a dummy makefile that forwards all its targets todebhelper
tools. For now you can leave it as is, but we’d need to make a few changes later.changelog
– is the list of changes between package versions. Normally it would contain the same info as what you have in the “releases” section on GitHub, but in a special format. Make sure to set the correct version number and write the sensible list of changes. While new entries can be added manually, it’s much easier to do it usingdch
/debchange
tool: eitherdch -i
to +1 the version ordch -v X.Y.Z
to generate an entry for a specific version.pixelpilot.1
andpixelpilot-rk.manpages
are the actual manual page and the listing of all man pages included in this package. The syntax of man pages is rather strange, so it’s better to use some tools to generate this file. The simplest one ishelp2man
tool that tries to generate a basic man page from the--help
output, but please don’t commit it as-is, add at least some information and examples! Or uselowdown
to convert markdown file to man page.- copyright, watch, source etc – are less important boilerplate
A few more files we’d need to add, because pixelpilot
needs to be started as a systemd service, those files name starts with package name “pixelpilot-rk” because when you’d want to build several packages from the same repo, they’d going to have different name and it prevents name clashes:
pixelpilot-rk.default
(however we’d name itpixelpilot-rk.pixelpilot.default
) – is the file that is going to be placed to/etc/default/pixelpilot
– it is the list of environment variables which can be used by systemd unit file and overwritten by the enduser. It looks like a “KEY=value” per-line with comments starting with#
.pixelpilot-rk.service
(we will name itpixelpilot-rk.pixelpilot.service
) – is the actual systemd service config. It is the standard systemd service configuration file.
We add .pixelpilot
in the middle of the name because we want user to use systemctl start pixelpilot
and not systemctl start pixelpilot-rk
just because it makes a bit more sense. But it’s also fine to leave them as just pixelpilot-rk.service
if you are fine with service name being the same as package name. You can also create a pixelpilot-rk@.service
if you need to create a service-template.
Since we use cmake
in this project and because we use non-default systemd service name, we need to make a few changes to rules
file:
%:
dh $@ --buildsystem=cmake
override_dh_auto_configure:
dh_auto_configure -- \
-DCMAKE_LIBRARY_PATH=$(DEB_HOST_MULTIARCH)
override_dh_installsystemd:
dh_installsystemd --restart-after-upgrade --name=pixelpilot
override_dh_installinit:
dh_installinit --name=pixelpilot
Note the --buildsystem=cmake
– it says debhelper that the project have to be build with cmake. And override_dh_install*
rules tell debhelper to look for pixelpilot-rk.pixelpilot.{service,default}
files.
Now you can commit the contents of debian/
directory, just don’t forget to remove all the unused .ex
files.
Building the package
building the package could be as simple as just typing dpkg-buildpackage -uc -us -b
. However it might be beneficial to build the project in a clean directory. Also, some tools expect that your work directory is named as <project name>_<version>
and they would put the final DEB package (and a few extra files) not into the current directory, but at one level up. So let’s create such a directory and export our source code there:
mkdir pixelpilot-rk_1.3.0
git archive master | tar -x -C pixelpilot-rk_1.3.0
If your project contains submodules, then git archive
won’t work, but you can use (there are probably ways to skip the tar stage):
git ls-files --recurse-submodules | tar -c -T- | tar -x -C pixelpilot-rk_1.3.0
You may actually want to also generate the “orig” archive along with sources directory:
ORIG_ARCHIVE=pixelpilot-rk_${PKG_VERSION}.orig.tar.gz
SRCDIR=pixelpilot-rk_${PKG_VERSION}
git ls-files --recurse-submodules | tar -caf $ORIG_ARCHIVE -T-
rm -rf $SRCDIR
mkdir $SRCDIR
tar -axf $ORIG_ARCHIVE -C $SRCDIR
Then step into the <name>_<version>
directory and start the build. Keep in mind that it assumes you have the <name>_<version>/debian/
directory! And worth noting that it is expected that CMakeLists.txt
should contain proper install
target that ideally installs not just binary, but also all the necessary assets like images / data-files / configs etc.
cd pixelpilot-rk_1.3.0
dpkg-buildpackage -uc -us -b
After it finishes, your DEB package is goint to be in the parent directory:
ls pixelpilot-rk*.deb
> pixelpilot-rk_1.3.0-1_arm64.deb pixelpilot-rk-dbgsym_1.3.0-1_arm64.deb
1st one is the actual software package and the dbgsym
contains information that is useful if one needs to, mainly, run gdb
on PixelPilot.
Cross-compiling to arm64 architecture
What if we are working on regular amd64
computer, but we need to build a package for arm
? Well, for C/C++ projects the easiest way is to either build on a separate ARM host or use qemu
emulator/virtual machine. There are likely a lot of ways to build in a container, here I’ll describe just one possibility.
DEBIAN_HOST=https://cloud.debian.org/images/cloud/bullseye
DEBIAN_RELEASE=latest
DEBIAN_SYSTEM=debian-11-generic-arm64.tar
wget -nv $(DEBIAN_HOST)/$(DEBIAN_RELEASE)/$(DEBIAN_SYSTEM).xz
tar -xf $(DEBIAN_SYSTEM).xz
This way we download the latest ARM64 Debian bullseye image (not .iso, but installed disk image). Now we mount the filesystem from this image:
mkdir -p $OUTPUT
LOOP=`sudo losetup -P --show -f disk.raw`
sudo mount ${LOOP}p1 $OUTPUT
sudo mkdir -p $OUTPUT/usr/src/PixelPilot_rk
sudo mount -o bind `pwd` $OUTPUT/usr/src/PixelPilot_rk
sudo rm $OUTPUT/etc/resolv.conf
echo nameserver 1.1.1.1 | sudo tee -a $OUTPUT/etc/resolv.conf
The way it’s done is a bit messy: first we mount the root partition (p1) of the image as $(OUTPUT)
directory but then we also just bind-mount the project directory to /usr/src
of the mounted image. It can be just copied there ofcourse. And we do a few hacks to make sure DNS works fine inside the container.
After that we need to create an entry-point script for the container that’s going to be executed and will build the package. It needs to perform all the steps from the “building the deb package” section above, but from within a container: install all the necessary packages, create the directories and call dpkg-buildpackage
. Since we mounted the current working directory to container, the final packages would appear in the current directory in the end.
There are definitely better ways to do the container part, I’m just not that well skilled in Docker / porman etc, so did it in the “manual” way.
Don’t forget to unmount what you mounted:
sudo umount $OUTPUT/usr/src/PixelPilot_rk
sudo umount $OUTPUT
sudo losetup --detach $LOOP