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