Méthode de création des paquets
Créer et exécuter des recettes
On se base idéalement sur la recette générique de 0 pour créer rapidement des paquets. La recette semble compliquée, mais elle ne l'est pas vraiment. Elle inclut seulement toutes les commandes que l'on appelerait sur la ligne de commande, sans prendre de raccourci et sans appeler d'outil externe non standard, à l'exception bien sûr de l'empaqueteur, spackpkg
, en fin de recette.
La recette équivaut en fait à empaqueter un logiciel manuellement sur le terminal, ou via un petit script, en le compilant et en l'installant à un emplacement spécifique puis à le passer à spack
pour en faire un paquet pour 0. Voici un exemple pour geany-0.19.1
, sachant qu'on dispose déjà de l'archive des sources placée au même endroit que le script suivant :
#!/bin/sh tar xvf geany-0.19.1.tar.bz2 # Décompacter les sources cd geany-0.19.1 # Se placer dans le répertoire fraîchement créé ./configure --prefix=/usr # Configuration standard des sources make # Compilation fakeroot make install DESTDIR=/tmp/geany # Installation de geany dans '/tmp/geany' /sbin/spackpkg . geany-0.16.1-x86_64-1.spack # Empaquetage de tout '/tmp/geany'
Les recettes sont exécutables (et censées l'être). On empaquète un logiciel en appelant la recette ainsi :
./unlogiciel.recette
On peut se garder un « log », ou journal de la compilation, en redirigeant les sorties grâce à tee
; on a ainsi les messages à l'écran et un journal qui se génère dans un fichier journal. Par exemple :
./unlogiciel.recette 2>&1 | tee /usr/local/logs/unlogiciel.log
On peut également ajouter les messages concernant l'exécution de bash
pour avoir des messages plus précis sur chaque action de l'interpréteur :
bash -ex unlogiciel.recette 2>&1 | tee /usr/local/logs/unlogiciel.log
La doc
On intègre la documentation de chaque paquet dans /usr/doc/paquet-version
. Les fichiers vides sont intégrés quand même dans le paquet pour rester le plus fidèle à son organisation originelle.
- Relever la doc de base à la racine des sources. Le plus souvent, on retrouve les fichiers :
AUTHORS ChangeLog* COPYING* INSTALL* NEWS README* TODO
On retrouve souvent aussi :
ABOUT-NLS BUGS Changelog changelog CHANGES HACKING LICENSE* MAINTAINERS PORTING THANKS readme* Readme* *.txt
La recette générique contient un maximum de noms génériques :
DOC="AUTHORS BUGS ?hange?og* *CHANGES* *COPYING* HACKING *INSTALL* *LEGAL* *LICENSE* \ MAINTAINERS *NEWS* *README* THANKS TODO *license* *readme*"
La compilation
Ensuite :
./configure --help
…pour consulter les options spécifiques au paquet.
On tente ensuite le ./configure && make
en 32 bits et en 64 bits, la variable $CC étant positionnée, d'abord avec CC=“gcc -m32”
puis avec CC=“gcc -m64
:
En 32 bits :
CC="gcc -m32" CXX="g++ -m32" \ PKG_CONFIG_PATH="/usr/lib/pkgconfig" \ LDFLAGS="-L/usr/lib -L/lib" \ ./configure \ --prefix=/usr \ --sysconfdir=/etc \ --localstatedir=/var \ --libdir=/usr/lib \ --mandir=/usr/man \ --infodir=/usr/info \ --docdir=/usr/doc/paquet-version \ --build=i686-0-linux-gnu make
En 64 bits :
CC="gcc -m64" CXX="g++ -m64" \ PKG_CONFIG_PATH="/usr/lib64/pkgconfig" \ LDFLAGS="-L/usr/lib64 -L/lib64" \ ./configure \ --prefix=/usr \ --sysconfdir=/etc \ --localstatedir=/var \ --libdir=/usr/lib64 \ --mandir=/usr/man \ --infodir=/usr/info \ --docdir=/usr/doc/paquet-version \ --build=x86_64-0-linux-gnu make
Il faut aussi détecter quel paramètre s'occupe d'isoler le paquet du système pour le construire dans un répertoire dédié. C'est généralement le paramètre DESTDIR
qui revient, mains une lecture
du 'Makefile' ou autre permet de savoir si le paramètre ne serait pas plutôt INSTALL_ROOT
, PREFIX
ou autre :
make install DESTDIR=/tmp/quelque/part make install install_prefix=/tmp/quelque/part make install INSTALL_ROOT=/tmp/quelque/part etc.
La recette generique.recette
permet de s'affranchir des nombreux paramètres à ajouter afin de ne pas mélanger ou écraser les bibliothèques. Personnellement, je passe toujours par la recette générique, je vois si les compilations passent et j'ajuste ensuite.
32 et/ou 64 bits ?
Les bibliothèques 32 bits vont dans /usr/lib
(ou /lib
le cas échéant) et les libs 64 bits dans /usr/lib64
(ou /lib64
le cas échéant).
N.B.: On peut tout à fait avoir des fichiers compilés 64 bits dans /lib
comme c'est le cas pour certains paquets. Le tout est de bien séparer les architectures lorsque les 2 sont présentes. Par exemple, on trouve les modules du noyau, pourtant compilés en 64 bits, sous /lib/modules
. Cela ne pose aucun problème et est tout à fait propre car on ne trouve aucun module compilé en 32 bits ailleurs sur le système.
Comment savoir si l'on doit compiler en 32 et en 64 bits ou seulement en 64 bits ? C'est simple, il suffit de voir si le paquet fournit des fichiers spécifiques à une seule architecture, la plupart du temps des bibliothèques ou des fichiers .pc
(fichiers pour pkgconfig
), parfois des binaires.
Rien ne nous oblige à compiler en 32 bits, évidemment.
La post-installation
Si des bibliothèques ou fichiers .pc
sont présents, un ldconfig
doit être exécuté dans la post-installation pour mettre à jour l'éditeur de liens ld
.
On vérifie ensuite si des fichiers de configuration sont présents dans le paquet résultant (dans /etc
ou en cherchant des fichiers .conf
par exemple) et ajoute une procédure de renommage en .new
pour, lors d'une mise à niveau du paquet, ne pas écraser une ancienne configuration existante sur le système. Le script de post-installation s'occupe alors de savoir si ce .new
doit rester ou pas selon la présence d'un ancien fichier de config'. C'est la fonction config()
qu'on voit dans la recette générique. À noter que cette fonction écrase l'ancien fichier s'ils sont rigoureusement identiques (donc non modifiés par l'utilisateur entre-temps) :
# On installe le doinst.sh : cat > ${PKG}/install/doinst.sh << "EOF" #!/bin/env bash if [ -x sbin/ldconfig ]; then sbin/ldconfig -r . fi config() { NEW="$1" OLD="$(dirname $NEW)/$(basename $NEW .new)" if [ ! -r $OLD ]; then mv $NEW $OLD elif [ "$(diff -abBEiw $OLD $NEW)" = "" ]; then mv $NEW $OLD fi } config etc/ficher.conf.new EOF
Ce sera à l'utilisateur de vérifier s'il veut migrer vers le fichier en .new
ou pas.
Le multilib
Parfois, on doit conserver certains binaires liés en dur ou spécifiques à une architecture. C'est le cas par exemple des programmes *-config
. On isole ces binaires en les renommant en *-config-32
et *-config-64
, puis on crée un faux binaire *-config
qui n'est en fait qu'un simple lien symbolique vers le programme enveloppe : /usr/bin/multiarch_wrapper
, lequel se charge de savoir quelles libs charger si le programme appelé doit se finir en -32
ou -64
, selon l'architecture actuelle. On positionne cette architecture via l'affectation de variable USE_ARCH={32,64}
, qu'on peut voir dans la recette générique ci-dessous à chaque compilation.
Le même cas peut s'appliquer à des fichiers en-têtes .h
, voire à des bibliothèques partagées.
Certains comportements obligent en outre à créer des répertoires isolés : c'est le cas des programmes créant du « cache » ou des index spécifiques à une architecture. Par exemple, pango
possède 2 répertoires de configuration, /etc/pango-32
et /etc/pango-64
car à chaque démarrage, pango est appelé à créer son cache et ses index dans les 2 architectures :
pango-querymodules-32 > /etc/pango-32/pango.modules pango-querymodules-64 > /etc/pango-64/pango.modules
En temps normal, le simple appel à pango-querymodules
passerait par le programme enveloppe puis exécuterait en fait pango-querymodules-64
.
Restrictions
Concernant la doc : les répertoires /usr/share/doc
et /usr/share/man
ne doivent tout simplement PAS exister dans le paquet. En
revanche,on tolère les répertoires comme par exemple /usr/share/vim/doc
, et plus généralement tant que les paquets ont leur propre arborescence. On prendra aussi soin de créer un lien sous /usr/doc/paquet-version
vers /usr/share/gtk-doc/paquet
le cas échéant.
Les variables dans les recettes
On quitte automatiquement si une erreur se produit avec set -e
et on définit la masque par défaut de création des fichiers à 022 (droits en 644) :
set -e umask 022 CWD=$(pwd)
Vien ensuite la variable PKGCAT, qui permet de ranger le paquet dans la catégorie ad-hoc. Si la catégorie n'existe pas, elle sera créée à l'empaquetage par Spack
:
PKGCAT=opt
Puis on définit les variables pour le nom des sources du paquet, la version, l'extension de l'archive, le nom du paquet qui peut défférencier des sources et le compteur de compilations :
NAMESRC=${NAMESRC:-} # nom des sources, par exemple : Python VERSION=${VERSION:-} # version : 2.6 EXT=${EXT:-tar.} # extension de l'archive des sources : tar.bz2, zip, bin, tgz, etc. NAMETGZ=${NAMETGZ:-} # nom du paquet résultant : python (sans majuscule) BUILD=${BUILD:-} # compteur de compilations : 1
On travaille dans /tmp
, les paquets sont créés dans /usr/local/paquets
. Comme on compile pour 2 architectures différentes, on les définit dans TARGET32
et TARGET64
pour les spécifier successivement dans le paramètre 'build=' des 2 './configure' que nous lancerons. Typiquement, on lance les compilations en invoquant le compilateur gcc
avec le drapeau -m32
puis -m64
.
On positionne également l'emplacement de pkg-config selon l'architecture avec PKG_CONFIG32
et PKG_CONFIG64
:
TMP=${TMP:-/tmp} OUT=${OUT:-/usr/local/paquets} ARCH=${ARCH:-x86_64} TARGET32=${TARGET32:-i686-0-linux-gnu} TARGET64=${TARGET64:-$ARCH-0-linux-gnu} BUILD32=${BUILD32:--m32} BUILD64=${BUILD64:--m64} PKG_CONFIG_PATH32=${PKG_CONFIG_PATH32:-/usr/lib/pkgconfig} PKG_CONFIG_PATH64=${PKG_CONFIG_PATH64:-/usr/lib64/pkgconfig}
On spécifie ensuite l'emplacement des sources sur le Net dans WGET
, la documentation non prise en charge par l'installateur à inclure dans le paquet dans DOC
puis la description du paquet en une ligne dans SLACKDESC
, sans dépasser la règle « handy-ruler » :
WGET=${WGET:-http://ftp.gnu.org/gnu/$NAMESRC/$NAMESRC-$VERSION.$EXT} DOC="AUTHORS BUGS ?hange?og* *CHANGES* *COPYING* HACKING *INSTALL* *LEGAL* *LICENSE* \ MAINTAINERS *NEWS* *README* THANKS TODO *license* *readme*" ########## |-----handy-ruler------------------------------------------------------| SLACKDESC="Quelque chose de court qui ne doit pas dépasser la longueur de la règle." ########################################
La variable USE_ARCH
qu'on remarquera ci-dessous sert à un programme enveloppe, nommé « multiarch_wrapper
», à sélectionner les bonnes bibliothèques et les bons binaires à utiliser, certains en 32 bits différant du 64 bits. Par exemple, xft-config
pointe sur le programme enveloppe afin de sélectionner les bons binaires (xft-config-32
ou xft-config-64
). Les bibliothèques se trouvent dans /usr/lib
pour le 32 bits (et /lib
, bien qu'il puisse aussi contenir du 64 bits, les modules noyau par exemple) et /usr/lib64
(et /lib64
) pour le 64 bits.
La suite de la recette est commentée :
Recette générique
#!/usr/bin/env bash # Voyez le fichier LICENCES pour connaître la licence de ce script. set -e umask 022 CWD=$(pwd) PKGCAT= NAMESRC=${NAMESRC:-} VERSION=${VERSION:-} EXT=${EXT:-tar.} NAMETGZ=${NAMETGZ:-} BUILD=${BUILD:-} TMP=${TMP:-/tmp} OUT=${OUT:-/usr/local/paquets/$PKGCAT} ARCH=${ARCH:-x86_64} TARGET32=${TARGET32:-i686-0-linux-gnu} TARGET64=${TARGET64:-$ARCH-0-linux-gnu} BUILD32=${BUILD32:--m32} BUILD64=${BUILD64:--m64} PKG_CONFIG_PATH32=${PKG_CONFIG_PATH32:-/usr/lib/pkgconfig} PKG_CONFIG_PATH64=${PKG_CONFIG_PATH64:-/usr/lib64/pkgconfig} WGET=${WGET:-http://ftp.gnu.org/gnu/$NAMESRC/$NAMESRC-$VERSION.$EXT} DOC="AUTHORS BUGS ?hange?og* *CHANGES* *COPYING* HACKING *INSTALL* *LEGAL* *LICENSE* \ MAINTAINERS *NEWS* *README* THANKS TODO *license* *readme*" ########## |-----handy-ruler------------------------------------------------------| SLACKDESC="" ######################################## SLACKDESCCHARS=`echo ${SLACKDESC} | wc -m` if [ $(echo "${SLACKDESCCHARS} -1" | bc) -ge 80 ]; then echo "La description est trop longue (80 caractères max.) !" exit 1 fi # On télécharge les sources : if [ ! -r ${NAMESRC}-${VERSION}.$EXT ]; then wget -vc $WGET -O ${NAMESRC}-${VERSION}.$EXT.part mv ${NAMESRC}-${VERSION}.$EXT{.part,} fi # On les vérifie : tar ft ${NAMESRC}-${VERSION}.$EXT 1> /dev/null 2> /dev/null # On crée le répertoire d'accueil : PKG=$TMP/build/${NAMETGZ} mkdir -p ${PKG} # On déballe et on se place dans les sources : NAME=$(tar ft $CWD/${NAMESRC}-${VERSION}.$EXT | head -n 1 | awk -F/ '{ print $1 }') cd $TMP rm -rf ${NAME} echo "Extraction en cours..." tar xf $CWD/${NAMESRC}-${VERSION}.$EXT cd ${NAME} # On vérifie les permissions des sources : find . \ \( -perm 777 -o -perm 775 -o -perm 711 -o -perm 555 -o -perm 511 \) -exec chmod 755 {} \; -o \ \( -perm 666 -o -perm 664 -o -perm 600 -o -perm 444 -o -perm 440 -o -perm 400 \) -exec chmod 644 {} \; # On positionne USE_ARCH : export USE_ARCH=32 # Compilation pour 32 bits : SLKCFLAGS="-O2 -march=i686 -pipe" LIBDIRSUFFIX="" CC="gcc ${BUILD32}" CXX="g++ ${BUILD32}" \ CFLAGS="${SLKCFLAGS}" CXXFLAGS="${SLKCFLAGS}" \ PKG_CONFIG_PATH="${PKG_CONFIG_PATH32}" \ LDFLAGS="-L/usr/lib${LIBDIRSUFFIX} -L/lib${LIBDIRSUFFIX}" \ ./configure \ --prefix=/usr \ --sysconfdir=/etc \ --localstatedir=/var \ --libdir=/usr/lib${LIBDIRSUFFIX} \ --mandir=/usr/man \ --infodir=/usr/info \ --docdir=/usr/doc/${NAMETGZ}-${VERSION} \ --build=${TARGET32} make -j3 || make fakeroot make install DESTDIR=${PKG} # On neutralise USE_ARCH : unset USE_ARCH # On re-déballe et on se re-place dans les sources : cd $TMP rm -rf ${NAME} echo "Extraction en cours..." tar xf $CWD/${NAMESRC}-${VERSION}.$EXT cd ${NAME} # On re-vérifie les permissions des sources : find . \ \( -perm 777 -o -perm 775 -o -perm 711 -o -perm 555 -o -perm 511 \) -exec chmod 755 {} \; -o \ \( -perm 666 -o -perm 664 -o -perm 600 -o -perm 444 -o -perm 440 -o -perm 400 \) -exec chmod 644 {} \; # On positionne USE_ARCH : export USE_ARCH=64 # Compilation pour 64 bits : SLKCFLAGS="-O2 -fPIC -pipe" LIBDIRSUFFIX="64" CC="gcc ${BUILD64}" CXX="g++ ${BUILD64}" \ CFLAGS="${SLKCFLAGS}" CXXFLAGS="${SLKCFLAGS}" \ PKG_CONFIG_PATH="${PKG_CONFIG_PATH64}" \ LDFLAGS="-L/usr/lib${LIBDIRSUFFIX} -L/lib${LIBDIRSUFFIX}" \ ./configure \ --prefix=/usr \ --sysconfdir=/etc \ --localstatedir=/var \ --libdir=/usr/lib${LIBDIRSUFFIX} \ --mandir=/usr/man \ --infodir=/usr/info \ --docdir=/usr/doc/${NAMETGZ}-${VERSION} \ --build=${TARGET64} make -j3 || make fakeroot make install DESTDIR=${PKG} # On neutralise USE_ARCH : unset USE_ARCH # On compresse les manuels : if [ -d ${PKG}/usr/man ]; then find ${PKG}/usr/man -type f -name "*.*" -exec gzip -9 {} \; for manpage in $(find ${PKG}/usr/man -type l) ; do ln -s $(readlink $manpage).gz ${manpage}.gz rm -f ${manpage} done fi # On compresse les pages info : if [ -d ${PKG}/usr/info ]; then rm -f ${PKG}/usr/info/dir gzip -9 ${PKG}/usr/info/*.info* fi # Installation de la documentation : mkdir -p ${PKG}/usr/doc/${NAMETGZ}-${VERSION} cp -a ${DOC} ${PKG}/usr/doc/${NAMETGZ}-${VERSION} 2>/dev/null || true # On installe le slack-desc : mkdir -p ${PKG}/install echo "${NAMETGZ}: ${NAMETGZ} (${SLACKDESC})" > ${PKG}/install/slack-desc # On installe le doinst.sh : cat > ${PKG}/install/doinst.sh << "EOF" #!/usr/bin/env bash if [ -x sbin/ldconfig ]; then sbin/ldconfig -r . fi config() { NEW="$1" OLD="$(dirname $NEW)/$(basename $NEW .new)" if [ ! -r $OLD ]; then mv $NEW $OLD elif [ "$(diff -abBEiw $OLD $NEW)" = "" ]; then mv $NEW $OLD fi } if [ -x usr/bin/update-desktop-database ]; then chroot . /usr/bin/update-desktop-database &> /dev/null fi if [ -x /usr/bin/update-mime-database ]; then chroot . /usr/bin/update-mime-database /usr/share/mime >/dev/null 2>&1 fi if [ -x usr/bin/gtk-update-icon-cache ]; then chroot . /usr/bin/gtk-update-icon-cache -f -t /usr/share/icons/hicolor 1>/dev/null 2>&1 fi EOF # On "strippe" tout ce qu'on trouve : find ${PKG} -type f | xargs file | grep "LSB executable" | cut -f 1 -d : | xargs strip --strip-unneeded 2> /dev/null || true find ${PKG} -type f | xargs file | grep "shared object" | cut -f 1 -d : | xargs strip --strip-unneeded 2> /dev/null || true find ${PKG} -type f | xargs file | grep "current ar archive" | cut -f 1 -d : | xargs strip -g 2> /dev/null || true # On vérifie enfin les droits de la doc, souvent problématiques : find ${PKG}/usr/{doc,man,info} -type d -exec chmod 755 {} \; 2> /dev/null || true find ${PKG}/usr/{doc,man,info} -type f -exec chmod 644 {} \; 2> /dev/null || true # Empaquetage ! cd ${PKG} mkdir -p $OUT PACKAGING=" chown root:root . -R /sbin/spackpkg . $OUT/${NAMETGZ}-${VERSION}-${ARCH}-$BUILD rm -rf ${PKG} if [ ! \"X${NAME}\" = \"X\" ]; then if [ -d $TMP/${NAME} ]; then rm -rf $TMP/${NAME} fi fi" if [ "$(which fakeroot 2> /dev/null)" ]; then echo "${PACKAGING}" | fakeroot else su -c "${PACKAGING}" fi exit 0