Bitbake: meccanismi

Come abbiamo già anticipato, bitbake definisce i seguenti formati di file:

  • .bb
  • .bbappend
  • .bbclass
  • .conf
  • .inc

I file di tipo .bb* e .inc possono contenere variabili e funzioni bitbake, mentre i .conf possono contenere solo variabili bitbake.

Uno dei principi su cui si basa bitbake è il riutilizzo della logica e per farlo implementa i seguenti meccanismi:

  • include di file .inc
  • inherit di file .bbclass

Per farla breve, include rappresenta una “inclusione piatta” come accade in linguaggio C, mentre inherit è più complesso e implementa delle logiche di ereditarietà. Una buona prassi è quella di utilizzare include per file che contengono dati in comune e inherit per logiche (funzioni, override, …) che possono essere riutilizzate.

Un singolo recipe può essere quindi realizzato a partire da un file .bb in cui vengono inclusi file .inc e anche ereditate delle .bbclass. In aggiunta a questo esistono anche i .bbappend, ovvero delle porzioni di recipe che vanno ad aggiungersi al .bb di partenza. Questi permettono di andare a modificare un qualsiasi .bb senza dover modificare il file originale.

Può sembrare un meccanismo contorto, ma permette ad altri sviluppatori di apportare modifiche puntuali su un recipe senza farsi carico di mantenere un fork del recipe originale. Nel caso il .bb venga aggiornato, è molto probabile che il .bbappend sia ancora utilizzabile.

Questo meccanismo nasce dall’esigenza di permettere a layer differenti di poter apportare modifiche alla generazione di un package, in quanto questo potrebbe necessitare di configurazioni diverse in base all’ hardware effetivamente presente nella scheda scelta o in base a diverse finalità di uso. Le modifiche possono riguardare qualsiasi fase di build del recipe, quindi sia a livello di compilazione, sia a livello di installazione di ulteriori dipendenze, che tutto il resto.

include / require

Per riutilizzare un qualsiasi tipo di logica, evitando di fare “copia-incolla”, è possibile utilizzare le istruzioni include o require. Permettono di “inglobare” un intero file in modo molto simile al #include in linguaggio C. L’unica differenza tra i due è che require genera un errore se non trova il file specificato, mentre include no.

Un esempio di utilizzo nel layer meta:

meta
meta
├── classes-global
├── classes-recipe
├── classes
├── conf
│    ├── distro
│    │    ├── include
│    │    │    ├── default-providers.inc
│    │    │    ├── default-versions.inc
│    │    │    ├── ...
│    │    │    ├── time64.inc
│    │    │    ├── tclibc-baremetal.inc
│    │    │    ├── tclibc-glibc.inc
│    │    │    └── tclibc-musl.inc
│    │    └── defaultsetup.conf
│    │
│    └── layer.conf
├── recipes-bsp
└── ...
meta/conf/distro/defaultsetup.conf
 1include conf/distro/include/default-providers.inc
 2include conf/distro/include/default-versions.inc
 3include conf/distro/include/default-distrovars.inc
 4include conf/distro/include/maintainers.inc
 5include conf/distro/include/time64.inc
 6require conf/distro/include/tcmode-${TCMODE}.inc
 7require conf/distro/include/tclibc-${TCLIBC}.inc
 8
 9require conf/distro/include/uninative-flags.inc
10
11# Allow single libc distros to disable this code
12TCLIBCAPPEND ?= "-${TCLIBC}"
13TMPDIR .= "${TCLIBCAPPEND}"

Osserviamo che i path sono definiti a partire dalla directory del layer, questo perchè, come vedremo, all’ interno di un layer possiamo individuare i file tramite dei percorsi relativi.

inherit class

Il layer meta fornisce già una quantità enorme di .bbclass che spaziano da automazioni per generare file di log relativi alla build del progetto, fino ad automazioni per gestire la compilazione di recipe tramite cmake. Nella documentazione ufficiale troviamo qualche esempio tali classi:

  • buildstats
  • cargo
  • cmake
  • image-container
  • kernel
  • toolchain-scripts
  • update-rc.d

Se il nostro recipe si occupa di costruire un applicativo basato su cmake, tramite un inherit cmake possiamo quindi evitare di dover implementare i task bitbake do_configure, do_compile e do_install in quanto cmake.bbclass si occupa già di implementare tale logica. Come comportamento di default, cmake.bbclass si occupa di lanciare cmake all compilando il progetto presente nella directory S dentro la directory di build B ed infine eseguire cmake install posizionando i file di output dentro la directory fakeroot D. Inoltre viene automaticamente impostata la dipendenza del recipe rispetto il recipe cmake-native.

Tramite le variabili OECMAKE_TARGET_INSTALL OECMAKE_TARGET_COMPILE OECMAKE_GENERATOR EXTRA_OECMAKE possiamo modificare il processo.

bbappend

Un file .bbappend non può esistere da solo all’interno del progetto, ma deve avere almeno un relativo file .bb. Non è necessario che questi si trovino nel medesimo layer. L’unico vincolo è che abbiano lo stesso nome e versione del recipe. Ricordiamo che un recipe è identificato su disco tramite un file del tipo:

busybox_1.36.1.bb
busybox    1.36.1
   ↑         ↑
 nome      versione  

Quindi se vogliamo creare un append a questo recipe dobbiamo utilizzare un file del tipo:

busybox_1.36.1.bbappend

bitbake permette di utilizzare il carattere % come wildcard per la versione del recipe, quindi è valido anche:

busybox_%.bbappend

invece non è valido il semplice:

busybox.bbappend    [NON VALIDO]

Ogni .bbappend è valutato appunto come una aggiunta al .bb originale e ai .bbappend precedenti. L’ordine dipende dal parsing dei recipe e dall’ordine di priorità dei layer.

conclusioni

.inc e .bbclass servono a risparmiare tempo nella scrittura di un recipe, mentre .bbappend serve per dare flessibilità nella configurazione.

Ok, se ti sembra tutto molto confuso, allora aspetta di arrivare alla fine e poi ne parliamo. Per il momento abbiamo solo planato sopra ai concetti, ma più avanti dovremo capire come utilizzarli effettivamente. Cosa? Quali menti malate si sono inventate tutto questo delirio? Per il momento non pensarci, un giorno ti sarà tutto più chiaro.