Yocto Cooker (1/3)

Publié par cpb
Jan 13 2022

Cette petite série de trois articles présente un outil (Yocto Cooker) qui permet d’organiser tous les fichiers nécessaires à la compilation d’un système Linux embarqué avec Yocto Project. Cet outil permet également de lancer automatiquement un ou plusieurs builds (compilation d’images complètes prêtes à installer).

Attention: cet article ne présente pas les concepts propres à Yocto Project, on pré-suppose une certaine familiarité avec cet environnement. Pour trouver une introduction à Yocto, et un tutorial complet sur son utilisation, on se reportera à l’article Linux embarqué avec Yocto Project.

Problématique

L’outil Yocto Project représente aujourd’hui l’une des deux principales solutions pour mettre au point et produire un système embarqué basé sur Linux.

À mon sens, le meilleur avantage de Yocto sur son principal concurrent (Buildroot) est la possibilité de gérer la configuration du système à différents niveaux (paramétrage des packages, contenu de l’image, définition de la plateforme cible, etc.) et de partager ces éléments de configuration entre différents builds. On peut ainsi gérer aisément une gamme complète de produits que ce soit au niveau matériel (lorsque la même application métier doit tourner sur différente plateformes, avec différentes options en terme de périphériques) ou au niveau applicatif (lorsqu’on propose plusieurs versions du même code métier : « lite » , normal, « full-featured » etc.).

La contrepartie de cette versatilité est la complexité de la configuration (qui représente donc le point faible de Yocto Project par rapport à Buildroot).

On peut se retrouver rapidement avec plusieurs dizaines de layers, une douzaine de configurations pour les machines, les images, voire les distros. Pour chaque build on peut avoir à gérer des fichiers local.conf et bblayers.conf spécifiques. Bref, la maintenance d’un environnement destiné à produire des images pour plusieurs projets, plusieurs plateformes ou plusieurs versions logicielles peut vite devenir un enfer.

Confrontés à ce problème pour le projet d’un client, nous avons décidé, avec mon collègue Patrick Boettcher de développer un outil permettant d’organiser le plus simplement possible les différents builds. Notre objectif était de centraliser dans un seul fichier tous les éléments nécessaires pour réaliser un voire plusieurs builds : notamment les noms et origines des layers à utiliser et les paramètres spécifiques à ajouter dans le fichier local.conf.

L’environnement Yocto Project utilise un vocabulaire issu de la cuisine (recipes, bitbake, etc.). Nous avons souhaité nous inscrire dans cette coutume, et comme notre logiciel est destiné à encadrer le travail du cuisinier bitbake, nous avons décidé de l’appeler chef. Malheureusement, à peine celui-ci publié nous avons reçu des menaces de procès car un logiciel commercial de gestion de configuration utilise déjà ce nom.

Nous avons choisi de le re-baptiser (un peu dans l’urgence) Yocto Cooker. Ce nom, choisi rapidement, n’est pas non plus parfait, car il existe au sein même de bitbake un fichier cooker.py. Attention donc à ne pas faire de confusion, notre projet est complètement extérieur à bitbake.

Principe

Yocto Cooker – ou plus simplement cooker – est un script Python qui, après analyse d’un fichier de configuration au format JSON, prépare les répertoires de travail nécessaires, télécharge les layers choisis et démarre bitbake pour réaliser le build désiré.

Comme cooker est là pour organiser le travail du cuisinier bitbake, son fichier de configuration en JSON s’appelle un menu.

Partant d’un simple fichier menu, cooker va préparer les répertoires et les fichiers selon l’arborescence suivante.

+- layers/ +- poky/
|          +- meta-openembedded/
|          +- meta-custom-layer/
|          +- ...
|
+- downloads/ ...
|
+- sstate-cache/ ...
|
+- builds/ +- build-custom-1/ conf/ +- local.conf
           |                        +- bblayers.conf
           |                        +- templateconf.cfg
           |
           +- build-custom-2/ conf/ +- local.conf
           |                        +- bblayers.conf
           |                        +- templateconf.cfg
           |
           +- ...

Tous les layers téléchargés sont proprement rangés dans le sous-répertoire layers/. Durant la phase de mise au point, il est possible de travailler dans certains d’entre-eux, nous en reparlerons. Notons que parfois les layers se trouvent en réalité à un degré d’imbrication plus loin. C’est le cas par exemple de la dizaine de layers se trouvant dans l’arborescence meta-openembedded.

Le répertoire downloads/ est utilisé par bitbake comme point de stockage lorsqu’il télécharge les sources des packages. Ceci afin d’éviter un téléchargement inutile si le package est déjà présent. À cet emplacement de l’arborescence, ce répertoire est mutualisé entre tous les builds.

De même le répertoire sstate-cache/ contient des éléments produits lors d’un build, qui sont susceptibles d’être réutilisés lors des builds suivants. Donc cooker le place à côté de download pour le partager entre tous les builds.

Le répertoire builds/ enfin contient un sous-dossier pour chaque build désiré. Dans chaque sous-dossier, cooker prépare les fichiers conf/local.conf et conf/bblayers.conf en fonction du contenu du menu fourni. Le fichier conf/templateconf.cfg est toujours identique.

Après avoir rempli cette arborescence, cooker va exécuter bitbake successivement dans chaque répertoire de build (sauf sélection explicite sur la ligne de commande) en ayant correctement initialisé ses variables d’environnement.

Bien sûr cooker propose différentes options de fonctionnement et permet de lancer directement une sous-tâche précise. Nous verrons cela dans les prochains articles.

Installation

Yocto Cooker étant un script Python, son installation se fait facilement à l’aide de pip. Vous pouvez télécharger les sources, les examiner, et les installer avec

$ git clone https://github.com/cpb-/yocto-cooker

$ cd yocto-cooker

$ sudo pip3 install .

Il est aussi possible de faire l’installation directe avec :

$ sudo python3 -m pip install --upgrade git+https://github.com/cpb-/yocto-cooker.git

Notez que vous pouvez également utiliser l’option -e de pip3 install pour que le script soit éditable, dans le cas où vous souhaiteriez contribuer à notre projet (et je vous y encourage fortement !).

Une fois le script cooker installé, vous pouvez le lancer avec l’option --help pour voir ses sous-commandes disponibles ou l’option --version pour vous assurer de la version installée :

$ cooker --version
# 1.1.0

$ cooker --help
usage: Cooker [-h] [--debug] [--version] [-v] [-n] {cook,init,update,generate,show,build,shell,clean} ...

positional arguments:
  {cook,init,update,generate,show,build,shell,clean}
               subcommands of Cooker
  cook         prepare the directories and cook the menu
  init         initialize the project-dir
  update       update source layers
  generate     generate build-configuration
  show         show builds and targets information
  build        build one or more configurations
  shell        run an interactive shell ($SHELL) for the given build
  clean        clean a previously build recipe

optional arguments:
 -h, --help    show this help message and exit
 --debug       activate debug printing
 --version     cooker version
 -v, --verbose activate verbose printing (of called subcommands)
 -n, --dry-run print what would have been done (without doing anything)

Pour pouvoir réaliser un build complet à partir d’une distribution Ubuntu-2020.04 fraîchement installée, il faut ajouter les packages suivants chrpath, cpio, diffstat, gawk, git, locales, locales-all, python3-pip, wget. Si besoin on définira la variable d’environnement LC_ALL ainsi avant de lancer la compilation :

$ export LC_ALL=en_US.UTF-8

Menu

Pour le premier test de cooker, nous allons lui demander de compiler une image de base pour une seule cible. Le menu sera donc très simple. En voici une version que je vais détailler plus bas.

On peut le télécharger ici : menu-001.json

$ cat menu-001.json
{
  "sources" : [
    { "url": "git://git.yoctoproject.org/poky", "branch": "dunfell", "rev": "yocto-3.1.13" }
  ],

  "layers" : [
    "poky/meta",
    "poky/meta-poky",
    "poky/meta-yocto-bsp"
  ],

  "builds" : {

    "qemuarm": {

      "target" : "core-image-base",
      "local.conf": [
        "MACHINE = 'qemuarm' ",
        "IMAGE_FEATURES += 'empty-root-password' "
      ]
    }
  }
}

Comme indiqué plus haut le fichier est au format JSON. Il décrit un objet menu
possédant trois attributs ("sources", "layers" et "builds"). C’est le
cas le plus fréquent, même si nous verrons dans le prochain article deux
autres attributs possibles.

sources

Le premier attribut ("sources") est un ensemble d’objets indiquant comment télécharger les layers utilisés pendant le build. Pour ce premier exemple nous n’utiliserons que Poky, mais on ajoute souvent le dépôt « meta-openembedded » ainsi que les layers permettant de supporter la cible matérielle et des layers développés spécifiquement.

Un objet source peut être défini grâce quelques attributs :

  • le champ "url" indique la provenance du dépôt Git du layer. Pour le moment cooker ne sait gérer que des dépôts Git. Si le besoin s’en fait sentir nous pourrons ajouter d’autres types de téléchargements ultérieurement.
  • le champ "branch" (facultatif) permet de restreindre le clonage à une branche précise. Ceci permet de passer facilement d’une version debug à une version release par exemple.
  • le champ "rev" (facultatif) contient un identifiant de commit Git ou un tag.

L’objectif du projet Yocto Cooker est de gérer facilement des builds du même projet sur plusieurs plateformes et de garantir une pérennité de l’environnement de compilation. Ainsi, il est fortement conseillé de préciser systématiquement le champ "rev" pour garantir la reproductibilité du build.

Un quatrième attribut de l’objet source est "method", absent ci-dessus. Il est surtout utile durant la phase de mise au point d’un layer, lorsqu’on le modifie directement sur place avant de relancer le build. Dans ce cas aucun téléchargement ne doit plus avoir lieu, et l’on indique "method": "ignore".

layers

Le second attribut ("layers") du menu est l’ensemble des layers à prendre en considération pour tous les builds. On voit que poky/, téléchargé précédemment, est un répertoire comprenant trois layers que nous utiliserons systématiquement (en fait Poky comprend deux autres layers meta-selftest et meta-skeleton qui ne nous sont pas utiles).

Nous verrons dans le prochain article qu’il est possible pour chaque build de sélectionner des layers supplémentaires. Si deux builds par exemple sont destinés à deux machines différents, chacun pourra inclure le layer contenant le support pour son type de processeur.

builds

Le troisième attributs du menu ("builds") contient un ensemble de paires clé/valeur qui décrivent les builds à produire. Dans notre cas un seul build est indiqué : "qemuarm". C’est un nom tout à fait arbitraire. Je l’ai choisi bien entendu pour représenter la cible, mais ce pourrait être n’importe quel nom. Attention, il sera utilisé pour créer le répertoire de compilation, évitez donc les espaces et les caractères invalides dans les noms de fichiers.

Un objet build est à son tour décrit par plusieurs attributs. Les deux indiqués ci-dessus sont les seuls véritablement indispensables.

target

Le premier attribut ("target") indique le nom de l’image qui doit être produite par bitbake. C’est l’argument qui lui sera passé sur sa ligne de commande.

local.conf

Le second attribut ("local.conf") est l’ensemble des chaînes de caractères à ajouter dans le fichier conf/local.conf du répertoire de build. Il s’agit de lignes se présentant toujours sous la forme suivante.

" VARIABLE_1 = 'valeur 1' ",
" VARIABLE_2 = 'valeur 2' "

L’encadrement de la ligne complète par des guillemets permet de respecter la syntaxe JSON, et l’encadrement des valeurs par des quotes simples correspond à la syntaxe de Yocto Project. Lorsque plusieurs lignes sont présentes elles doivent être séparées par des virgules.

Ce menu remplit la variable MACHINE avec la chaîne qemuarm afin que bitbake prépare une image susceptible d’être exécutée par l’émulateur Qemu. Cette ligne est indispensable pour réaliser un build avec Yocto.

On ajoute également la chaîne empty-root-password dans la variable IMAGES_FEATURES afin que Yocto nous prépare une image sans mot de passe pour root. C’était le comportement par défaut jusqu’à la mi-2021. Désormais la connexion sans mot de passe explicite (configuré avec inherit extrausers) est impossible sans cette option. Bien entendu elle n’est présente qu’à titre d’exemple pour cet article, on ne l’utilise jamais sur un système réel.

Lancement

L’exécution de Yocto Cooker est très simple. Nous lançons la commande :

$ cooker  cook  menu-001.json

Ceci demande à cooker de préparer (action cook) tous les builds (un seul dans notre cas) décrits dans le fichier menu.

Nous laissons cooker et bitbake travailler pendant une heure environ (cela dépend bien sûr des performances de la machine de compilation) afin d’obtenir une image.

Au bout d’un moment, on observe le compte-rendu de la compilation suivant. Les premières lignes, préfixées par un caractère dièse #, sont dues à cooker, les suivantes à bitbake.

$ cooker cook menu-001.json
# Update layers in project directory
# Downloading source from  git://git.yoctoproject.org/poky
# Updating source /home/cpb/Lab/cooker-article/layers/poky...
# Generating dirs for all build-configurations
# Building build-qemuarm (core-image-base)

[...]
Parsing recipes: 100% |################################################################################| Time: 0:00:16
Parsing of 774 .bb files complete (0 cached, 774 parsed). 1326 targets, 61 skipped, 0 masked, 0 errors.
NOTE: Resolving any missing task queue dependencies

Build Configuration:
BB_VERSION           = "1.46.0"
BUILD_SYS            = "x86_64-linux"
NATIVELSBSTRING      = "ubuntu-20.04"
TARGET_SYS           = "arm-poky-linux-gnueabi"
MACHINE              = "qemuarm"
DISTRO               = "poky"
DISTRO_VERSION       = "3.1.13"
TUNE_FEATURES        = "arm armv7ve vfp thumb neon callconvention-hard"
TARGET_FPU           = "hard"
meta
meta-poky
meta-yocto-bsp       = "HEAD:795339092f87672e4f68e4d3bc4cfd0e252d1831"

NOTE: Fetching uninative binary shim http://downloads.yoctoproject.org/releases/uninative/3.4/x86_64-nativesdk-libc.tar.xz;sha256sum=126f4f7f6f21084ee140dac3eb4c536b963837826b7c38599db0b512c3377ba2 (will check PREMIRRORS first)
Initialising tasks: 100% |#############################################################################| Time: 0:00:01
Sstate summary: Wanted 1592 Found 0 Missed 1592 Current 0 (0% match, 0% complete)
NOTE: Executing Tasks
NOTE: Tasks Summary: Attempted 4090 tasks of which 8 didn't need to be rerun and all succeeded.

$

Nous pouvons vérifier que bitbake nous a bien produit une image. Pour cela il a créé un répertoire builds/build-qemuarm puisque le nom du build est qemuarm.

$ ls builds/build-qemuarm/tmp/deploy/images/qemuarm/
core-image-base-qemuarm-20220108105921.qemuboot.conf
core-image-base-qemuarm-20220108105921.rootfs.ext4
core-image-base-qemuarm-20220108105921.rootfs.manifest
core-image-base-qemuarm-20220108105921.rootfs.tar.bz2
core-image-base-qemuarm-20220108105921.testdata.json
core-image-base-qemuarm.ext4
core-image-base-qemuarm.manifest
core-image-base-qemuarm.qemuboot.conf
core-image-base-qemuarm.tar.bz2
core-image-base-qemuarm.testdata.json
modules--5.4.158+gitAUTOINC+db8bfc3a10_414c50525a-r0-qemuarm-20220108105921.tgz
modules-qemuarm.tgz
zImage
zImage--5.4.158+gitAUTOINC+db8bfc3a10_414c50525a-r0-qemuarm-20220108105921.bin
zImage-qemuarm.bin

$

L’arborescence produite à ce stade est la suivante (j’ai indiqué surtout les répertoires et fichiers avec lesquels nous sommes susceptibles d’interagir).

+- layers/ +- poky/
|
+- downloads/...
|
+- sstate-cache/...
|
+- builds +-build-qemuarm +-conf/+-local.conf
                          |      +-bblayers.conf
                          |
                          +-tmp/+-deploy/ +- images/
                          |     |         +- licenses/
                          |     |         +- rpm/
                          |     |
                          |     +-work/ + all-poky-linux/
                          |     |       +- armv7veth.../
                          |     |       +- qemuarm-poky.../
                          |     |       +- x86_64-linux/
                          |   [...]
                        [...]

Pour tester notre build, il faut lancer qemu-system-arm en lui passant pas mal de paramètres. Or ceux-ci sont déjà présents dans le fichier d’extension .qemuboot.conf ci-dessus et sont lus par le script runqemu fourni par Poky.

Néanmoins il n’est pas possible de lancer directement runqemu :

$ runqemu
runqemu: command not found

$

Pour cela il faudrait avoir chargé les variables d’environnement fournies par Poky (c’est le rôle du fameux script poky/oe-init-build-env). C’est ce que nous propose cooker avec son action shell.

$ cooker shell build-qemuarm

### Shell environment set up for builds. ###
[...]

[build-qemuarm]$ pwd
/home/cpb/Lab/cooker-article/builds/build-qemuarm

[build-qemuarm]$

On notera que pour l’action shell il n’est pas nécessaire de préciser le nom du fichier menu, celui-ci est mémorisé dans un fichier .cookerconfig dans le répertoire où l’on a lancé cooker cook . Nous retrouverons ce comportement pour d’autres actions de cooker qui seront présentées dans le troisième article de cette série.

Note : au moment de la rédaction de ces lignes, cette commande peut échouer sur les systèmes où le fichier /bin/sh est un lien symbolique vers /bin/dash. Pour corriger le problème, il suffit de renseigner la variable d’environnement SHELL avant de lancer cooker ainsi : export SHELL=/bin/bash.

Ici cooker nous a initialisé un nouveau shell avec les variables d’environnement nécessaires. Il nous a également déplacés dans le répertoire de build. Il faudra penser à quitter ce shell avant de relancer éventuellement cooker.

Relançons runqemuarm avec deux arguments : le premier pour qu’il démarre en console texte uniquement, le second pour indiquer l’architecture de la cible.

[build-qemuarm]$ runqemu nographic qemuarm
runqemu - INFO - Running MACHINE=qemuarm bitbake -e ...
runqemu - INFO - Continuing with the following parameters:
KERNEL: [/home/cpb/Lab/cooker-article/builds/build-qemuarm/tmp/deploy/images/qemuarm/zImage--5.4.158+gitAUTOINC+db8bfc3a10_414c50525a-r0-qemuarm-20220108105921.bin]
MACHINE: [qemuarm]
FSTYPE: [ext4]
  [...]
[    0.000000] Booting Linux on physical CPU 0x0
[    0.000000] Linux version 5.4.158-yocto-standard (oe-user@oe-host) (gcc version 9.3.0 (GCC)) #1 SMP PREEMPT Tue Nov 9 16:43:05 UTC 2021
[    0.000000] CPU: ARMv7 Processor [412fc0f1] revision 1 (ARMv7), cr=30c5387d
[    0.000000] CPU: div instructions available: patching division code
[    0.000000] CPU: PIPT / VIPT nonaliasing data cache, PIPT instruction cache
[    0.000000] OF: fdt: Machine model: linux,dummy-virt
[    0.000000] Memory policy: Data cache writealloc
[    0.000000] psci: probing for conduit method from DT.
  [...]
[    5.747954] udevd[137]: starting version 3.2.9
[    5.814839] udevd[138]: starting eudev-3.2.9
[    7.933359] EXT4-fs (vda): re-mounted. Opts: (null)
[   15.199894] Installing knfsd (copyright (C) 1996 okir@monad.swb.de).
[   16.930967] NFSD: Using /var/lib/nfs/v4recovery as the NFSv4 state recovery directory
[   16.944135] NFSD: Using legacy client tracking operations.
[   16.945009] NFSD: starting 90-second grace period (net f0000039)

Poky (Yocto Project Reference Distro) 3.1.13 qemuarm /dev/ttyAMA0

qemuarm login:

Notre build s’est donc bien passé, nous pouvons même nous connecter (sans mot de passe puisque nous l’avons configuré ainsi) :

qemuarm login: root
root@qemuarm:~# uname -a
Linux qemuarm 5.4.158-yocto-standard #1 SMP PREEMPT Tue Nov 9 16:43:05 UTC 2021 armv7l GNU/Linux
[...]
root@qemuarm:~# halt
[...]

Deactivating swap...
Unmounting local filesystems...
[  274.354864] EXT4-fs (vda): re-mounted. Opts: (null)
[  275.562475] reboot: Power down
runqemu - INFO - Cleaning up

[build-qemuarm]$

Attention, à ce niveau nous sommes encore dans le shell que cooker a lancé pour nous et nous devons le quitter pour revenir à l’état initial.

[build-qemuarm]$ exit

$

Conclusion

Dans l’expérience ci-dessus nous avons vu que Yocto Cooker nous facilite la tâche de compilation d’une image, en regroupant tous les paramètres du build dans un seul fichier, le menu, qui devient le nœud central de la configuration, le seul fichier à sauvegarder, à versionner et à conserver pour assurer la reproductibilité de l’opération.

Dans l’article suivant nous examinerons des attributs supplémentaires du menu, ainsi que la notion d’héritage entre les builds. Dans le troisième et dernier article de cette série nous verrons d’autres options et actions proposées par cooker.

Précédent :

URL de trackback pour cette page