Thursday 16 July 2020

Creating "universal" .deb packages

This is a way to create a "universal" .deb package suitable for installing on Debian, Ubuntu and similar distributions such as Linux Mint; and on any machine architecture  (64-bit, 32-bit, even Raspberry Pi).

The package actually installs the source files under /usr/src/ and performs the compilation and installation of the actual binary in the post-installation phase.   The package manager will make sure all dependencies are installed before running the post-install script.  As long as all dependencies are correctly specified  (remember, if you need libfoo to run something, you will also need libfoo-dev to build it)  then the overall effect will be exactly the same as if they had installed a pre-compiled binary package; but you only have to .deb-ify it once, and you won't be expecting random strangers to install unknown binaries on their systems.

We are going to create a package called "wibble", which will contain this simple program:

#include <stdio.h>
int main() {
    printf("Wibble\n");
    return 0;
};

All it does is display "Wibble", but the same principles obviously are applicable to anything more ambitious.


Building It

Open a terminal window, create a folder and navigate to it.

Our package is going to be called "wibble", version 1.0-0 and it will be suitable for all architectures.  So the canonical package name would be wibble_1.0-0_all.  Make a folder to contain the files that will go into the package, and a skeleton folder structure beneath this:

$ mkdir wibble_1.0-0_all
$ cd wibble_1.0-0_all
$ mkdir usr
$ mkdir usr/share
$ mkdir usr/share/doc
$ mkdir usr/share/doc/wibble
$ mkdir usr/src
$ mkdir usr/src/wibble
$ mkdir DEBIAN

Everything but the DEBIAN folder will be unpacked under the root directory.  The DEBIAN folder contains a control file, and some scripts which will be run by the package manager at various stages of the installation and removal processes.

Now we will create some files to go in those folders, as follows:

usr/share/doc/wibble/COPYING

NO RIGHTS RESERVED. THIS CODE IS DEDICATED TO THE PUBLIC DOMAIN.

YOU MAY USE IT, ABUSE IT, ENJOY IT, DESTROY IT, STUDY IT, SHARE IT AND
ADAPT IT WITHOUT RESTRICTION.

usr/share/doc/wibble/README.jm

This package is not quite as simple as it looks.

(/usr/share/doc/wibble/ is where a package's information files conventionally reside -- READMEs, changelogs and so forth, perhaps even an "examples" subfolder.)

usr/src/wibble/wibble.c

#include <stdio.h>
int main() {
    printf("Wibble\n");
    return 0;
};

DEBIAN/control

Package: wibble
Version: 1.0-1
Section: base
Priority: optional
Architecture: all
Depends: build-essential
Maintainer: Julie Montoya <bluerizlagirl@gmail.com>
Description: Wibble demo
 A simple demo that does not do very much, except prove it is possible
 to do what it does.


This file tells the package manager about the package.  Note the Depends: line. Use this to specify any dependencies  (and don't forget the -dev packages, since you are going to be building stuff).  The package build-essential is actually a metapackage; that is to say, it does not contain any files of its own except a documentation folder, but crucially it depends on a bunch of other packages, so you can force them all to install in one fell swoop.  Note also that continuation lines of the description begin with a space.

This is all well and good, but all this will do is unpack the source "tree"  (here, just one little file)  into a folder under /usr/src/ .  We still need to build the code.  Fortunately, the Debian package format allows for us to do some finishing-off after the installation is completed, and again just before removal commences, by means of scripts which, if they exist within the package, will automatically be run at certain stages.

DEBIAN/postinst

#!/bin/sh

##############################  POSTINST  script  ##############################

#  By the time this runs, dpkg will already have unpacked the Source Code

#  under /usr/src/ -- this is where we build and install it.
#
#  This may be as simple as  ./configure && make install  but probably isn't


OLD_FOLDER=$(pwd)

cd /usr/src/wibble/
gcc -owibble wibble.c
install -c wibble /usr/bin/

cd $OLD_FOLDER


exit


This script runs after all the files that make up the package have been installed to their proper locations, and all dependencies have been installed.  So now we just have to build our Source Code, and install the binary we have just created to its intended location.

With this simple bit of code, we don't need to use make.  But a more complex project probably will use a configure script and makefile.  You also will need to remember to pass appropriate options to configure to persuade it to use /usr/bin/ , /usr/lib/ , /etc/ and /usr/share/as opposed to /usr/local/wherever -- you are building a system package, and are allowed -- indeed, encouraged -- to step in those places.  The package manager has an idea of what it has done for itself and how to undo it.  We have to take care of anything the package manager does not know we have done.  For this, we need the following script:

DEBIAN/prerm

#!/bin/sh

################################  PRERM script  ################################

#  We need to remove any files we created during installation that were
#  not on the manifest, so folders to be removed are empty.
#
#  NB, this includes the actual executables we installed!
#  This may be as simple as  make uninstall && make clean  but probably isn't

if [ -e /usr/src/wibble/wibble ]
then
    rm -f /usr/src/wibble/wibble
fi

if [ -e /usr/bin/wibble ]
then
    rm -f /usr/bin/wibble
fi

exit


This is a script that runs before the files installed during installation are removed.  The package manager will only remove files that were on the original manifest, and will complain if a folder it wants to remove is not empty  (as might be the case if some sort of upgrade had been done outside of dpkg).  So this is where we can remove the files we created  (not forgetting the actual binary!)

With a real package with a Makefile, you probably will be able to run make uninstall to remove the binaries and make clean to get rid of all extraneous files.

Set permissions on the scripts:

$ chmod 775 DEBIAN/prerm DEBIAN/postinst

And build the package:

$ dpkg-deb -b wibble_1.0-0_all


If all goes well, you will end up with a .deb package!  You can inspect this with various GUI and command-line tools and see the individual files inside it, if you like.

Testing It

You can test the package by just running the DEBIAN/postinst script.  The final installation will fail for want of write permission, but if it gets that far then the build was successful.

The next test will be to install it with dpkg:

$ sudo dpkg -i wibble_1.0-0_all.deb

Now remove it, to make sure that works too:

$ sudo dpkg -r wibble

But what you have not tested yet is the dependencies.  To do this properly, you will need a pristine system with only a bare minimum of packages installed -- probably the second-best way to get one, if you can't just fire up a brand new virtual machine ;) , is just to boot up a live Ubuntu system from a USB drive.  This should let you install packages from an Internet repository using apt-get.  Or if you have a spare Raspberry Pi, you can even burn a fresh SD card image and use that  (which will also prove it really is packaged for all architectures).

If it works, well done!  Otherwise, make a note of any dependencies you missed, and add them to the DEBIAN/control file.  Your test environment need not be reinstalled from scratch, as long as you are sure you did not remove anything from the list of dependencies  (though if the test environment is just your main laptop booted from USB, that can't really be helped).


Note it's also entirely possible to install a package with no files at all except DEBIAN/control . You might think that is not much use, but you can specify a whole bunch of packages in Depends: which will be installed automatically.  You can use this to install the same software packages  (possibly even from a local repository)  on a bunch of machines together. 

No comments:

Post a Comment