Linux embarqué avec Yocto (étape II.3)


Christophe BLAESS – Juillet 2019

II.3 – Personnalisation des recettes

Nous avons vu comment ajouter des applications dont les recettes sont livrées avec Poky ou référencées sur Open Embedded Layers Index. Supposons que nous devions adapter de manière plus ou moins importante le contenu d’une de ces recettes. Comment procéder sans toucher aux fichiers originaux ? Nous allons le voir dans cette séquence…

Adaptation d’une recette de Poky

Il y a plusieurs manières d’adapter un élément d’une image, qui dépendent du type de modification à effectuer. Dans la plupart des cas, on va commencer par rechercher dans l’arborescence de Poky le répertoire où se trouve la recette, et examiner les fichiers présents pour comprendre le mécanisme de construction. Prenons par exemple l’application psplash qui affiche une image lors du boot du système.

[build-qemu]$ cd  ../poky/
[poky]$ find . -name psplash
./meta/recipes-core/psplash
./meta-poky/recipes-core/psplash
[poky]$

Surprise, deux répertoires sont consacrés à cette application. Examinons leurs contenus.

[poky]$ ls  meta/recipes-core/psplash/
files psplash_git.bb
[poky]$ ls meta/recipes-core/psplash/files/
psplash-init psplash-poky-img.h
[poky]$

Le répertoire de base contient un fichier recette au format .bb qui indique comment télécharger et compiler l’application et un sous-répertoire files. Ce dernier contient un script de lancement psplash-init et un fichier header .h qui contient l’image sous forme de tableau en C :

/* GdkPixbuf RGB C-Source image dump 1-byte-run-length-encoded */
define POKY_IMG_ROWSTRIDE (1920)
define POKY_IMG_WIDTH (640)
define POKY_IMG_HEIGHT (480)
define POKY_IMG_BYTES_PER_PIXEL (3) /* 3:RGB, 4:RGBA */
define POKY_IMG_RLE_PIXEL_DATA ((uint8*) \
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377" \
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377" \
[...]
"\374\361\376\377\374\377\377\377\377\377\377\377\377\377\377\377\377" \
"\223\377\377\377\377\376\377\374\361\376\377\374\377\377\377\377\377" \
[...]
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377" \
"\377\377\377\377\232\377\377\377")

Ce charabia apparent est bien la représentation d’une image graphique pixel par pixel. Voyons l’autre répertoire intitulé pspslash:

[poky]$ ls  meta-poky/recipes-core/psplash/
files psplash_git.bbappend

Il contient un fichier .bbappend avec le même nom que la recette .bb du répertoire précédent. Il s’agit d’une extension de recette, qui est prise en compte après la recette principale et peut donc venir modifier son contenu avant de l’interpréter.

Il est important de bien comprendre que bitbake charge d’abord en mémoire toutes les recettes et extensions en analysant leurs contenus (Parsing recipes) avant d’organiser le travail (Initialising tasks) puis de réaliser les opérations nécessaires (Executing RunQueue Tasks). Il est donc possible pour une extension .bbappend de surcharger le contenu précédent d’une recette .bb et de modifier son comportement

Voyons le contenu de cette extension :

[poky]$ cat  meta-poky/recipes-core/psplash/psplash_git.bbappend 
FILESEXTRAPATHS_prepend_poky := "${THISDIR}/files:"

Il s’agit d’un contenu que l’on retrouve très souvent dans les fichiers .bbappend avec de légères variations. Nous l’avons vu dans la séquence précédente, contrairement aux langages de programmation habituels, il y a une véritable interprétation de la partie gauche d’une affectation. Le caractère souligné “_” sert à préfixer un opérateur qui précise ou limite la portée de cette affectation. La partie gauche signifie ici “Lors de la compilation d’une image Poky” (_poky) “ajouter au début” (_prepend) de la variable FILESEXTRAPATHS.

L’affectation “:=” indique que l’interprétation de la partie droite doit se faire dès la lecture du fichier. Avec une affectation “=“, elle serait différée au moment de l’analyse de toutes les variables lues. Ceci nous garantit que l’on prend en compte immédiatement le contenu de la variable THISDIR.

La partie droite de l’affectation permet de préciser le sous-dossier files/ qui est juste à côté de la recette. On notera qu’il est suivi d’un caractère deux-points “:“.

La variable FILESEXTRAPATHS contient la liste des chemins dans lesquels on recherche les fichiers (hors code source) nécessaire pour la réalisation d’une recette. Les chemins de la liste sont séparés par des deux-points “:” et ils sont parcourus dans l’ordre de la liste.

En ajoutant le répertoire files/ accompagnant cette extension de recette au début de la liste, on s’assure que les fichiers qu’il contient auront précédence sur ceux de la recette initiale.

Voyons ce que contient ce répertoire :

 [poky]$ ls  meta-poky/recipes-core/psplash/files/
psplash-poky-img.h
[poky]$

Le fichier présent dans l’arborescence meta-poky/ indiqué vient donc remplacer celui de l’arborescence meta/. Ceci permet d’afficher une image personnalisée au démarrage. Nous pouvons réaliser le même type de modification pour afficher notre propre splashscreen. Créons un répertoire de travail.

[build-qemu]$ mkdir  -p  ../meta-my-own-layer/recipes-core/psplash/files

[build-qemu]$ cd ../meta-my-own-layer/recipes-core/psplash/files/

Après avoir consulté la documentation de psplash, je crée avec Gimp une image de dimension 640×400 pixels que j’exporte au format PNG. J’ai fait plusieurs essais pour obtenir la dimension qui convient bien (et qui est légèrement différente de l’originale). Vous pouvez télécharger l’image dans le lien ci-dessous.

[files]$ ls
my-own-splashscreen.png my-own-splashscreen.xcf

J’utilise l’outil gdk-pixbuf-csource (fourni sur ma machine par le package libgdk-pixbuf2.0-dev) pour lui demander de convertir mon image PNG en fichier header pour le langage C.

[files]$ gdk-pixbuf-csource  --macros  my-own-splashscreen.png  > psplash-poky-img.h

Il est nécessaire de modifier quelques éléments de l’image, que l’on peut automatiser avec les lignes sed suivantes :

[files]$ sed  -i  -e  "s/MY_PIXBUF/POKY_IMG/g"  psplash-poky-img.h
[files]$ sed -i -e "s/guint8/uint8/g" psplash-poky-img.h

Il nous faut créer le fichier d’extension, de la même manière que celui de meta-poky.

[files]$ cd  ..
[psplash]$ nano psplash_git.bbappend

Le fichier ne contient que la ligne indiquant que le contenu du répertoire files/ doit être plus prioritaire lors de la rechercher d’un fichier.

FILESEXTRAPATHS_prepend := "${THISDIR}/files:"

Nous pouvons alors regénérer et tester notre image.

[psplash]$ cd  ../../../build-qemu/
[build-qemu]$ bitbake my-own-image

Au démarrage, nous avons le plaisir de voir notre splashscreen personnalisé apparaître comme sur la figure II.3-1.

Figure II.3-1 – Splashscreen personnalisé.

Nous voyons ainsi comment remplacer un fichier complet proposé par une recette. Cela peut être utile dans de nombreux cas, principalement pour des éléments de configuration système (nous le retrouverons par exemple pour ajuster la configuration du réseau).

Néanmoins d’autres modifications peuvent être nécessaires, celles qui consistent à modifier une petite partie d’un fichier. Par exemple quelques lignes d’un fichier source avant compilation. Pour cela on préfère la méthode des patches.

Ajout d’un patch dans une recette

Nous allons commencer par produire et faire appliquer un patch pour un fichier directement fourni par une recette. Je vous propose par exemple de prendre la recette base-files qui se trouve dans le répertoire meta/recipes-core/ de poky. Comme son nom l’indique il s’agit d’une recette qui fournit directement des fichiers de base dans son sous-répertoire base-files/. L’un d’eux est profile, que l’on retrouve dans le répertoire etc/ de la cible. Il configure des variables d’environnement du shell comme PATH, PS1, TERM, EDITOR… avec des valeurs par défaut que l’utilisateur pourra surcharger s’il le souhaite.

La variable EDITOR justement, qui indique l’éditeur préféré de l’utilisateur, est initialisée à la valeur “vi“. Mais nous avons installé sur notre image nano, il serait dommage de ne pas en profiter. Créons donc un patch pour modifier cette ligne du fichier.

Pour commencer je crée une copie temporaire du répertoire base-files/ qui contient tous les fichiers. Je l’effacerai quand j’aurai fini de préparer le patch, je reste donc dans mon répertoire de travail initial.

[build-qemu]$ cp  -R  ../poky/meta/recipes-core/base-files/base-files  base-files-origin

J’en crée une deuxième copie où je ferai la modification.

[build-qemu]$ cp  -R  ../poky/meta/recipes-core/base-files/base-files  base-files-modified

Puis j’édite le fichier profile du répertoire base-files-modified pour remplacer la ligne

EDITOR="vi"            # needed for packages like cron, git-commit

par

EDITOR="nano"          # needed for packages like cron, git-commit

Je vérifie la création du patch.

[build-qemu]$ diff -ruN base-files-origin/ base-files-modified/
diff -ruN base-files-origin/profile base-files-modified/profile
--- base-files-origin/profile 2019-05-08 16:50:29.602695461 +0200
+++ base-files-modified/profile 2019-05-08 16:51:26.380075920 +0200+0200
@@ -2,7 +2,7 @@
# and Bourne compatible shells (bash(1), ksh(1), ash(1), …).
PATH="/usr/local/bin:/usr/bin:/bin"
-EDITOR="vi" # needed for packages like cron, git-commit
+EDITOR="nano" # needed for packages like cron, git-commit
[ "$TERM" ] || TERM="vt100" # Basic terminal capab. For screen etc.
# Add /sbin & co to $PATH for the root user

Le patch est correct, je l’enregistre en créant un répertoire base-files/ dans notre layer personnalisé, avant de supprimer les deux répertoires temporaires.

[build-qemu]$ mkdir  -p  ../meta-my-own-layer/recipes-core/base-files/files/
[build-qemu]$ diff -ruN base-files-origin/ base-files-modified/ > ../meta-my-own-layer/recipes-core/base-files/files/0001-prefer-nano-to-vi-in-profile.patch
[build-qemu]$ rm -rf base-files-origin/ base-files-modified/

Je crée ensuite une extension de recette dans le répertoire base-files de notre layer. Le nom du fichier est base-files_%.bbappend. Lorsque le fichier d’extension s’applique à une version particulière d’une recette (par exemple 3.0.14), on le nomme ainsi base-files_3.0.14.bbappend. Le caractère pourcent % à un rôle qui rapelle l’astérique * dans les motifs du shell. base-files_3.%.bbappend s’appliquerait à toutes les recettes de base-files dont le numéro majeur de version est 3. base-files_%.bbappend s’applique à toutes les versions.

[build-qemu]$ nano ../meta-my-own-layer/recipes-core/base-files/base-files_%.bbappend
FILESEXTRAPATHS_append := "${THISDIR}/files:"
SRC_URI += "file://0001-prefer-nano-to-vi-in-profile.patch"

Comme auparavant nous ajoutons le chemin du sous-répertoire contenant notre patch dans FILESEXTRAPATHS. Comme notre fichier est le seul de ce nom, il ne surcharge rien, on peut donc ajouter notre répertoire à la fin de la variable en utilisant le suffixe _append au lieu du _prepend utilisé jusqu’alors. Notons que l’utilisation de += ne fonctionnerait pas, car il utilise une espace comme caractère de séparation et non un deux-points.

On ajoute notre patch à la liste des fichiers appartenant à la recette. Son extension .patch suffira à ce qu’il soit pris en compte correctement. On peut relancer la génération de l’image.

[build-qemu]$ bitbake  my-own-image

Et nous testons notre résultat :

mybox login: root
Password: (linux)
root@mybox:~# echo $EDITOR
nano
root@mybox:~#

La variable d’environnement est bien initialisée avec la modification apportée par notre patch.

Patch sur un fichier source de package

Le patch que nous avons développé dans l’exemple précédent était produit et appliqué sur un fichier présent dans une recette. Nous allons à présent voir comment produire un patch qui doit s’appliquer sur le fichier source d’un package téléchargé et compilé par une recette.

Pour continuer avec les packages que nous avons déjà installé, je vous propose de travailler sur nano. La première chose à faire est de regarder le fichier de recette pour voir comment bitbake obtient les sources du package.

[build-qemu]$ cat ../meta-openembedded/meta-oe/recipes-support/nano/nano_4.1.bb 
DESCRIPTION = "GNU nano (Nano's ANOther editor, or \
Not ANOther editor) is an enhanced clone of the \
Pico text editor."
HOMEPAGE = "http://www.nano-editor.org/"
LICENSE = "GPLv3"
LIC_FILES_CHKSUM = "file://COPYING;md5=f27defe1e96c2e1ecd4e0c9be8967949"
SECTION = "console/utils"
DEPENDS = "ncurses file"
RDEPENDS_${PN} = "ncurses-terminfo-base"
PV_MAJOR = "${@d.getVar('PV').split('.')[0]}"
SRC_URI = "https://nano-editor.org/dist/v${PV_MAJOR}/nano-${PV}.tar.xz"
SRC_URI[md5sum] = "235eaf7d27db3c3679b2054361ff6181"
SRC_URI[sha256sum] = "86bde596a038d6fde619b49d785c0ebf0b3eaa7001a39dbe9316bd5392d221d0"
inherit autotools gettext pkgconfig
PACKAGECONFIG[tiny] = "--enable-tiny,"

Nous pouvons remarquer que la variable SRC_URI qui décrit la provenance des fichiers source indique l’URL suivante pour le téléchargement.

SRC_URI = "https://nano-editor.org/dist/v${PV_MAJOR}/nano-${PV}.tar.xz"

Deux variables sont mises à contribution dans ce chemin : PV et PV_MAJOR. La variable PV (Package Version) est omniprésente dans les recettes de Yocto, elle est automatiquement remplie avec le numéro de version du package concerné. Comment ce numéro est-il obtenu ? Simplement grâce au nom du fichier de recette. Il s’agit ici de nano_4.1.bb, donc PV est remplie avec la chaîne “4.1“. La variable PV_MAJOR est définie juste au-dessus de SRC_URI grâce à un petit morceau de script Python en-ligne qui extrait la première portion de PV précédant un point. Il s’agit donc de “4“. Autrement dit, l’URL devient : https://nano-editor.org/dist/v4/nano-4.1.tar.xz. Téléchargeons ce package…

[build-qemu]$ wget  https://nano-editor.org/dist/v4/nano-4.1.tar.xz
[...]
2019-05-08 16:55:14 (5,09 MB/s) - «nano-4.1.tar.xz» enregistré [1478088/1478088]

[build-qemu]$

Comme il s’agit d’une archive, et pas d’un dépôt git, je l’extrais puis je duplique l’arborescence pour avoir une copie de travail à modifier.

[build-qemu]$ tar  xf  nano-4.1.tar.xz 
[build-qemu]$ cp -R nano-4.1 nano-4.1-origin

Je vais ensuite modifier un fichier source, par exemple nano-4.1/src/nano.c en changeant simplement la ligne 2715 :

statusbar(_("Welcome to nano.  For basic help, type Ctrl+G."));

en

statusbar(_("Welcome to my patched version of nano."));

Puis je produis un fichier de patch que j’enregistre dans un répertoire créé spécialement dans notre layer.

[build-qemu]$ mkdir -p ../meta-my-own-layer/recipes-support/nano/files/
[build-qemu]$ diff -ruN nano-4.1-origin/ nano-4.1
diff -ruN nano-4.1-origin/src/nano.c nano-4.1/src/nano.c
--- nano-4.1-origin/src/nano.c 2019-05-08 16:56:38.036547228 +0200
+++ nano-4.1/src/nano.c 2019-05-08 16:57:58.643593095 +0200
@@ -2712,7 +2712,7 @@
#ifdef ENABLE_HELP
if (*openfile->filename == '\0' && openfile->totsize == 0 &&
openfile->next == openfile && !ISSET(NO_HELP))
-statusbar(_("Welcome to nano. For basic help, type Ctrl+G."));
+statusbar(_("Welcome to my patched version of nano."));
#endif
while (TRUE) {
[build-qemu]$ diff -ruN nano-4.1-origin/ nano-4.1 > ../meta-my-own-layer/recipes-support/nano/files/0001-display-a-modified-welcome-message.patch
[build-qemu]$ rm -rf nano-4.1*
[build-qemu]$

Je crée le fichier d’extension de recette comme nous l’avons fait pour base-files :

[build-qemu]$ nano ../meta-my-own-layer/recipes-support/nano/nano_4.1.bbappend

FILESEXTRAPATHS_append := "${THISDIR}/files"
SRC_URI += "file://0001-display-a-modified-welcome-message.patch"

Comme un patch s’applique sur une version bien particulière des sources, on préfère généralement indiquer ce numéro dans le nom du fichier .bbappend.

Après recompilation et démarrage de l’image, nous avons la confirmation de notre modification quand nous lançons nano et observons le message en bas de l’écran (voir figure II.3-2).

Figure II.3-2 – Modification du message de bienvenue de nano

Conclusion

Nous avons vu dans cette séquence comment modifier le comportement de recettes existantes sans les modifier, avec différentes approches selon le type de modification à apporter. Le principe des extensions grâce aux fichiers .bbappend est très puissant et permet de garantir la pérennité des modifications que l’on apporte même si les versions des packages d’origine évoluent.

Les deux premières parties de ce cours en ligne nous ont permis de voir comment créer une image de Linux embarqué pour une architecture cible de notre choix, et d’ajuster son contenu. Il est temps à présent de s’intéresser à l’ajout de notre propre code, ce qui sera l’objet de la troisième partie.