Petit traité empirique de l’empaquetage Debian
Vincent Bernat
Note
Ce guide est une mise à jour d’une version précédente. Si vous avez besoin de construire des paquets pour des distributions plus anciennes que Debian Stretch et Ubuntu Bionic, jetez un œil sur l’édition précédente.
Bien que la création de paquets Debian soit abondamment documentée, la plupart des tutoriaux ciblent les paquets respectueux de la charte Debian. De plus, leur création a longtemps eu la réputation d’être particulièrement difficile1 et beaucoup se sont tournés vers des outils moins contraignants2 tels que fpm ou CheckInstall.
Toutefois, la construction de paquets Debian en utilisant les outils officiels est plutôt simple en appliquant ces quelques concessions :
-
Aucun paquet source ne sera généré. Les paquets seront construits directement depuis une copie propre issue du système de versions.
-
Des dépendances supplémentaires peuvent être téléchargées pendant la construction. Empaqueter individuellement chaque dépendance est un travail ingrat, notamment avec certains environnements tels que Java, JavaScript et Go.
-
Les paquets produits peuvent combiner et inclure des dépendances tierces. Cela peut lever certaines objections liées à la sécurité et à la maintenance à long terme, mais c’est une concession courante dans certains écosystèmes tels que Java, JavaScript et Go.
Ceinture blanche#
Deux types de paquets coexistent dans l’archive Debian : les paquets sources et les paquets binaires. Un paquet source produit un ou plusieurs paquets binaires. Chaque paquet doit porter un nom.
Comme indiqué lors de l’introduction, aucun paquet source ne sera
construit. Nous allons travailler directement sur sa représentation
décompressée : une arborescence de fichiers incluant le répertoire
debian/
. Les exemples qui suivent utilisent une arborescence
composée uniquement du répertoire debian/
, mais ce dernier peut être
inclus dans n’importe quel project existant.
Comme point de départ, nous allons empaqueter memcached, un cache mémoire distribué. Il nous faut créer quatre fichiers :
debian/compat
,debian/changelog
,debian/control
etdebian/rules
.
Le premier contient uniquement 11
3 :
echo 11 > debian/compat
Le second contient ceci :
memcached (0.0-0) UNRELEASED; urgency=medium * Fake entry -- Happy Packager <happy@example.com> Tue, 19 Apr 2016 22:27:05 +0200
La seule information d’importance est le nom du paquet source,
memcached
, sur la première ligne. Toutes les autres informations
sont sans influence sur les paquets créés.
Le fichier de contrôle#
debian/control
décrit les méta-données à propos du paquet source et
des paquets binaires. Un bloc est dédié à chacun d’eux.
Source: memcached Maintainer: Vincent Bernat <bernat@debian.org> Package: memcached Architecture: any Description: high-performance memory object caching system
Le paquet source est memcached
. Il faut utiliser le même nom que
dans le fichier debian/changelog
.
Un seul paquet binaire est créé : memcached
. Par la suite, si vous
voyez memcached
, il s’agit du nom du paquet binaire. Le champ
Architecture
doit être soit any
, soit all
. Ce dernier est
utilisé exclusivement si tous les fichiers sont indépendants de
l’architecture matérielle. Dans le doute, il suffit de mettre any
.
Le champ Description
contient une courte description du paquet
binaire.
La recette#
Le dernier fichier à rédiger est debian/rules
. Il s’agit de la
recette du paquet. Nous avons besoin de télécharger memcached, le
construire et installer son arborescence dans debian/memcached/
:
#!/usr/bin/make -f DISTRIBUTION = $(shell sed -n "s/^VERSION_CODENAME=//p" /etc/os-release) VERSION = 1.6.6 PACKAGEVERSION = $(VERSION)-0~$(DISTRIBUTION)0 TARBALL = memcached-$(VERSION).tar.gz URL = http://www.memcached.org/files/$(TARBALL) %: dh $@ override_dh_auto_clean: override_dh_auto_test: override_dh_auto_build: override_dh_auto_install: wget -N --progress=dot:mega $(URL) tar --strip-components=1 -xf $(TARBALL) ./configure --prefix=/usr make make install DESTDIR=debian/memcached override_dh_gencontrol: dh_gencontrol -- -v$(PACKAGEVERSION)
Les cibles vides override_dh_auto_clean
, override_dh_auto_test
et
override_dh_auto_build
permettent de s’assurer que debhelper ne
fera rien de « magique ». La cible override_dh_gencontrol
permet de
spécifier la version4 sans avoir à tenir à jour
debian/changelog
. Cette recette est très similaire à ce qui aurait
été écrit pour fpm
:
DISTRIBUTION=$(sed -n "s/^VERSION_CODENAME=//p" /etc/os-release) VERSION=1.6.6 PACKAGEVERSION=${VERSION}-0~${DISTRIBUTION}0 TARBALL=memcached-${VERSION}.tar.gz URL=http://www.memcached.org/files/${TARBALL} wget -N --progress=dot:mega ${URL} tar --strip-components=1 -xf ${TARBALL} ./configure --prefix=/usr make make install DESTDIR=/tmp/installdir # Build the final package fpm -s dir -t deb \ -n memcached \ -v ${PACKAGEVERSION} \ -C /tmp/installdir \ --description "high-performance memory object caching system"
Vous pouvez lire le résultat final sur GitHub et
le construire avec la commande dpkg-buildpackage -us -uc -b
ou avec
GIT_PBUILDER_OPTIONS=--use-network=yes DIST=bionic git-pbuilder -us
-uc -b
si vous avez déjà mis en place un outil tel que pbuilder
.
Ceinture jaune#
À partir de là, il est possible d’inclure quelques améliorations. Aucune n’est essentielle mais le gain est suffisamment intéressant pour justifier l’effort.
Les dépendances sources#
Notre recette initiale ne fonctionne que si nous disposons déjà de
wget
et de libevent-dev
. Ces paquets ne sont pas présents sur tous
les systèmes. Il est assez aisé de spécifier ces dépendances en
ajoutant un champ Build-Depends
dans debian/control
:
Source: memcached Build-Depends: debhelper (>= 11), wget, ca-certificates, libevent-dev
Il faut toujours spécifier debhelper (>= 11)
car son utilisation est
centrale dans debian/rules
. Il n’y a pas besoin de dépendre de
make
ou d’un compilateur C car le paquet build-essential
est
considéré comme toujours présent et il les fournit indirectement.
dpkg-buildpackage
se plaindra si une des dépendances est manquante.
Pour installer sans peine ces paquets, par exemple depuis un
environnement d’intégration continue, vous pouvez utiliser la commande
suivante5 :
mk-build-deps \ -t 'apt-get -o Debug::pkgProblemResolver=yes --no-install-recommends -qqy' \ -i -r debian/control
Il est aussi intéressant de se pencher sur des outils tels que pbuilder, sbuild ou whalebuilder. Ils permettent de construire des paquets dans un environnement minimal et isolé.
Les dépendances binaires#
Si le paquet ainsi construit est installé sur une machine vierge,
memcached refusera de démarrer en raison de l’absence de
libevent. Il est possible d’exprimer cette dépendance en ajoutant un
champ Depends
dans le fichier debian/control
. De plus, dans le cas
des bibliothèques dynamiques, ces dépendances peuvent être générées
automatiquement en utilisant des variables de substitution :
Package: memcached Depends: ${misc:Depends}, ${shlibs:Depends}
Le paquet construit contiendra les informations suivantes :
$ dpkg -I ../memcached_1.6.6-0\~buster0_amd64.deb | grep Depends Depends: libc6 (>= 2.17), libevent-2.1-6 (>= 2.1.8-stable)
Intégration avec un système de démarrage#
La plupart des paquets fournissant un démon incluent une intégration avec le système de démarrage afin de démarrer le démon au boot ou de le redémarrer après une mise à jour. Bien que Debian assure la prise en charge de plusieurs systèmes de démarrage, je pense qu’il est préférable de se limiter à systemd.
Pour fournir un script pour systemd, il convient de placer son
contenu dans debian/memcached.service
. Par exemple :
[Unit] Description=memcached daemon After=network.target [Service] Type=forking Environment=PORT=11211 Environment=MAXCONN=1024 Environment=CACHESIZE=64 Environment=OPTIONS= ExecStart=/usr/bin/memcached -d -p $PORT -m $CACHESIZE -c $MAXCONN $OPTIONS Restart=on-failure User=_memcached DynamicUser=yes [Install] WantedBy=multi-user.target
La directive Type
est la plus importante. Nous avons utilisé
forking
car memcached est démarré à l’aide de l’option -d
. Cela
lui indique de se mettre en arrière-plan lorsqu’il est prêt à accepter
des requêtes. Les autres possibilités sont notify
pour un démon
spécifiquement prévu pour fonctionner avec systemd ou simple
pour
un démon ne passant pas en arrière-plan.
Si un utilisateur veut personnaliser l’une des directives
Environment
, il peut utiliser systemctl edit memcached.service
pour créer le fichier
/etc/systemd/system/memcached.service.d/override.conf
qui va
surcharger les définitions du script fourni par le paquet. systemctl
cat memcached.service
permet d’obtenir la définition finale du
service.
La directive DynamicUser
est particulièrement intéressante. Elle
indique à systemd de créer dynamiquement l’utilisateur
_memcached
6 qui sera utilisé pour faire tourner le démon.
Cela nous libère de la gestion d’un utilisateur système !
Le résultat final est disponible sur
GitHub et peut être testé avec la
commande dpkg-buildpackage -us -uc -b
.
Ceinture verte#
Il est possible d’exploiter certaines capacités de debhelper pour
réduire la taille du fichier debian/rules
et pour le rendre
plus déclaratif. Cette section est totalement optionnelle : vous
pouvez la sauter si besoin.
Banalités#
Il y a quatre étapes dans la construction d’un paquet Debian :
-
debian/rules clean
va nettoyer l’arborescence pour revenir dans son état initial. -
debian/rules build
doit construire le logiciel. Pour quelque chose de basé sur autoconf, comme memcached, il s’agit essentiellement d’exécuter./configure && make
. -
debian/rules install
doit installer l’arborescence de chaque paquet binaire dans le répertoire approprié. Pour des logiciels basés sur autoconf, il s’agit d’exécutermake install DESTDIR=debian/memcached
. -
debian/rules binary
doit empaqueter les différentes arborescences en paquets binaires.
Il ne faut pas écrire directement chacune de ces cibles. L’utilitaire
dh
, un composant de debhelper, va faire la majeure partie du
boulot. Le fichier debian/rules
minimaliste suivant suffit pour accomplir
cette tâche pour de nombreux paquets sources :
#!/usr/bin/make -f %: dh $@
Pour chacune des quatre cibles décrites ci-dessus, vous pouvez
exécuter dh --no-act
pour voir les utilitaires invoqués. Par
exemple :
$ dh build --no-act dh_testdir dh_update_autotools_config dh_auto_configure dh_auto_build dh_auto_test
Chacun de ces utilitaires dispose d’une page de manuel. Ceux
commençant par dh_auto
sont un peu « magiques ». Par exemple,
dh_auto_configure
va tenter de configurer automatiquement le
logiciel avant l’étape de construction. Selon les cas, il peut
invoquer ./configure
, cmake
ou Makefile.PL
.
Si un des utilitaires dh_
ne fait pas ce qu’il faut, il est possible
de le remplacer en déclarant une cible nommée de manière adéquate :
override_dh_auto_configure: ./configure --with-some-grog
Chaque utilitaire est également configurable via des options. Ainsi, il est possible de modifier leurs comportements en définissant la cible correspondante et en invoquant l’utilitaire manuellement :
override_dh_auto_configure: dh_auto_configure -- --with-some-grog
Ainsi, ./configure
sera appelé avec l’option --with-some-grog
mais
aussi avec des options par défaut telles que --prefix=/usr
. Il y a
une page de manuel pour chacun de ces outils.
Dans l’exemple initial de memcached, ces cibles « magiques » sont
surchargées. dh_auto_clean
, dh_auto_configure
et dh_auto_build
ont été neutralisées pour éviter tout comportement
inattendu. dh_auto_install
a été détournée pour exécuter l’intégralité
de la construction de l’arborescence cible. De plus, le comportement
de dh_gencontrol
a été modifié en lui fournissant le numéro de version
désiré plutôt que de le laisser regarder dans debian/changelog
.
Construction automatique#
memcached utilisant autoconf, dh
sait comment le construire :
./configure && make && make install
. Il est donc possible de laisser
dh
faire la majeure partie du boulot avec le fichier debian/rules
suivant :
#!/usr/bin/make -f DISTRIBUTION = $(shell sed -n "s/^VERSION_CODENAME=//p" /etc/os-release) VERSION = 1.6.6 PACKAGEVERSION = $(VERSION)-0~$(DISTRIBUTION)0 TARBALL = memcached-$(VERSION).tar.gz URL = http://www.memcached.org/files/$(TARBALL) %: dh $@ --with systemd override_dh_update_autotools_config: wget -N --progress=dot:mega $(URL) tar --strip-components=1 -xf $(TARBALL) override_dh_auto_test: # Don't run the whitespace test rm t/whitespace.t dh_auto_test override_dh_gencontrol: dh_gencontrol -- -v$(PACKAGEVERSION)
La cible dh_update_autotools_config
est détournée pour effectuer le
téléchargement et la mise en place de l’arborescence. Ni
dh_auto_configure
, ni dh_auto_build
ne sont modifiés. dh
appellera ./configure
avec les options appropriées puis make
.
dh_auto_test
doit exécuter la suite de tests de memcached.
Toutefois, un des tests échoue en raison d’un fichier dans le
répertoire debian/
. Nous supprimons ce test récalcitrant et
invoquons manuellement dh_auto_test
. dh_auto_install
n’est pas
surchargé et dh
exécutera alors une variante de make install
.
Afin de mieux apprécier la différence, la voici sous forme de rustine :
--- memcached-intermediate/debian/rules 2019-05-31 07:52:40.908868035 +0200 +++ memcached/debian/rules 2019-05-31 07:28:17.404380064 +0200 @@ -9,15 +9,14 @@ %: dh $@ -override_dh_auto_clean: -override_dh_auto_test: -override_dh_auto_build: -override_dh_auto_install: +override_dh_update_autotools_config: wget -N --progress=dot:mega $(URL) tar --strip-components=1 -xf $(TARBALL) - ./configure --prefix=/usr - make - make install DESTDIR=debian/memcached + +override_dh_auto_test: + # Don't run the whitespace test + rm t/whitespace.t + dh_auto_test override_dh_gencontrol: dh_gencontrol -- -v$(PACKAGEVERSION)
Vous avez le choix de laisser dh
faire une partie du travail ou
non. Il est généralement possible de partir d’un debian/rules
minimal et de surcharger uniquement quelques cibles.
Fichiers supplémentaires#
Bien que make install
a installé les fichiers essentiels pour
memcached, il est parfois nécessaire de copier quelques fichiers
supplémentaires dans le paquet binaire. Pour se faire, il est possible
d’utiliser cp
ou encore de déclarer les fichiers à copier :
- les fichiers listés dans
debian/memcached.docs
seront copiés dans/usr/share/doc/memcached
pardh_installdocs
, - les fichiers listés dans
debian/memcached.examples
seront copiés dans/usr/share/doc/memcached/examples
pardh_installexamples
, - les fichiers listés dans
debian/memcached.manpages
seront copiés dans le sous-répertoire approprié de/usr/share/man
pardh_installman
,
Voici un exemple pour debian/memcached.docs
:
doc/*.txt
Si vous avez besoin de copier des fichiers à un endroit arbitraire, il
est possible de lister ceux-ci ainsi que leur répertoire cible dans le
fichier debian/memcached.install
. dh_install
se chargera de la
copie. Par exemple :
scripts/memcached-tool usr/bin
L’utilisation de ces fichiers permet une description plus
déclarative de la recette. Il s’agit d’une histoire de goût et vous
pouvez tout à fait utiliser cp
à la place. Le résultat final est
visible sur GitHub.
Autres exemples#
Le dépôt Git comprend d’autres exemples. Ils suivent tous le même schéma et mettent en œuvre les techniques décrites dans les sections précédentes :
dh_update_autotools_config
est surchargée pour télécharger et mettre en place les sources,dh_gencontrol
est modifiée pour fixer la version du paquet.
Il y a notamment des exemples de démons en Java, Go, Python et Node.js. Le but de ces exemples est de démontrer que l’utilisation des outils Debian est relativement simple. Mission accomplie ?
Mise à jour (05.2019)
Ce guide a été mis à jour pour utiliser
override_dh_update_autotools_config
pour télécharger des fichiers à
la place de override_dh_auto_clean
. Cela permet une intégration plus
simple avec la plupart des outils de construction, tels que pbuilder
et sbuild
.
-
La mémoire collective est toujours marquée par la glorieuse époque précédant l’introduction de debhelper 7.0.50 (circa 2009). La création du fichier
debian/rules
était alors particulièrement laborieuse. Toutefois, de nos jours, le squelette est devenu minimal. ↩︎ -
La complexité n’est pas la seule raison de ce choix : les outils alternatifs proposent également la création de paquets RPM, ce que les outils Debian ne permettent pas. ↩︎
-
Ce niveau de compatibilité est disponible à partir de Debian 9 et d’Ubuntu Bionic. Il couvre donc les distributions modernes. Cependant, il souffre d’un problème empêchant le démarrage d’un service lorsqu’un paquet est installé, désinstallé puis réinstallé de nouveau. Le niveau 10 augmente sensiblement la complexité. Si possible, vous pouvez opter pour la version 12, disponible depuis Debian 10 et Ubuntu Focal. ↩︎
-
Il y a différentes façons de numéroter les versions d’un paquet. La façon proposée ici n’est pas plus mauvaise qu’une autre pour Ubuntu. Pour Debian, elle ne couvre pas les mises à jour entre deux versions de la distribution. De nos jours, il est cependant plutôt courant de réinstaller un système plutôt que de le mettre à jour. ↩︎
-
Les paquets
devscripts
etequivs
sont alors nécessaires. ↩︎ -
La charte Debian ne se prononce pas sur la convention à utiliser. Il est courant de préfixer le nom du démon avec un tiret bas (comme dans les BSD). Un autre usage courant est d’utiliser
Debian-
comme préfixe. Cette dernière méthode a l’inconvénient de produire un nom d’utilisateur trop long pour être visible dans les utilitaires commetop
etps
.Si la directive
User
n’est pas précisée, systemd utilise simplement le nom du service comme nom d’utilisateur. Si cela vous convient, vous pouvez alors omettre cette directive. ↩︎