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;debian/rules.
Le premier contient uniquement 113 :
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
_memcached6 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 cleanva nettoyer l’arborescence pour revenir dans son état initial. -
debian/rules builddoit construire le logiciel. Pour quelque chose de basé sur autoconf, comme memcached, il s’agit essentiellement d’exécuter./configure && make. -
debian/rules installdoit 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 binarydoit 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.docsseront copiés dans/usr/share/doc/memcachedpardh_installdocs; - les fichiers listés dans
debian/memcached.examplesseront copiés dans/usr/share/doc/memcached/examplespardh_installexamples; - les fichiers listés dans
debian/memcached.manpagesseront copiés dans le sous-répertoire approprié de/usr/share/manpardh_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_configest surchargée pour télécharger et mettre en place les sources ;dh_gencontrolest 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
devscriptsetequivssont 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 commetopetps.Si la directive
Usern’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. ↩︎