«« sommaire »»

II.3 – Personnalisation des recettes

Christophe BLAESS - juillet 2024

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. Notre première approche va consister à remplacer un fichier fourni par une recette.

Pour ce faire, 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.

Pour trouver l'emplacement d'une recette dont on connait le nom, on peut utiliser la sous-commande «find-recipe» de la commande «devtool» livrée dans Poky.

[build-qemu]$ devtool  find-recipe  psplash
NOTE: Starting bitbake server...
Loading cache: 100% |                                                                                                   | ETA:  --:--:--
Loaded 0 entries from dependency cache.
Parsing recipes: 100% |##################################################################################################| Time: 0:00:28
Parsing of 1874 .bb files complete (0 cached, 1874 parsed). 3212 targets, 126 skipped, 0 masked, 0 errors.

Summary: There was 1 WARNING message.
/home/Builds/Lab/layers/poky/meta/recipes-core/psplash/psplash_git.bb

[build-qemu]$ 

Examinons le contenu du répertoire de psplash :

[build-qemu]$ ls ../../layers/poky/meta/recipes-core/psplash/
files  psplash_git.bb

[build-qemu]$ ls ../../layers/poky/meta/recipes-core/psplash/files/
psplash-init  psplash-poky-img.png  psplash-poky-img.svg  psplash-start.service  psplash-systemd.service[build-qemu]$ 

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/. Dans ce dernier se trouvent un script de lancement psplash-init utilisé quand le système démarre à la manière System V, des fichiers de configuration servant quand le démarrage est géré par Systemd et un fichier graphique d'extension .png qui contient l’image (que nous allons remplacer).

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

Nous allons créer dans notre layer un répertoire «psplash» avec un chemin semblable à celui de la recette originale dans poky, et y créer un sous-répertoire «files/». L'option «-p» de mkdir permet de créer en une seule fois les trois répertoires imbriqués 

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

J'ajoute dans le sous-répertoire files/ un fichier PNG que j'ai créé avec Gimp. J’ai fait plusieurs essais pour obtenir la dimension qui me convient (et qui est légèrement différente de l’originale). Vous pouvez créer votre propre image ou télécharger celle-ci.

[build-qemu]$ ls ../../layers/meta-my-layer/recipes-core/psplash/files/
psplash-poky-img.png

Je vais ensuite créer une extension de recette ayant le même nom que la recette d'origine mais le suffixe «.bbappend». Ce fichier ne contiendra qu'une seule ligne 

[build-qemu]$ cat  ../../layers/meta-my-layer/recipes-core/psplash/psplash_git.bbappend
FILESEXTRAPATHS:prepend := "${THISDIR}/files:"

Il s’agit d’un contenu que l’on retrouve très souvent dans les fichiers .bbappend (avec parfois de légères variations).

La partie gauche de l'affectation signifie ici «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. Comme son nom l'indique, cette dernière représente le répertoire courant, celui de l'extension de recette.

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 «:» qui n'a rien à voir avec ceux de la partie gauche. La variable «FILESEXTRAPATHS» contient la liste des chemins dans lesquels on recherche les fichiers nécessaires 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. Ici, le nom du répertoire (files/) est le même que le répertoire de la recette initiale, mais cela n'est pas obligatoire.

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

[build-qemu]$ bitbake  my-image

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

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 du patch.

Ajout d'un patch dans une recette

Nous allons commencer par produire et faire appliquer un patch sur un fichier fourni directement par une recette, sans qu'il y ait de compilation. 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 situés dans son sous-répertoire base-files/. L’un d’eux est «profile» qui est copié 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. Son contenu est pris en compte par exemple lorsqu'il faut enregistrer un message de commit pour git ou éditer un fichier de programmation horaire (crontab). Cette variable est initialisée à la valeur «vi». Mais nous avons installé sur notre image l'éditeur nano, il serait dommage de ne pas en profiter. Créons donc un patch pour modifier cette ligne du fichier.

Dans le cas (assez rare) où le patch à créer concerne les fichiers fournis par une recette comme le fichier /etc/profile, le plus simple est d'appeler manuellement diff. Lorsqu'il s'agira de modification des fichiers d'un projet téléchargé avec git par exemple, on préférera faire appel à l'outil «devtool» dont nous parlerons prochainement.

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  ../../layers/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  base-files-origin  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 que le patch puisse être créé correctement :

[build-qemu]$ diff  -ruN  base-files-origin/  base-files-modified/ 
diff -ruN base-files-origin/profile base-files-modified/profile
diff -ruN base-files-origin/profile base-files-modified/profile
--- base-files-origin/profile	2024-07-26 12:29:48.736573323 +0200
+++ base-files-modified/profile	2024-07-26 12:30:54.326491282 +0200
@@ -66,7 +66,7 @@
 fi
 
 if [ -z "$EDITOR" ]; then
-	EDITOR="vi"			# needed for packages like cron, git-commit
+	EDITOR="nano"			# needed for packages like cron, git-commit
 fi
 
 export PATH PS1 OPIEDIR QPEDIR QTDIR EDITOR TERM

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  ../../layers/meta-my-layer/recipes-core/base-files/files/

[build-qemu]$ diff  -ruN  base-files-origin/  base-files-modified/  > ../../layers/meta-my-layer/recipes-core/base-files/files/0001-nano-as-preferred-editor.patch

[build-qemu]$ rm  -rf  base-files-origin/  base-files-modified/

Nous avons obtenu ainsi notre fichier de patch. Il doit nécessairement avoir une extension «.patch». Il est recommandé de lui donner un nom significatif (par exemple nano-as-preferred-editor) et l'usage veut que le nom du fichier commence par un nombre qui permettra d'ordonner les patches dans le cas où plusieurs sont fournis (les patches pouvant modifier successivement les mêmes fichiers, l'ordre d'application est important).

Je crée ensuite une extension de recette dans le répertoire base-files/ de notre layer, en vérifiant au préalable le nom de la recette à surcharger :

[build-qemu]$ ls  ../../layers/poky/meta/recipes-core/base-files/
files  base-files_3.0.14.bb

Lorsque le fichier d’extension s’applique à une version spécifique d’une recette (par exemple 3.0.14 uniquement), on le nomme base-files_3.0.14.bbappend. Attention, le caractère souligné (underscore) «_» dans le nom de recette a une véritable signification : il permet de distinguer le nom du package (qui ne peut donc pas contenir de souligné, uniquement des tirets «-» pour séparer les mots) de son numéro de version.

Si l'extension s'applique à plusieurs versions de la recette, on utilise le caractère générique pourcent «%» dont le rôle rapelle celui de l’astérique «*» dans les motifs du shell :

En toute rigueur un patch s'applique à une version spécifique d'un fichier, même s'il est peu probable que le fichier profile change beaucoup d'une version à l'autre de la recette base-files. Je crée donc la recette suivante :

[build-qemu]$ nano ../../layers/meta-my-layer/recipes-core/base-files/base-files_3.0.14.bbappend 
    FILESEXTRAPATHS:prepend := "${THISDIR}/files:"
    SRC_URI += "file://0001-nano-as-preferred-editor.patch"

Comme auparavant nous ajoutons le chemin du sous-répertoire contenant notre patch dans FILESEXTRAPATHS. Notons que l’utilisation de «+=» ne fonctionnerait pas, car il utilise toujours une espace comme caractère de séparation et non un deux-points.

On ajoute notre patch à la liste des fichiers appartenant à la recette (variable SRC_URI). Son extension «.patch» suffit à ce qu’il soit pris en compte correctement par bitbake. On peut relancer la génération de l’image.

Au moment du build une erreur apparaît qui n'existait pas sur les branches de Poky antérieures à Scarthgap:

[build-qemu]$ bitbake  my-image
ERROR: base-files-3.0.14-r0 do_patch: QA Issue: Missing Upstream-Status in patch
/home/Builds/Lab/layers/meta-my-layer/recipes-core/base-files/files/0001-nano-as-preferred-editor.patch
Please add according to https://docs.yoctoproject.org/contributor-guide/recipe-style-guide.html#patch-upstream-status . [patch-status]
ERROR: base-files-3.0.14-r0 do_patch: Fatal QA errors were found, failing task.
ERROR: Logfile of failure stored in: /home/Builds/Lab/builds/build-qemu/tmp/work/qemuarm-poky-linux-gnueabi/base-files/3.0.14/temp/log.do_patch.3049766
ERROR: Task (/home/Builds/Lab/layers/poky/meta/recipes-core/base-files/base-files_3.0.14.bb:do_patch) failed with exit code '1'
NOTE: Tasks Summary: Attempted 2437 tasks of which 2432 didn't need to be rerun and 1 failed.

Le système interne de Yocto chargé de surveiller la qualité du build nous indique que dorénavant un champ «Upstream-Status» doit être présent en entête des patches pour faciliter la maintenance de la recette. Ce champ indique la situation du patch par rapport à la recette originale sur laquelle il s'applique. Les valeurs possibles sont :

J'ajoute donc une ligne en tout début de mon fichier de patch, qui devient :

Upstream-Status: Pending
diff -ruN base-files-origin/profile base-files-modified/profile
--- base-files-origin/profile   2024-07-26 02:29:48.736573323 +0200
+++ base-files-modified/profile 2024-07-26 02:30:54.326491282 +0200
 [...]

Après rebuild, nous testons notre résultat :

My experimental distro 1.0 mybox ttyAMA0

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 s’appliquant sur un fichier source d’un package téléchargé et compilé par une recette.

Pour continuer avec les packages que nous avons déjà installés, je vous propose de travailler sur le code de l'éditeur nano.

Pour ce faire, nous pourrions télécharger les sources de nano, les extraire, faire notre modification, puis créer un patch avec diff comme précédemment ou avec git par exemple. Une autre solution beaucoup plus adaptée à ce genre de situation est l'utilisation de la commande devtool que nous avons déjà aperçue en début de chapitre pour rechercher l'emplacement d'un fichier recette.

La force de devtool est d'être capable de prendre en charge toutes les actions nécessaires pour créer le patch et l'extension de recette en nous laissant nous concentrer sur la modification des sources.

Nous commençons par demander à devtool de nous déployer les sources dans un répertoire où nous pourrons faire nos modifications.

[build-qemu]$ devtool  modify  nano
NOTE: Starting bitbake server...
  [...]
Initialising tasks: 100% |###############################################################################################| Time: 0:00:00
NOTE: Executing Tasks
NOTE: Tasks Summary: Attempted 113 tasks of which 110 didn't need to be rerun and all succeeded.
INFO: Source tree extracted to /home/Builds/Lab/builds/build-qemu/workspace/sources/nano
INFO: Recipe nano now set up to build from /home/Builds/Lab/builds/build-qemu/workspace/sources/nano

[build-qemu]$

En résumé, bitbake nous indique avoir fait deux choses :

A tout moment on peut annuler cette situation et revenir à la configuration précédente avec «devtool reset nano».

Entrons dans le répertoire des sources de nano :

[build-qemu]$ cd  workspace/sources/nano/

[nano]$ ls
ABOUT-NLS  COPYING.DOC   INSTALL      NEWS    TODO        config.guess  config.sub    depcomp     lib      po
AUTHORS    ChangeLog     Makefile.am  README  aclocal.m4  config.h.in   configure     doc         m4       src
COPYING    IMPROVEMENTS  Makefile.in  THANKS  compile     config.rpath  configure.ac  install-sh  missing  syntax

[nano]$ git  status
On branch devtool
nothing to commit, working tree clean

Les fichiers sources sont bien présents et une branche spécifique pour git a été créée pour nos modifications.

Pour cet exemple, je cherche à faire une modification simple mais assez facilement visible. Ainsi, je propose de modifier le texte de bienvenueWelcome to nano. For basic help, type CTRL+G.») qui apparaît juste au-dessus des deux lignes rappelant les raccourcis clavier lorsqu'on lance «nano» sans argument. On voit ce texte sur la figure II.3-2.

Nous pouvons trouver le message de bienvenue dans le fichier src/nano.c à la ligne 2551 (dans nano 7.2) :

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

Je modifie le fichier source pour remplacer le message par :

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

Je crée un commit pour cette modification en lui donnant un libellé descriptif. Dans un vrai projet on enchaîne souvent une dizaine de commits au fur et à mesure de la mise au point.

[nano]$ git commit src/nano.c -m 'modify welcome message.'
[devtool bf4b83b] modify welcome message
 1 file changed, 1 insertion(+), 1 deletion(-)

Nous pourrions tester notre modification avant de la commiter. Ici, elle est si simple que je la valide directement. Pour cela je sors du répertoire de travail.

[nano]$ cd ../../../

[build-qemu]$

Et je demande à devtool d'intégrer mon commit sous forme de patch dans mon layer.

[build-qemu]$ devtool  update-recipe  nano  -a  ../../layers/meta-my-layer/
NOTE: Starting bitbake server...
  [...]
NOTE: Writing append file /home/Builds/Lab/layers/meta-my-layer/recipes-support/nano/nano_7.2.bbappend
NOTE: Copying 0001-modify-welcome-message.patch to /home/Builds/Lab/layers/meta-my-layer/recipes-support/nano/nano/0001-modify-welcome-message.patch

[build-qemu]$ 

devtool a créé :

[build-qemu]$ cat ../../layers/meta-my-layer/recipes-support/nano/nano_7.2.bbappend
FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"
SRC_URI += "file://0001-modify-welcome-message.patch"

on remarquera que dans la ligne «FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"» la partie gauche «"${THISDIR}/files:"» que nous utilisions auparavant a été remplacée par «"${THISDIR}/${PN}:"», la variable «${PN}» (Package Name) contenant le nom de la recette.

Nous pouvons relancer le build de notre système :

[build-qemu]$ bitbake  my-image

Puis sur notre cible virtuelle, nous pouvons admirer le résultat de notre patch en lançant la commande nano, comme on le voit sur la figure II.3-3.

Conclusion

Nous avons vu dans cette séquence comment modifier le comportement de recettes existantes sans toucher aux fichiers originaux, 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.

 

Si vous préférez une session de cours interactif, en mode présentiel ou distanciel, n'hésitez pas à vous inscrire à mes formations "Développeur Linux embarqué avec Yocto Project" ou "Yocto Project avancé" .

Ce document est placé sous licence Creative Commons CC BY-NC. Vous pouvez copier son contenu et le réemployer à votre gré pour une utilisation non-commerciale. Vous devez en outre mentionner sa provenance.

Le nom Yocto Project est une marque déposée par la Linux Foundation. Le présent document n'est en aucune façon approuvé par Yocto Project ou la Linux Foundation.

«« sommaire »»