«« sommaire »»

III.3 – Intégrer notre application dans la production de Yocto

Christophe BLAESS - juillet 2024

Pour le moment, nous avons compilé notre code applicatif métier à l’extérieur de Yocto, en utilisant la toolchain extraite (et installée éventuellement sur une autre machine de développement). Ceci est parfaitement acceptable — et même recommandé — pendant les phases initiales de codage, mise au point, et débogage pour simplifier le travail du développeur et réduire les temps de compilation.

Une fois le projet suffisament stable pour les premiers déploiements, il est intéressant d'incorporer la compilation du code métier directement dans la production de l'image embarquée. Généralement cela concerne une branche stable du code source, tandis que des branches de développement, d'expérimentation et de test coexistent dans le système de gestion de versions.

Dans cette séquence nous allons voir comment obtenir une recette simple afin d’intégrer directement notre code applicatif dans l’image produite par bitbake.

Recette utilisant les Autotools

Les recettes les plus simples à écrire sont celles qui font appel aux méthodes des outils de construction standards comme les Autotools. En effet, Yocto contient déjà des classes (des fichiers «.bbclass» situés dans poky/meta/classes-recipes/) capables de compiler ce type de projet. Il suffit que notre recette demande à hériter des méthodes de la classe.

Nous pouvons assez facilement écrire ce type de recette. Mais il y a encore mieux : la commande «devtool» que nous avons déjà utilisée précédemment est capable de créer la recette adéquate en étudiant l'organisation des fichiers sources. Il nous suffit de lui donner le lien vers le répertoire de sources :

[build-qemu]$ devtool  add  hello-autotools  https://github.com/logilin/hello-autotools
NOTE: Starting bitbake server...
INFO: Fetching git://github.com/logilin/hello-autotools;protocol=https;branch=master...
[...]
INFO: Using default source tree path /home/Builds/Lab/builds/build-qemu/workspace/sources/hello-autotools
INFO: Recipe /home/Builds/Lab/builds/build-qemu/workspace/recipes/hello-autotools/hello-autotools_git.bb has been automatically created; further editing may be required to make it fully functional

«devtool» nous indique avoir téléchargé le package de sources et l'avoir extrait dans le sous-répertoire «workspace/sources/» comme nous l'avions déjà vu avec notre patch pour nano Puis elle nous dit avoir créé une recette de base qu'ellle nous conseille de vérifier.

Nous pouvons examiner et éditer cette recette 

[build-qemu]$ devtool  edit-recipe  hello-autotools

# Recipe created by recipetool
# This is the basis of a recipe and may need further editing in order to be fully functional.
# (Feel free to remove these comments when editing.)

# WARNING: the following LICENSE and LIC_FILES_CHKSUM values are best guesses - it is
# your responsibility to verify that the values are complete and correct.
LICENSE = "GPL-2.0-only"
LIC_FILES_CHKSUM = "file://LICENSE;md5=8c16666ae6c159876a0ba63099614381"

SRC_URI = "git://github.com/logilin/hello-autotools;protocol=https;branch=master"

# Modify these as desired
PV = "1.0+git"
SRCREV = "c5e0f97948c37f38b016dc6ec616a1a4389a592f"

S = "${WORKDIR}/git"


# NOTE: if this software is not capable of being built in a separate build directory
# from the source, you should replace autotools with autotools-brokensep in the
# inherit line
inherit autotools

# Specify any options you want to pass to the configure script using EXTRA_OECONF:
EXTRA_OECONF = ""

Ce fichier mérite quelques explications :

La recette légèrement modifiée est donc la suivante :

[build-qemu]$ cat  workspace/recipes/hello-autotools/hello-autotools_git.bb

SUMMARY = "Simple Hello World based on autotools."
LICENSE = "GPL-2.0-only"
LIC_FILES_CHKSUM = "file://LICENSE;md5=8c16666ae6c159876a0ba63099614381"

SRC_URI = "git://github.com/logilin/hello-autotools;protocol=https;branch=master"

PV = "1.0+git"
SRCREV = "c5e0f97948c37f38b016dc6ec616a1a4389a592f"

S = "${WORKDIR}/git"


inherit autotools

EXTRA_OECONF = ""

Je la valide, un peu comme nous l'avons fait avec le patch de nano, en précisant dans quel layer et quel sous-répertoire «recipes-...» l'ajouter :

[build-qemu]$ devtool  finish  hello-autotools ../../layers/meta-my-layer/recipes-custom/
NOTE: Starting bitbake server...
[...]
INFO: Moving recipe file to /home/Builds/Lab/layers/meta-my-layer/recipes-custom/hello-autotools
[...]

[build-qemu]$ ls  ../../layers/meta-my-layer/recipes-custom/hello-autotools/
hello-autotools_git.bb

Il nous suffit à présent d’ajouter la ligne suivante dans notre fichier image.

IMAGE_INSTALL:append = " hello-autotools"

Après re-compilation, notre projet est bien installé :

My experimental distro 1.0 mybox ttyAMA0

mybox login: root
Password: (linux)

root@mybox:~# hello-autotools
Hello from mybox (built with autools)

root@mybox:~# 

Recette utilisant CMake

Les projets que l’on compile avec l’environnement CMake peuvent également être facilement incorporés à Yocto en utilisant devtool :

[build-qemu]$ devtool  add  hello-cmake  https://github.com/logilin/hello-cmake
  [...]
INFO: Recipe /home/Builds/Lab/builds/build-qemu/workspace/recipes/hello-cmake/hello-cmake_git.bb has been automatically created; further editing may be required to make it fully functional

[build-qemu]$ devtool  edit-recipe  hello-cmake

Après suppression des lignes de commentaires inutiles, la recette devient :

LICENSE = "GPL-2.0-only"
LIC_FILES_CHKSUM = "file://LICENSE;md5=8c16666ae6c159876a0ba63099614381"

SRC_URI = "git://github.com/logilin/hello-cmake;protocol=https;branch=master"

PV = "1.0+git"
SRCREV = "dd3affd45589b47c60e26369c0a747b135eb1fee"

S = "${WORKDIR}/git"

inherit cmake

EXTRA_OECMAKE = ""

Je valide la recette puis je l'ajoute dans mon image :

[build-qemu]$ devtool finish hello-cmake ../../layers/meta-my-layer/recipes-custom/	
  [...]

[build-qemu]$ nano ../../layers/meta-my-layer/recipes-custom/images/my-image.bb 
 [...]
IMAGE_INSTALL:append = " hello-cmake"

Et après compilation et boot, on observe :

My experimental distro 1.0 mybox ttyAMA0

mybox login: root
Password: (linux)

root@mybox:~# hello-cmake
Hello from mybox (built with CMake)

root@mybox:~# 

Recette pour un projet avec Makefile personnalisé

Il arrive fréquemment qu’un projet dispose d’un Makefile spécifique, directement livré avec les sources, sans passer par la phase de configuration des Autotools ou de CMake.

Pour ce type de projet, devtool est capable de créer une recette par défaut, mais il peut être nécessaire de l'éditer un peu plus que les précédentes.

[build-qemu]$ devtool  add  hello-makefile  https://github.com/logilin/hello-makefile
[...]
INFO: Recipe /home/Builds/Lab/builds/build-qemu/workspace/recipes/hello-makefile/hello-makefile_git.bb has been automatically created; further editing may be required to make it fully functional

[build-qemu]$ devtool  edit-recipe  hello-makefile

Après quelques effacements de commentaires, la recette devient :

LICENSE = "GPL-2.0-only"
LIC_FILES_CHKSUM = "file://LICENSE;md5=8c16666ae6c159876a0ba63099614381"

SRC_URI = "git://github.com/logilin/hello-makefile;protocol=https;branch=master"

PV = "1.0+git"
SRCREV = "f15090770356f854c3403ebe0bcaad51d4439ffc"

S = "${WORKDIR}/git"

do_compile () {
        oe_runmake
}

do_install () {
        oe_runmake install 'DESTDIR=${D}'
}

Nous voyons l'utilisation de la commande «oe_runmake()» (Open Embedded Run Make) de Yocto Project qui permet d'appeler make en ayant renseigné les variables d'environnement nécessaires.

Vérifions le Makefile du projet :

[build-qemu]$ cat  workspace/sources/hello-makefile/Makefile 
  [...]
CC ?= gcc
CFLAGS += -Wall
EXE = hello-makefile
OBJS = hello-makefile.o
DESTDIR ?= /usr/local/bin

[...]

.PHONY: install

install: $(EXE)
	install -m 0755 -d ${DESTDIR}/
	install -m 0755 ${EXE} ${DESTDIR}/

Nous voyons que le Makefile installe le fichier exécutable directement dans le répertoire ${DESTDIR}. Mais la recette lui passe la valeur ${D} c'est-à-dire la racine du système de fichiers de la cible. Nous devons modifier cette ligne. Je remplace donc la fin de la recette ainsi :

[...]

do_install () {
        oe_runmake install 'DESTDIR=${D}${bindir}'
}

Je valide ma recette :

[build-qemu]$ devtool  finish  hello-makefile ../../layers/meta-my-layer/recipes-custom/ 

Après ajout de «IMAGE_INSTALL:append = " hello-makefile"» dans mon fichier d'image, je relance le build et au démarrage de l'émulateur je peux exécuter :

mybox login: root
Password: (linux)

root@mybox:~# hello-makefile 
Hello from mybox (build with standard Makefile) 

root@mybox:~# 

Recette pour un projet sans Makefile

Finalement, les recettes les plus complexes sont celles qui doivent compiler des packages ne comportant aucun Makefile (ou un Makefile mal fichu, qui ne permet pas la cross-compilation). Pour nous placer dans la situation la plus défavorable, imaginons un projet ne comportant qu’un fichier source à télécharger depuis un site web, avec le contenu suivant :

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/param.h>

#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN  64
#endif

int main(void)
{
	char hostname[MAXHOSTNAMELEN];

	gethostname(hostname, MAXHOSTNAMELEN);
	hostname[MAXHOSTNAMELEN - 1] = '\0';

	fprintf(stdout, "Simple hello from %s (built without Makefile)\n", hostname);

#ifndef NDEBUG
	fprintf(stdout,"  [DEBUG MODE]\n");
#endif

#ifdef MY_OPTION
	fprintf(stdout, "  [MY OPTION]\n");
#endif

	return EXIT_SUCCESS;
}

Le fichier ci-dessus prend en considération deux constantes symboliques qui peuvent être précisées lors de la compilation :

Je crée un répertoire hello-simple dans mon layer, et j’ajoute la recette hello-simple.bb suivante, dans laquelle la compilation est explicitement réalisée par la méthode do_compile() et la copie de l'exécutable par do_install(). Le fichier source initial n'ayant pas de numéro de version, on voit que le fichier de recette est nommé simplement avec le nom du package.

[build-qemu]$ mkdir  ../../layers/meta-my-layer/recipes-custom/hello-simple/

Voici le contenu du fichier de recette :

DESCRIPTION = "A simple hello world application built without Makefile."
SECTION = "Custom"

LICENSE = "GPL-2.0-only"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/GPL-2.0-only;md5=801f80980d171dd6425610833a22dbe6"

SRC_URI="https://www.logilin.fr/files/yocto-lab/files/${BPN}.c"
SRC_URI[md5sum] = "ac02346e365c2030fd9acee21023028a"

S = "${WORKDIR}"

TARGET_CFLAGS += "-DNDEBUG -DMY_OPTION"

do_compile() {
        ${CC} ${CFLAGS} ${LDFLAGS} ${S}/${BPN}.c -o ${WORKDIR}/${BPN}
}

do_install() {
        install -m 0755 -d ${D}${bindir}
        install -m 0755 ${WORKDIR}/${BPN} ${D}${bindir}
}

Comme on le voit, la fonction do_compile() fait directement appel au compilateur C, avec les options nécessaires pour la compilation et l’édition des liens. La variable CFLAGS est automatiquement renseignée avec le contenu de la variable TARGET_CFLAGS pendant la cross-compilation pour la cible. Dans cette variable, nous avons défini les deux constantes NDEBUG et MY_OPTION. Le code source du programme appelle fprintf() en fonction de la présence de ces constantes. Bien entendu, pour des projets réels, on est amené à définir des options plus complètes dans TARGET_CFLAGS, et éventuellement TARGET_LDFLAGS.

La recette ci-dessus ne fait pas référence au nom du fichier source, elle utilise seulement le nom du package à travers la variable «${BPN}», elle pourrait donc être adaptée facilement (à la somme de contrôle MD5 près) pour n’importe quel fichier à compiler de cette manière. Après compilation, nous pouvons vérifier que les deux options ont bien été passées au compilateur. Le premier message ne s’affiche que si l’option n’est pas définie, il est donc normal qu’il ne soit pas présent ici.

My experimental distro 1.0 mybox ttyAMA0

mybox login: root
Password: (linux)

root@mybox:~# hello-simple 
Simple hello from mybox (built without Makefile) 
  [MY OPTION]

root@mybox:~# 

Conclusion

Nous avons vu dans cette séquence différentes manières d’intégrer dans le build de Yocto des applications externes, qu’il s’agisse de projets développés spécifiquement ou des utilitaires libres glanés sur Internet (dans ce cas, il est toutefois préférable de s’assurer que la recette ne soit pas déjà disponible quelque part). Cette intégration de code personnalisé dans l’image produite par Yocto Project est généralement réalisée en fin de projet, car il est plus simple pendant la phase de mise au point d'assurer la compilation indépendamment de bitbake, comme nous l’avons vu dans la séquence précédente.

Ainsi s’achève la troisième partie de ce cours en ligne, après le développement du code métier, nous allons nous intéresser à l’ajustement de l'image pour une cible spécifique.

 

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 »»