Recipe II
Proseguiamo il discorso che avevamo iniziato sui recipe in un articolo precedente.
Per capire come funziona un recipe dobbiamo intanto trovare dei nomi precisi per riferirci a delle cose precise. Usiamo quindi i nomi che bitbake utilizza per le variabili collegate ad un determinato recipe.
Per quanto riguarda il file .bb o .bbappend
che si trova dentro il layer abbiamo:
FILEpercorso assoluto al file.bbFILE_DIRNAMEpercorso assoluto alla cartella contenente il.bbFILE_LAYERNAMEnome del relativo contenitore di recipePNnomePVversioneBBINCLUDEDpercorsi dei.bb*utilizzati
Per quanto riguarda il processo di build
del recipe, all’interno di build/tmp abbiamo:
WORKDIRdove avviene costruito il recipeSRC_URIda dove arrivano i sorgenti per il recipeSdove vengono messi i sorgentiBdove avviene la compilazioneDdove viene creato il fake-root-filesystem

Seguendo l’esempio di qui sopra abbiamo:
FILE = "/layers/meta-mylayer/recipes-example/myapp/myapp_2.1.bb"
FILE_DIRNAME = "/layers/meta-mylayer/recipes-example/myapp"
FILE_LAYERNAME = "example"
PN = "myapp"
PV = "2.1"
BBINCLUDED = "......."
WORKDIR = "/build/tmp/work/devboard-2/myapp/2.1"
SRC_URI = "https://git.example.com"
S = "/build/tmp/work/devboard-2/myapp/2.1/src"
B = "/build/tmp/work/devboard-2/myapp/2.1/build"
D = "/build/tmp/work/devboard-2/myapp/2.1/image"Un recipe non è altro che una lista di istruzioni
da eseguire per ottenere un risultato a partire da una
serie di ingredienti.
SRC_URI è la variabile principale che ci indica quali
sono questi ingredienti.
Adesso vediamo come aggiungere ingredienti
alla nostra ricetta.
aggiungere ingredienti
Prendiamo la seguente FILE_DIRNAME come esempio:
myapp
├── files
│ ├── special_constants.h
│ ├── doc_special_constants.md
│ └── dark-theme.patch
└── myapp_2.1.bbAvente come recipe:
0SRC_URI = "https://git.example.com"
1SRCREV = "${AUTOREV}"
2
3S = "${WORKDIR}/git"
4B = "${WORKDIR}/build"
5
6do_compile(){
7 # compile commands
8}SRC_URI ha un comportamento un po’ particolare,
perchè a volte estrae i file dentro S,
altre volte li estrae in WORKDIR.
Dipende se stiamo definendo una “cartella/archivio” (come tar, git)
o se stiamo definendo dei file singoli.
I contenuti delle cartelle vengono
estratti o clonati in una posizioine temporanea
e poi spostati dentro S,
mentre i file singoli vengono
copiati direttamente dentro WORKDIR.
Per i file locali SRC_URI cerca in alcune
sottocartelle predefinite di FILE_DIRNAME,
come ad esempio files.
Poi vedremo come poter utilizzare altri nomi.
Quindi se dopo il fetch e unpack la nostra
WORKDIR appare così:
WORKDIR
├── git
│ ├── code.c
│ ├── code.h
│ └── Cmakelists.txt
├── build
├── image
├── packages
└── tempe vogliamo aggiungere il file special_constants.h
senza dover modificare il progetto git originale
possiamo modificare SRC_URI:
0SRC_URI = "https://git.example.com"
1SRCREV = "${AUTOREV}"
2
3S = "${WORKDIR}/git"
4B = "${WORKDIR}/build"
5
6# change original git without forking
7SRC_URI += "file://special_constants.h;subdir=git"
8
9do_compile(){
10 # compile commands
11}dove la costruzione di SRC_URI rispetta la sintassi specifica
SRC_URI = "source1;option1;option2;option3 source2;option1 ..."e l’opzione subdir=git informa bitbake
di posizionare il file nella subdirectory git della WORKDIR.
Altre opzioni di SRC_URI sono visibile nella
documentazione ufficiale.
La nostra WORKDIR appare ora così:
WORKDIR
├── git
│ ├── code.c
│ ├── code.h
│ ├── special_constants.h
│ └── Cmakelists.txt
├── build
├── image
├── packages
└── tempmodificare ingredienti
Spesso capita di dover applicare
delle modifiche ad alcuni file di S
e per farlo possiamo usare vari modi:
- sostituire tali file (sconsigliato)
- applicare una
patchalla repository - creare una funzione apposita
per effettuare un
sedsui file
sostituzione completa
Se vogliamo rimpiazzare totalmente
il file code.c con un omonimo,
ma avente contenuti diversi,
possiamo riscrivere il nostro recipe come:
1SRC_URI = "https://git.example.com"
2SRCREV = "${AUTOREV}"
3
4S = "${WORKDIR}/git"
5B = "${WORKDIR}/build"
6
7# replace file
8SRC_URI += "file://code.c;subdir=git"
9
10do_compile(){
11 # compile commands
12}patch
La creazione della patch la vedremo a breve,
ma intanto facciamo conto di averne una
che a partire dalla repository originale
effettua delle modifiche su code.c:
1SRC_URI = "https://git.example.com"
2SRCREV = "${AUTOREV}"
3
4S = "${WORKDIR}/git"
5B = "${WORKDIR}/build"
6
7# apply patch to entire repository
8SRC_URI += "file://dark-theme.patch;subdir=git"
9
10do_compile(){
11 # compile commands
12}Quando in SRC_URI compare un file con estensione .patch o .diff,
bitbake prova in automatico ad applicarla.
Non è necessario specificare la subdir corretta.
L’opzione apply=no evita questo automatismo.
La nostra WORKDIR appare identica a quella iniziale,
ma con la presenza del file dark-theme.patch e
con i contenuti di code.c modificati dalla patch:
WORKDIR
├── git
│ ├── code.c
│ ├── code.h
│ ├── dark-theme.patch
│ └── Cmakelists.txt
├── build
├── image
├── packages
└── tempsostituzione contenuti (sed)
Il comando sed (stream editor) fornisce diverse modalità per
modificare un file o un qualsiasi input testuale.
Dato un determinato pattern
permette di modificare le singole occorrenze
oppure l’intera riga in cui si verifica l’occorrenza.
Per una spiegazione completa del comando
guardare la documentazione,
qui faremo solo una breve panoramica
delle opzioni più utili
perchè ne esistono veramente molte.
Per modificare un file si utilizzano gli argomenti:
-iindicain-place-eindicaexpressione viene seguito dal comando.
Il comando definisce l’operazione da effettuare e il modo in cui effettuarla. La sintassi generale prevede di indicare:
- l’operazione
- il pattern da cercare
- quante volte eseguire l’operazione sui match trovati
L’operazione è indicata da una singola lettera
e il primo carattere che la segue è considerato
in automatico come il separatore dei vari campi (delimiter).
Come separatore possiamo quindi usare |, ,, @
o altri caratteri a seconda di quale ci torna più comodo.
L’ultimo campo indica dove effettuare l’operazione e può essere:
gper indicareglobalcioè su tutte le occorrenzenumeroper indicare su quale occorrenza (1 indica la prima)numero seguito da gper indicare tutte le occorrenze fino a quella definita
Le lettere associate alle operazioni sono:
spersubstituteaperappendiperinsertdperdeletecperchange(sostituisce l’intera riga)pperprint
Quindi per sostituire tutte le occorrenze di un pattern in un file possiamo usare il comando:
sed -i -e 's|pattern|replacement|g' "file"E se dato un file code.c vogliamo
sostituire tutte le occorrenze di LED_PIN
con OUTPUT_PIN possiamo utilizzare un recipe del tipo:
1SRC_URI = "https://git.example.com"
2SRCREV = "${AUTOREV}"
3
4S = "${WORKDIR}/git"
5B = "${WORKDIR}/build"
6
7do_compile(){
8 # compile commands
9}
10
11
12# define as bitbake variables
13SOURCE_FILE="${S}/code.c"
14PATTERN="LED_PIN"
15REPLACEMENT="OUTPUT_PIN"
16
17do_configure:append () {
18 echo "DYNAMIC CONFIG VALUES"
19
20 # Check if the pattern exists after patching
21 grep -F "${PATTERN}" "${SOURCE_FILE}"
22 if [ $? -ne 0 ]; then
23 bbfatal "Pattern ${PATTERN} not found in ${SOURCE_FILE}"
24 fi
25
26 # Perform the substitution using bitbake variables
27 sed -i -e 's|${PATTERN}|${REPLACEMENT}|g' "${SOURCE_FILE}"
28}rimozione ingredienti
Possiamo effettuarla tramite una
patch come nell’ esempio precedente,
oppure aggiungere del codice
per rimuovere un file dopo che bitbake
ha estratto la repository.
Siccome l’estrazione avviene nel task do_unpack,
aggiungiamo la nostra logica alla fine di questo task.
Se vogliamo eliminare il file code.c
possiamo riscrivere il nostro recipe come:
1SRC_URI = "https://git.example.com"
2SRCREV = "${AUTOREV}"
3
4S = "${WORKDIR}/git"
5B = "${WORKDIR}/build"
6
7do_compile(){
8 # compile commands
9}
10
11
12# define as bitbake variable
13RM_FILE="${S}/code.c"
14
15do_unpack:append() {
16 rm -f "${RM_FILE}"
17}quality of life
Abbiamo visto che, per file locali,
SRC_URI cerca nella sottocartella files.
In realtà le sottocartelle di default sono:
${FILE_DIRNAME}/${PF}${FILE_DIRNAME}/${P}${FILE_DIRNAME}/${PN}${FILE_DIRNAME}/${BP}${FILE_DIRNAME}/${BPN}${FILE_DIRNAME}/files${FILE_DIRNAME}
Per utilizzare dei nomi diversi da quelli di default
dobbiamo aggiungere la nostra sottocartella
al path di ricerca usato da bitbake.
Per farlo è sufficiente utilizzare la variabile FILESEXTRAPATHS.
Se vogliamo utilizzare una cartella di nome additional
possiamo riscrivere il nostro recipe come:
1SRC_URI = "https://git.example.com"
2SRCREV = "${AUTOREV}"
3
4S = "${WORKDIR}/git"
5B = "${WORKDIR}/build"
6
7FILESEXTRAPATHS:prepend := "${THISDIR}/additional"
8SRC_URI += "file://special_constants.h;subdir=git"
9
10do_compile(){
11 # compile commands
12}Così possiamo avere una struttura della FILE_DIRNAME del tipo:
myapp
├── additional
│ ├── special_constants.h
│ ├── doc_special_constants.md
│ └── dark-theme.patch
└── myapp_2.1.bbE questo meccanismo torna comodo soprattuto se
utilizzato in un .bbappend invece che nel .bb,
in quanto THISDIR rappresenta la cartella
attuale in cui bitbake sta effettuando il parsing,
mentre FILE_DIRNAME è sempre associata alla
cartella in cui risiede il file .bb.
Inoltre, il motivo per cui utilizziamo
un := è per valutare immediatamente il valore
di THISDIR.
Il motivo per cui utilizziamo prepend
è per portare questo path davanti a tutti gli altri
già definiti.
La variabile FILESEXTRAPATHS non è globale,
ma privata per ogni recipe.
Considerando di lavorare su un layer differente
rispetto a quello in cui myapp_2.1.bb è definito
possiamo quindi avere:
myapp
└── myapp_2.1.bbmyapp
├── additional
│ ├── special_constants.h
│ └── doc_special_constants.md
└── myapp_%.bbappend1FILESEXTRAPATHS:prepend := "${THISDIR}/additional"
2SRC_URI += "file://special_constants.h;subdir=git"sviluppo (devshell)
In Yocto esiste uno strumento potentissimo
che ci permette di “entrare nell’ambiente di un recipe”
e avere a disposizione solo i comandi e le variabili
che sono effettivamente utilizzabili dal recipe.
Questo diventa molto utile per fare debugging
e capire come implementare il nostro recipe.
Il comando è devshell (development shell)
e in Yocto non è implementato come un comando
a sè stante, ma come un task associato ad ogni recipe.
Se vogliamo entrare in una devshell del recipe busybox
ci basta usare:
bitbake busybox -c devshellA questo punto bitbake avvierà una bash shell
all’interno della directory S
e importerà tutte le variabili di ambiente necessarie.
La shell verrà aperta in un terminale definito
dalla variabile OE_TERMINAL.
Il terminale può essere:
gnomexfcescreen- …
Questo determina l’applicazione grafica che si occupa di disegnare
il terminale, mentre la shell sarà sempre bash.
Se stiamo usando Yocto all’interno di un ambiente non grafico,
come ssh o un container, allora l’opzione consigliata è:
export OE_TERMINAL=screenE’ importante sapere che devshell
è un task che avviene dopo do_patch,
quindi nel ambiente vedremo applicate tutte
le patch definite all’interno del recipe.
Per altri dettagli visitare la documentazione.
creare patch
Utilizzando la devshell su un recipe
possiamo entrare nella cartella S
dopo che vi sono state applicate le patch definite
nel recipe.
Questo ci permette di effettuare
qualsiasi tipo di modifica sulla
repository e poi salvare le modifiche
in un commit.
Il commit verrà poi usato per creare la patch.
Quindi, per creare il commit:
git add .
git commit -m "message"E’ importante sapere che per avere una patch
che sia riconosciuta da Yocto, questa
deve contenere un preciso metadata tag
dentro il messaggio.
Il tag è chiamato Upstream-Status e indica
la relazione tra la patch e upstream source,
con l’obiettivo di rendere più agevole
il lavoro dei manutentori dei layer Yocto.
E’ sufficiente che il messaggio
contenga una stringa del tipo
Upstream-Status: status [reason]Dove status può essere:
AcceptedBackportInappropriateDeniedPendingSubmitted
Mentre reason può essere una stringa qualsiasi.
Una spiegazione più dettagliata è disponibile nella documentazione.
Se sbagliamo a inserire un tag corretto, bitbake genererà un errore durante l’applicazione della patch, come descritto nella documentazione errori QA.
Gli errori QA (Quality Assurance)
sono generati dalla classe insane,
come spiegato nella documentazione.
Ne parleremo più avanti, intanto la soluzione rapida
è disabilitare la generazione errori QA
sul recipe in questione con:
1INSANE_SKIP:${PN} = "patch-status "Tornando alla generazione della patch,
per creare il vero e proprio file .patch:
git format-patch HEAD^questo definisce una patch che replica
le modifiche introdotte dall’ ultimo commit (HEAD^).
Il file .patch è disponibile in S
e dovremo occuparci di copiarlo al di fuori di questa
e dentro la cartella del recipe.