4 tecniche per organizzare un Board Support Package Yocto

Chi ha mai lavorato con Yocto avrà certamente avuto la necessità di integrare nel proprio progetto un buon numero di layer di terze parti, necessari per supportare il proprio hardware o integrare determinate feature. Ciascuno di questi layer viene spesso gestito come un singolo repository di codice, usando uno tra i tanti meccanismi di SCM.

Ciò ci pone di fronte ad un problema di organizzazione di progetto non banale: dovendo avere a che fare con un numero spesso considerevole di repository esterni, come possiamo rendere la nostra vita (presente e soprattutto futura) quanto più semplice possibile?

Come vedremo, la risposta a questa domanda è tutt’altro che scontata, specie quando iniziamo a scendere nel dettaglio di cosa si intende per “semplice”. La questione nasconde infatte molteplici sfaccettature, che spaziano da considerazioni prettamente logistiche, ad argomenti più profondi come la manutenibilità del progetto.

Presenteremo quindi quattro papabili approcci per affrontare questo problema, ciascuno con i suoi pro e contro, e soprattutto col proprio bagaglio di conseguenze sul futuro del progetto.

Poiché le soluzioni adottate sono fortemente legate sistema di SCM utilizzato, ed essendo git lo standard di fatto in questo ambito, prenderemo quest’ultimo come nostro riferimento. Inoltre, gli approcci presentati non sono strettamente legati a Yocto, ma possono essere declinati per la gestione di qualsiasi progetto che abbia dipendenze esterne da tracciare, sebbene alcuni dei pro e contro messi in luce dipendano molto dal caso d’uso particolare di Yocto.

Problema? Quale problema?

La prima soluzione al problema è semplicemente quella di ignorare il problema.

Sebbene possa suonare come un controsenso, in termini di rapporto tempo investito/risultati, questo approccio è probabilmente quello che rende meglio nell’immediato, sebbene si trascini dietro tutta una serie di conseguenze da non sottovalutare.

L’approccio consiste essenzialmente nel trattare tutti i layer esterni importati nel nostro progetto come un ammasso di file non versionati, committandoli come tali nel nostro repository. Otterremo così una struttura piatta, in cui non esiste distinzione (a livello di SCM) tra i nostri layer e quelli di terze parti.

Di fatto ciò si traduce in:

my-yocto-project$ git clone --branch zeus git://git.openembedded.org/meta-openembedded
my-yocto-project$ cd meta-openembedded
meta-openembedded$ rm -rf .git
meta-openembedded$ git status
On branch master
Untracked files:
  (use "git add <file>..." to include in what will be committed)
        ./

nothing added to commit but untracked files present (use "git add" to track)
my-yocto-project$ git add .
my-yocto-project$ git commit -m"Import meta-openembedded layer"

Eliminando la cartella .git contenuta nel repo appena clonato, possiamo tranquillamente committare il contenuto di meta-openembedded senza preoccupazione alcuna. Questo ci semplifica la vita sotto parecchi aspetti:

Ovviamente se fosse tutto così facile, non staremmo qui a parlare degli altri tre metodi. Infatti i contro non sono pochi:

In sintesi, questo approccio è estremamente semplice da applicare e mantenere, molto (forse troppo) versatile, ma anche molto fragile in termine di semplicità di sviluppi futuri.

git submodules

Passiamo dunque a quello che è forse l’approccio più diffuso, ossia l’utilizzo dei submodules.

Per chi non lo sapesse, un submodule in Git non è altro che un “puntatore” ad un preciso punto nella storia di un altro repository, ovunque esso sia hostato. Durante l’inizializzazione di un repository contenente submodules, questi devono essere “dereferenziati” con il contenuto del repository nel punto desiderato della sua storia.

Tutte queste informazioni vengono memorizzate in un singolo file di metadati, .gitmodules, contenuto nella root del nostro repository. Il contenuto dei submodules stessi non “contaminerà” mai la storia del nostro repository.

L’uso dei submodules in Git è abbastanza immediato:

my-yocto-project$ git submodule add -b zeus git://git.openembedded.org/meta-openembedded
my-yocto-project$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   .gitmodules
        new file:   meta-openembedded

my-yocto-project$ git commit -m"Add meta-openembedded layer as a submodule"

In termini di pro e contro, questo approccio è pressoché il duale del precedente. Come punti a favore:

Di contro:

Questo approccio è dunque valido nel caso in cui si reputa che il progetto sarà attivamente mantenuto per un tempo molto lungo, o sia comunque tenuto regolarmente sotto backup.

Nel caso di progetti molto brevi, o comunque relativamente statici nel tempo, gli altri approcci sono spesso preferibili.

repo

Nato in origine come parte del progetto Android, repo è uno strumento che affianca Git nella gestione di progetti composti da un numero elevato di repository. Nel tempo, è stato adottato anche al di fuori dell’ambiente Android da vendor come NXP per la gestione di BSP basati su Yocto.

Il funzionamento di repo è molto semplice, e si basa sull’utilizzo di un manifest in formato XML che descrive la struttura del progetto, i repository che lo compongono, dove recuparli, e di quale ref farne il checkout. Dispone inoltre di funzionalità che permettono di fare semplici operazioni di setup in fase di inizializzazione del progetto, come ad esempio copiare file o directory.

Questo è un esempio di manifest usato da NXP:

<?xml version="1.0" encoding="UTF-8"?>
<manifest>

  <default sync-j="4" revision="thud"/>

  <remote fetch="https://git.yoctoproject.org/git" name="yocto"/>
  <remote fetch="https://github.com/Freescale" name="freescale"/>
  <remote fetch="https://github.com/openembedded" name="oe"/>

  <project remote="yocto" revision="thud" name="poky" path="sources/poky"/>
  <project remote="yocto" revision="thud" name="meta-freescale" path="sources/meta-freescale"/>

  <project remote="oe" revision="thud" name="meta-openembedded" path="sources/meta-openembedded"/>

  <project remote="freescale" revision="thud" name="fsl-community-bsp-base" path="sources/base">
    <linkfile dest="README" src="README"/>
    <linkfile dest="setup-environment" src="setup-environment"/>
  </project>

  <project remote="freescale" revision="thud" name="meta-freescale-3rdparty" path="sources/meta-freescale-3rdparty"/>
  <project remote="freescale" revision="thud" name="meta-freescale-distro" path="sources/meta-freescale-distro"/>
  <project remote="freescale" revision="thud" name="Documentation" path="sources/Documentation"/>

</manifest>

Vengono qui definiti tre remote, che vengono poi utilizzati per effettuare il checkout di 7 repository alla revisione thud (ie. Yocto 2.6), tutti posizionati sotto la directory sources/. Sono presenti inoltre due direttive linkfile per creare due symlink da file presenti in sources/base alla root del progetto.

Usare repo è molto semplice. L’installazione si riduce a:

~$ mkdir -p ~/.bin
~$ PATH="${HOME}/.bin:${PATH}"
~$ curl https://storage.googleapis.com/git-repo-downloads/repo > ~/.bin/repo
~$ chmod a+rx ~/.bin/repo

Dopodiché, immaginando di avere un file manifest.xml nella root del nostro repository, per inizializzare il nostro repository basta dare una serie di comandi una tantum:

my-yocto-project$ mkdir -p .repo/manifests
my-yocto-project$ cp manifest.xml .repo/manifests
my-yocto-project$ repo init -u . -m manifest.xml
my-yocto-project$ repo sync

Al termine di repo sync, avremo la nostra struttura di progetto così come definita nel manifest.xml.

In termini di vantaggi e svantaggi, la soluzione è molto simile all’utilizzo dei submodule, con la differenza che la gestione dei moduli è demandata a repo, semplificando in parte la vita dello sviluppatore.

Come bonus, repo implementa anche comandi classici di git quali status e diff in modo che tengona conto della presenza di submodules nel progetto e applichino le relative operazioni anche al contenuto dei submodule stessi, piuttosto che fermarsi al confine con essi – come invece avviene per i corrispondenti comandi in git.

Negli scenari adatti all’utilizzo di git submodulerepo risulta essere un’alternativa valida e semplice da usare.

git subtree

Arriviamo quindi all’ultimo approccio presentato, e che personalmente preferisco: git subtree.

git subtree permette di annidare un repository all’interno di un altro, in modo simile a come avviene usando i submodules, senza tuttavia aggiungere ulteriori metadati (ie. .gitmodules) al repository. Viene bensì creata una copia locale del repository, similmente a quanto visto nel primo approccio.

Utilizzare git subtree è semplice quanto lanciare questo singolo comando:

my-yocto-project$ git subtree add --prefix=meta-openembedded/ --squash git://git.openembedded.org/meta-openembedded zeus

Andiamo ad analizzare questa riga:

La storia risultante da questo tipo di merge è qualcosa del genere:

* 3b4d30004 - (HEAD -> master) Merge commit '5cbefde41f9f99a2c5101fabd8ab8388c99c3a57' as 'meta-openembedded'
|\
| * 5cbefde41 - Squashed 'meta-openembedded/' content from commit bb65c27a7
* 877cd2813 - Another awesome commit
* 18c08af2e - An awesome commit

dove il bb65c27a7 nel commit di squash è l’hash del commit corrispondente al tip del branch zeus di meta-openembedded al momento del fetch.

L’unico vincolo per l’utilizzo di git subtree è che il prefix in cui il subtree verrà clonato sia inizialmente vuoto. Il contenuto del subtree così aggiunto è parte integrante del nostro repository, per cui un successivo clone di quest’ultimo non richiede alcuna operazione aggiuntiva, o addirittura di sapere che subtree è usato nel progetto.

git subtree unisce dunque il meglio dei casi visti sopra:

Ovviamente questo approccio non è esente da svantaggi, in primis la complessità nell’effettuare operazioni di merge e/o rebase che coinvolgano branch interessati dalla presenza di subtree, ma si tratta di eventualità più uniche che rare, che sono fortemente contro-bilanciate dalla mole di vantaggi.

Conclusioni

Come visto, la scelta sulla gestione del progetto dipende molto dalle esigenze, ma a meno di non ricadere in un caso molto particolare che spinga necessariamente verso una particolare direzione, reputo che le caratteristiche di git subtree si sposino benissimo con l’uso che ne richiede un progetto Yocto, motivo per cui è diventato velocemente il mio default nella gestione di questo tipo di progetti.

Per saperne di più su git subtree, Atlassian ha pubblicato tempo fa un ottimo blog post a riguardo; in alternativa, man git-subtree è un’ottima fonte di sapere!