Les outils Buildroot et Yocto sont les "build systems" (outils de construction) les plus utilisés dans l’environnement Linux embarqué et ils ont été évoqués maintes fois dans de nombreux articles ou ouvrages. Lors des diverses prestations réalisées sur toutes les routes de France et de Navarre, la sempiternelle question revient régulièrement :
“Vaut-il mieux utiliser Buildroot ou Yocto pour mon projet ?”
En corollaire, la question de l’utilisation d’une distribution classique adaptée à la cible peut également se poser car les distributions comme Debian proposent désormais un support ARM ainsi que des outils de construction comme ELBE [1] ou DEBOS [2]. De même, la société Canonical a mis en place une offre commerciale Ubuntu Core [3] optimisée en empreinte mémoire et dédiée aux solutions Edge / IoT . Enfin, la société française (bretonne !) IoT.bzh propose Redpesk Embedded Linux, qui fournit un haut niveau de sécurité et des fonctions de "conteneurisation". [22]
Cette approche est cependant très différente avec son lot d’avantages et d'inconvénients. Même si elle ne constitue pas le cœur du sujet, nous en dirons quelques mots à la fin de l’article.
Ce document n’a pas pour but de décrire en profondeur les fonctionnalités des deux outils mais d’introduire les grandes lignes de chacun afin de réaliser un comparatif.
Introduction à la notion de “build system”
Le concept de build system est déjà utilisé depuis longtemps dans le cadre des composants open source. Le fait qu’il faille compiler les sources sur plusieurs type de plateformes de développement et les utiliser sur des cibles encore plus variées a imposé depuis longtemps la mise en place d’outils comme Autotools [4] (1991) pour le projet GNU (FSF), CMake [5] (2001) ou plus récemment Meson [6] (2012). Le principe est de décrire le projet par des métadonnées qui seront utilisées pour produire un fichier Makefile utilisé ensuite par la commande make. En effet, les paramètres de la compilation peuvent être très différents suivant l’environnement développement (Linux, Windows, MacOS, etc.) et c’est encore plus vrai dans le cas de la compilation croisée (Linux/x86 vers Linux/ARM).
La figure ci-après décrit le principe de fonctionnement de CMake. Au lieu d’un fichier Makefile fourni avec les sources, le fichier CMakeLists.txt est utilisé par la commande cmake afin de produire un Makefile adapté à l’environnement de développement.
Un build system comme Buildroot ou Yocto est une généralisation de ce principe car une distribution Linux (Yocto utilise le terme “image” Linux) est finalement un assemblage de composants produits par les outils précités. Cet ensemble constitue le BSP (Board Support Package) incluant le bootloader, le noyau Linux et par extension le root-filesystem et le SDK (la chaîne de compilation croisée).
La complexité est bien plus grande car il faut prendre en compte les dépendances des composants entre eux. Dans le cas de Yocto, les composants sont produits sous forme de paquets (packages), les formats les plus connus car utilisés dans les distributions classiques étant RPM et DEB (mais également IPK). Le build system utilise une description de la procédure de production du composant binaire (avec ou sans paquet) dans un fichier nommé recette. Le mot est utilisé par Yocto mais il est applicable aux autres build systems comme Buildroot. L’orchestration est réalisée par un moteur de compilation (comme la commande make pour Buildroot ou bitbake pour Yocto).
Les distributions classiques (comme Debian) utilisent elles même un build system mais la principale différence est l’utilisation de paquets binaires disponibles auprès des dépôts Debian alors qu’un build system comme Yocto ou Buildroot construit systématiquement l’ensemble des composants (puis l’image Linux) à partir des sources. Il est toujours possible de re-compiler l’intégralité d’une distribution Debian à partir des sources mais c’est assez rare.
La figure ci-dessous décrit l’architecture générale d’un build system Linux. A partir des sources (à gauche) on produit les éléments du BSP (à droite) : root-filesystem, noyau, device tree, bootloader et SDK. Le centre du schéma correspond aux recettes et à la configuration qui définit les paramètres de l’image à produire (cible matérielle, composants à installer, format des éléments produits).
Le principe est bien entendu adaptable à une autre cible que Linux pour laquelle certains éléments binaires produits seront différents. On peut citer le build system Android / AOSP [7] ou bien celui de RTEMS (RSB - RTEMS Source Builder) [8] .
Selon l’outil utilisé, la configuration est produite via une interface graphique (cas du fichier .config pour Buildroot et de ses dérivés). Dans le cas de Yocto, le fichier de configuration (dont local.conf) est produit automatiquement mais l’adaptation nécessite une édition manuelle.
Concrètement, les principaux build systems dans le monde Linux sont les suivants :
- Buildroot
- OpenWrt, dérivé de Buildroot et dédié aux routeurs Wi-Fi et en premier lieu le WRT54GL de Linksys [9]. Contrairement à Buildroot, il prend en charge les paquets binaires au format IPK.
- LTIB, proche de Buildroot et utilisé pour les BSP Freescale/NXP mais désormais obsolète
- PTXdist, créé par Pengutronix
- Yocto / OpenEmbedded
- Soong, dédié à Android / AOSP (Android Open Source Project)
Si on exclut Yocto et Soong, les build systems cités utilisent un outil de configuration graphique basé sur Kconfig et sont donc assez proches de Buildroot.
Buildroot
Comme de nombreux projets open source, Buildroot était avant tout un outil interne à la communauté du projet uClibc [10] (Micro-C-libC), qui comme chacun le sait est une alternative plus légère à la GNU C Library initialement utilisée sur uCLinux (Micro-C-Linux), une version du noyau Linux dédiée aux processeurs dépourvus de MMU. Le test de la libC - qui est à la base du root-filesystem d’un système Linux - est donc une tâche fondamentale mais répétitive et qu’il est très intéressant d’automatiser.
Le projet a démarré en 2000 et Buildroot est apparu il y a près de 20 ans en janvier 2005. La première version indépendante du projet uClibc est apparue en février 2009. Tout comme les versions suivantes, elle fut nommée 2009.02 (année.mois) . Une nouvelle version est publiée tous les 3 mois, la dernière étant à ce jour la 2024.05.
De part son statut original d’outil interne, Buildroot a toujours conservé quelques caractéristiques toujours valables aujourd’hui :
- Fort accent mis sur la simplicité et le minimalisme
- Simple à utiliser, mais aussi simple à comprendre ou à modifier/étendre
- Cas d'utilisation spéciaux gérés via des scripts d'extension, plutôt que dans Buildroot lui-même
- Réutilisation de technologies existantes : Kconfig [11], emprunté au projet du noyau Linux pour l’outil de configuration, GNU Make (commande make sous Linux) comme séquenceur des tâches
- Communauté ouverte, pas de support officiel par un éditeur ni de version “pro” (contrairement à d’autres projets libres comme Qt ou même Yocto qui est la base de certains outils commerciaux)
La production d’une image pour une cible donnée se limite à deux commandes :
$ make <my-target>_defconfig # create the .config configuration file
$ make
On peut également ajuster le fichier de configuration .config par :
$ make menuconfig
Principaux répertoires
Les principaux répertoires utilisés par Buildroot après extraction des sources sont présentés en gras ci-dessous.
- arch correspond au support des différentes architectures matérielles (arm, x86, etc.).
- board contient des paramètres liés au support des cibles matérielles, par exemple board/raspberrypi . On compte un peu moins de 100 sous-répertoires dans board ce qui correspond à peu près au nombre de cibles prises en charge.
- configs contient la liste des fichiers de configuration pour les cartes prises en charge (sous la forme <my-board>_defconfig)
- linux contient les “recettes” pour la construction du noyau Linux.
- output contient les composants binaires produits par Buildroot. Chaque composant est compilé dans output/build, le résultat final à installer sur la cible est dans output/images.
- package contient la liste des recettes des composants pris en charge par Buildroot (avec un sous-répertoire par composant).
$ ls -l
total 1044
drwxrwxr-x 2 pierre pierre 4096 mai 7 09:30 arch
drwxrwxr-x 85 pierre pierre 4096 mai 7 09:30 board
drwxrwxr-x 26 pierre pierre 4096 mai 7 09:30 boot
-rw-rw-r-- 1 pierre pierre 555505 mai 7 09:30 CHANGES
-rw-rw-r-- 1 pierre pierre 31370 mai 7 09:30 Config.in
-rw-rw-r-- 1 pierre pierre 168570 mai 7 09:30 Config.in.legacy
drwxrwxr-x 2 pierre pierre 20480 mai 7 09:30 configs
-rw-rw-r-- 1 pierre pierre 18767 mai 7 09:30 COPYING
-rw-rw-r-- 1 pierre pierre 85379 mai 7 09:30 DEVELOPERS
drwxr-xr-x 5 pierre pierre 4096 mai 7 10:28 docs
drwxrwxr-x 20 pierre pierre 4096 mai 7 09:30 fs
drwxrwxr-x 3 pierre pierre 4096 mai 7 09:30 linux
-rw-rw-r-- 1 pierre pierre 46252 mai 7 09:30 Makefile
-rw-rw-r-- 1 pierre pierre 2292 mai 7 09:30 Makefile.legacy
drwxrwxr-x 3 pierre pierre 4096 jun 7 18:03 output
drwxrwxr-x 2838 pierre pierre 73728 mai 7 09:30 package
-rw-rw-r-- 1 pierre pierre 1075 mai 7 09:30 README
drwxrwxr-x 13 pierre pierre 4096 mai 7 09:30 support
drwxrwxr-x 3 pierre pierre 4096 mai 7 09:30 system
drwxrwxr-x 6 pierre pierre 4096 mai 7 09:30 toolchain
drwxrwxr-x 4 pierre pierre 4096 mai 7 09:30 utils
Les actions disponibles dans Buildroot sont exclusivement basées sur make et Buildroot subit donc les limitations de cette commande, en particulier l’impossibilité d’exécuter des actions en parallèle, sauf la compilation (option -j de make).
I will be static forever !
“Buildroot est statique” est certainement une des phrases les plus entendues à propos de cet outil. L’explication est avant tout historique, car c’était au départ un outil dédié à la création d’un root-filesystem contenant quelques composants et non une distribution Linux telle qu’on l’entend aujourd’hui. Buildroot ne permet pas d’utiliser des paquets binaires que ce soit dans l’environnement de compilation (host) et a fortiori sur la cible. Il n’y a donc pas de moyen propre d’ajouter ou supprimer un composant sauf si on crée une nouvelle image. Dans le cas de Buildroot, certains évoquent un firmware Linux plus qu’une distribution. À ce jour (version 2024.05), il y a presque 2900 composants intégrés à Buildroot correspondant chacun à un sous-répertoire de package. Dans chaque sous-répertoire, le composant est défini par un fichier Config.in (utilisé par l’outil de configuration graphique), la recette elle-même au format GNU-Make (.mk) ainsi que - si nécessaire - un fichier .hash contenant la somme sha256 de l’archive des sources.
$ ls -l package/zlib
total 8
-rw-rw-r-- 1 pierre pierre 1281 mai 7 09:30 Config.in
-rw-rw-r-- 1 pierre pierre 233 mai 7 09:30 zlib.mk
Buildroot fournit des macros (au format GNU Make) permettant à la recette de prendre en charge les principaux formats utilisés dans l’open source (Autotools, CMake, etc.), exemple pour Autotools :
$(eval $(autotools-package))
L’ajout d’un nouveau paquet passe par l’ajout d’un nouveau sous-répertoire, ce qui met en évidence une limite du modèle. En effet, la version 2009.02 comptait un peu moins de 300 paquets (donc 300 sous-répertoires) et la version actuelle en compte près de 2900 et cela ne cessera d’augmenter. Le fait d’ajouter ou de modifier localement un nouveau sous-répertoire a le gros désavantage de rompre la compatibilité avec la version officielle.
Dans le cas des configurations ou de paquets locaux (donc externes au projet Buildroot), on peut cependant utiliser la variable BR2_EXTERNAL afin d’ajouter un répertoire contenant des nouveaux paquets, mais également des éléments spécifiques au projet comme des patch pour le noyau ou bien des configurations spécifiques (de type <my-project>_defconfig) comme dans le répertoire configs. On pourra alors sélectionner les nouveaux paquets et la nouvelle configuration en utilisant la commande :
$ make BR2_EXTERNAL=/my/directory <my-project>_defconfig
Cette méthode est très utilisée dans le cadre d’un projet mais il n’existe quasiment pas de contributions à Buildroot fournies sous la forme d’un répertoire externe. L’ajout ou la mise à jour d’un composant passe systématiquement par une suite de patch proposée à la communauté Buildroot et s’appliquant à l’arborescence du projet. Ce point est une différence fondamentale avec Yocto.
Cette approche simple permet de garantir la création d’une image Linux en quelques dizaines de minutes sur un PC récent, voire une dizaine de minutes si on utilise un compilateur binaire - donc non compilé par Buildroot.
Yocto / OpenEmbedded
OpenEmbedded (OE) a été initialement développé en 2003 dans le cadre du projet OpenZaurus [12] (pour le Zaurus de Sharp) qui avait pour but de développer une distribution Linux alternative par rapport à la version officielle. En 2010, le projet a été officiellement intégré à la fondation Linux sous le nom Yocto Project [13] mais une bonne partie du contenu (dont les recettes) est issue du projet OpenEmbedded.
Les bases d'OpenEmbedded sont depuis toujours plus ambitieuses que celles de Buildroot :
- Utilisation d’un séquenceur de tâches dédié (commande bitbake) en remplacement de make. La commande bitbake permet d’exécuter en parallèle les différentes étapes de la compilation de la recette suffixée .bb (téléchargement des sources, extraction, compilation, création du paquet, etc.).
- Utilisation de classes permettant un héritage à l’intérieur d’une recette ou bien globalement pour toutes les recettes à construire
- Utilisation systématique d’un système de paquets lors de la compilation des recettes, la base des paquets pouvant être installée sur la cible, ce qui permet d’avoir des fonctionnalités proches de celle d’une distribution Linux
- Création d’un SDK simple à installer sans disposer d’OpenEmbedded sur la PC de développement. Un shell-script d'installation contenant les éléments à installer est produit par OE.
- Généralisation de la notion de “layer” (ou couche) qui correspond à un répertoire contenant un ensemble de métadonnées (recettes, fichiers de configuration)
Dans la suite, nous allons évoquer rapidement les principaux concepts de Yocto qui n’ont pour la plupart pas d’équivalent sur Buildroot ce qui offre donc un grand nombre de fonctionnalités dans le cadre de projets complexes. Bien entendu, le prix à payer est une procédure de compilation beaucoup plus lourde donc un durée de compilation de l’image largement plus longue. En effet, la production de l’image la plus légère (nommée core-image-minimal) pour une cible QEMU prend plus d’une heure sur un PC récent.
$ cd <yocto-src-path>
$ source oe-init-build-env <build-directory> # create the "build" directory
$ bitbake core-image-minimal
Il faut noter que Yocto ne fournit pas d’interface graphique de configuration hormis Toaster [14], une interface web dont les performances laissent à désirer et qui est très peu utilisée.
Notion de layer (ou couche)
Ce point est une différence majeure avec Buildroot. Un environnement de développement Yocto est un ensemble de layers externes ou internes correspondant à des répertoires meta-* et fournis par différents projets comme le projet Yocto officiel ou bien le fournisseur du BSP de la cible matérielle. Le schéma ci-dessous décrit l’organisation des différents layers. Ceux du projet Yocto (en jaune) sont systématiquement présents. Les autres (en bleu) sont fournis par le BSP ou bien d’autres communautés techniquement liées au projet Yocto sans en faire officiellement partie.
Ce principe permet de déléguer les développements spécifiques à d’autres communautés sans impacter la charge de développement du projet Yocto. A titre d’exemple, le layer meta-raspberrypi [15] contient le BSP Yocto pour l’ensemble des cartes Raspberry Pi et il est géré par un développeur totalement indépendant du projet officiel. Contrairement à Buildroot, il y a très peu de cibles matérielles disposant d’un support Yocto officiel. La dernière version LTS de Yocto (5.0) fournit le support pour plusieurs cibles émulées par QEMU (arm, arm64, x86, etc. soit 12 au total) et 4 cibles réelles dont la BeagleBone Black. Cela fait 16 cartes à comparer avec la centaine de cibles du projet Buildroot !
Utilisation des classes
Ce point est un des éléments importants des fonctionnalités de Yocto. Une classe pourra être utilisée pour prendre en charge le format des sources à compiler par la recette (utilisation du build system Autotools, CMake, etc.). Pour ce faire, on ajoutera simplement la ligne suivante à la recette :
inherit autotools
Une classe peut également être utilisée pour valider une fonctionnalité valable pour l’environnement de compilation (donc pour toutes les recettes). Dans ce cas, on renseignera le fichier local.conf . Dans l’exemple qui suit, la classe rm_work permet la suppression des fichiers intermédiaires après la compilation d’une recette.
INHERIT += "rm_work"
Extension de recette (bbappend)
Cette fonctionnalité est certainement une des plus intéressantes et la plus utilisée dans Yocto. Si on considère une recette .bb dans un layer A, on peut dans un layer B créer une extension de la recette dans un fichier suffixé par .bbappend. Les données de la recette initiale init-ifupdown_1.0.bb seront fusionnées avec celles de l’extension. Dans le cas du schéma ci-dessous, le fichier interfaces initial sera remplacé par celui fourni dans le répertoire de la recette étendue. Le fichier d’extension doit porter le même nom mais avec le suffixe .bbappend.
Ce principe permet également d’appliquer des patch à une recette, modifier la configuration du noyau Linux ou ajouter des fonctions exécutées lors de la procédure de compilation de la recette. Dans le cadre d’un développement de projet réel autour de Yocto, on a plus souvent l’occasion d’écrire des extensions de recettes que des recettes originales !
A titre d’exemple l’adaptation du noyau Linux d’un BSP peut conduire à l’application de plusieurs dizaines de patch intégrés à un fichier .bbappend. La commande devtool fournie avec Yocto permet la génération automatique d’un fichier .bbappend et des patch associés.
Comparaison des outils
Buildroot et Yocto sont à la fois proches dans le principe et très différents dans l’approche utilisée et les fonctionnalités offertes. Il n’y a bien évidemment pas de vainqueur ni de vaincu car chaque outil est dédié à un usage donné et il n’est pas si difficile de passer de l’un à l’autre (surtout dans le sens Yocto vers Buildroot). Cependant, la compréhension des principes d’un outil de construction est facilitée par l’existence d’une interface graphique de configuration, et nous conseillons en général de démarrer par Buildroot qui permettra d’arriver plus rapidement à un résultat à partir du moment où il existe un support pour la cible matérielle, ce qui est le cas pour un bon nombre de cartes accessibles du marché comme les Raspberry Pi ou les BeagleBone Black. Les étudiants des filières spécialisées utilisent plus facilement Buildroot car sa mise en place est beaucoup plus simple sur une machine aux performances moyennes.
Cependant, dans un contexte professionnel, la connaissance de Yocto est indispensable du fait de sa large utilisation dans l’industrie car dans la quasi totalité des cas, il est utilisé pour les BSP livrés avec le matériel. En effet, mis à part le français OposSOM [16], il n’existe pas ou très peu de fabricants qui fournissent leur BSP officiel sur Buildroot. NXP utilisaient l’outil LTIB, très proche de Buildroot mais ils ont définitivement migré vers Yocto. Autre point, nous dispensons des formations de prise en main de Yocto alors que le sujet Buildroot est parfois abordé de manière sporadique dans le cadre d’une introduction aux build systems.
Enfin, il faut également prendre en compte les paramètres économiques car Yocto est un projet officiel de la fondation Linux, soutenu par Intel (entre autres) et bénéficiant d’un budget raisonnable. La situation de Buildroot est très différente, car malgré son dynamisme, le projet est soutenu par des sociétés de taille moyenne comme Bootlin (qui cependant dispensent également des formations sur Yocto !).
The truth is out there ?
Dans cet article nous vantons les mérites de l’utilisation d’un outil de type build system pour l’environnement Linux embarqué. Lors d’une participation à un salon en Allemagne il y a quelques années, nous avons eu la chance de discuter avec Thomas Gleixner, fondateur de Linutronix et célèbre pour la maintenance du projet PREEMPT_RT (désormais projet officiel de la fondation Linux).
Selon lui, l’utilisation de Yocto ou Buildroot prive l’utilisateur (et donc le client final) de la maintenance des paquets qui est prise en charge par la distribution. Linutronix a donc créé un outil nommé ELBE (Embedded Linux Build Environment) basé sur Debian et qui profite des alertes de sécurité de la communauté Debian. ELBE est donc un outil hybride entre build system et distribution. Nous avons évoqué cet outil au début de l’article ainsi que DEBOS qui à peu près du même tonneau. Une comparaison Yocto vs Debian/ELBE publiée par Linutronix est disponible sur ce lien [17] .
“ELBE reuses the security of Debian. An update-checker informs if security updates are available.”
L’apparition de nombreuses cartes abordables basées sur l’architecture ARM a pour effet de voir apparaître des versions ARM de distributions comme Debian, Ubuntu, ArchLinux ou même Fedora. Cependant, ces distributions ne sont pas adaptées à un environnement embarqué et sont avant tout des environnements utilisables uniquement pour des tests préliminaires ou des POC (Proof Of Concept). Le cas de l’architecture x86 est à part, car on peut envisager d’utiliser une distribution Debian optimisée installée dans un rack industriel (mais c’est un autre sujet).
Le récent problème de la mise à l’arrêt de la base de CVE (Common Vulnerabilities and Exposures) par la NVD (National Vulnerability Database) [18] est un avantage certain pour Debian car ils disposent de leur propre équipe et maintiennent leur base de données [19]. Dans le même ordre d’idée, la plateforme Jetson de NVIDIA n’utilise pas officiellement Yocto mais un BSP basé sur Ubuntu [20]. Une solution existe cependant auprès d’une entreprise partenaire [21] .
Conclusion
Si l’on exclut l’utilisation de Debian, le choix de l’un ou l’autre des outils (ou bien d’une autre solution) est fortement dépendante du contexte d’activité de l’utilisateur. Dans le cadre de la prise en main d’un build system ou d'un projet simple, Buildroot est fortement conseillé. Yocto est le plus souvent imposé par le BSP fourni par le constructeur mais il est toujours possible pour un ingénieur entraîné d’intégrer les métadonnées du BSP Yocto dans Buildroot (même si ça n'est pas conseillé).