Utiliser Git LFS avec Yocto Kirkstone/Scarthgap

Publié par cpb
Juin 03 2024

La fonctionnalité LFS (Large File Support) permet de superviser avec git des fichiers volumineux en évitant les téléchargements longs. Lors d’une opération git clone, le fichier original est remplacé par un fichier de référence beaucoup plus petit. Le remplacement inverse par le contenu initial n’a lieu que sur demande explicite.

Les anciennes versions de Yocto Project étaient capable d’extraire automatiquement les contenus LFS, mais il semble que cette fonctionnalité ait disparue depuis la branche Kirkstone de Poky. Nous pouvons y remédier assez simple en appelant explicitement git lfs pull dans une recette. Toutefois, l’écriture de la fonction nécessaire n’est pas intuitive et s’avère plutôt intéressante.

Création d’un repository utilisant Git LFS.

Je commence par créer un repository git local, dans lequel je crée un fichier d’un gigagoctet de données aléatoires.

$ mkdir  git-lfs-and-yocto-project

$ cd  git-lfs-and-yocto-project/

$ git  init
Initialized empty Git repository in /home/cpb/git-lfs-and-yocto-project/.git/

$ dd  if=/dev/urandom  of=my-large-file  bs=1G  count=1
1+0 records in
1+0 records out
1073741824 bytes (1,1 GB, 1,0 GiB) copied, 3,35781 s, 320 MB/s

Le package git-lfs est installé au préalable sur ma machine, je vais l’initialiser dans ce répertoire.

$ git  lfs install
Updated git hooks.
Git LFS initialized.

Je demande à git lfs de prendre en charge ce fichier, ce qui va créer un fichier .gitattributes que j’ajoute au suivi de git.

$ git  lfs  track  my-large-file
Tracking "my-large-file"

$ ls  -a
.  ..  .git  .gitattributes  my-large-file

$ cat  .gitattributes 
my-large-file filter=lfs diff=lfs merge=lfs -text

$ git  add  .gitattributes 

Je peux alors ajouter puis commiter le fichier d’un gigaoctet comme habituellement :

$ git  add  my-large-file 

$ git  commit  my-large-file  -m 'Adding a very interesting large file'
[master (root-commit) 3625817] Adding a very interesting large file
 1 file changed, 3 insertions(+)
 create mode 100644 my-large-file

Enfin, je pousse le repository vers un dépôt Github créé pour l’occasion.

$ git  remote  add origin  git@github.com:cpb-/git-lfs-and-yocto-project.git

$ git  push  -u  origin master
Uploading LFS objects: 100% (1/1), 1.1 GB | 9.4 MB/s, done.                                                                                                   
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Delta compression using up to 12 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 353 bytes | 353.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:cpb-/git-lfs-and-yocto-project.git
 * [new branch]      master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.

Pour vérifier que git lfs est bien en oeuvre, je supprime le répertoire et je clone le repository depuis Github.

$ cd  ..

$ rm  -rf  git-lfs-and-yocto-project/

$ git  clone  https://github.com/cpb-/git-lfs-and-yocto-project.git
Cloning into 'git-lfs-and-yocto-project'...
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 3 (delta 0), pack-reused 0
Receiving objects: 100% (3/3), done.

$ cd  git-lfs-and-yocto-project/

$ ls  -l
total 4
-rw-rw-r-- 1 cpb cpb 135 juin   1 08:03 my-large-file

$ cat  my-large-file 
version https://git-lfs.github.com/spec/v1
oid sha256:1c8e5f10aa5e18b3d6aebaee0d5122f8e674057dbd52a8854cad49c929621681
size 1073741824

Pour obtenir le contenu réel de mon fichier, je dois le demander explicitement à git lfs

$ git  lfs  pull
Downloading LFS objects: 100% (1/1), 1.1 GB | 37 MB/s                                                                                                         

$ ls  -l
total 1048580
-rw-rw-r-- 1 cpb cpb 1073741824 juin   1 08:08 my-large-file

Si vous souhaitez faire le test vous-même, l’URL https://github.com/cpb-/git-lfs-and-yocto-project.git est accessible publiquement pendant quelques temps. Néanmoins je ne la conserverai pas indéfiniment car elle consomme inutilement du volume de stockage dans mon espace Github LFS.

Git LFS et Yocto Project

Pour faire mes essais, je vais construire rapidement un environnement de build minimal pour Yocto

$ mkdir  layers

$ cd  layers/

$ git  clone git://git.yoctoproject.org/poky  -b  scarthgap
Cloning into 'poky'...
remote: Enumerating objects: 647916, done.
remote: Total 647916 (delta 0), reused 0 (delta 0), pack-reused 647916
Receiving objects: 100% (647916/647916), 205.39 MiB | 45.10 MiB/s, done.
Resolving deltas: 100% (471176/471176), done.

$ cd  ..

$ mkdir  builds

$ source  layers/poky/oe-init-build-env  builds/build-test

Je crée un layer spécifique pour pouvoir y développer ma recette

$ bitbake-layers  create-layer ../../layers/meta-test
NOTE: Starting bitbake server...
Add your new layer with 'bitbake-layers add-layer ../../layers/meta-test'

$ bitbake-layers  add-layer ../../layers/meta-test
NOTE: Starting bitbake server...

$ mkdir  -p  ../../layers/meta-test/recipes-test/git-lfs-test

Et je crée une recette qui télécharge notre dépot pour copier le fichier sur la cible

$ cat ../../layers/meta-test/recipes-test/git-lfs-test/git-lfs-test.bb

SUMMARY = "Small recipe to test `git lfs` downloading"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"

SRC_URI += "git://github.com/cpb-/git-lfs-and-yocto-project;protocol=https;branch=master"
SRCREV = "${AUTOREV}"
S = "${WORKDIR}"

inherit allarch

do_install() {
        install -d ${D}/data
        install -m 0644 ${S}/my-large-file ${D}/data
}

FILES:${PN} += "data/my-large-file"

Je lance un build pour produire le résultat de cette recette :

$ bitbake  git-lfs-test
Loading cache: 100% |###########################################################| Time: 0:00:00
Loaded 1878 entries from dependency cache.
Parsing recipes: 100% |##########################################################| Time: 0:00:00
Parsing of 923 .bb files complete (922 cached, 1 parsed). 1879 targets, 47 skipped, 0 masked, 0 errors.
NOTE: Resolving any missing task queue dependencies

Build Configuration:
BB_VERSION           = "2.8.0"
BUILD_SYS            = "x86_64-linux"
NATIVELSBSTRING      = "universal"
TARGET_SYS           = "x86_64-poky-linux"
MACHINE              = "qemux86-64"
DISTRO               = "poky"
DISTRO_VERSION       = "5.0.1"
TUNE_FEATURES        = "m64 core2"
TARGET_FPU           = ""
meta                 
meta-poky            
meta-yocto-bsp       = "scarthgap:c5df9c829a549ca002c36afd6bdf23639831502e"
meta-test            = "<unknown>:<unknown>"

Au bout de quelques minutes je peux regarder le contenu du répertoire image de la recette git-lfs-test correspondant au contenu à copier sur la cible.

$ ls  tmp/work/all-poky-linux/git-lfs-test/1.0/image/data/ -l
total 4
-rw-r--r-- 1 cpb cpb 135 juin 1 09:50 my-large-file

Le fichier ne fait que 135 octets, il s’agit juste de la référence LFS, pas du contenu réel du fichier. Pour obtenir ce contenu, je dois appeler git lfs pull dans une fonction de ma recette.

Supposons que ce contenu soit nécessaire pour la phase l’étape do_compile et que nous ne puissions pas nous contenter de l’ajouter au début de do_install. La première idée est généralement de vouloir écrire un do_fetch:append() toutefois cela ne fonctionne pas, l’étape do_fetch() se contente de remplir le répertoire downloads. En effet :

$ bitbake  -c cleanall  git-lfs-test
  [...]
$ bitbake  -c fetch git-lfs-test
  [...]
$ ls  tmp/work/all-poky-linux/git-lfs-test/1.0/
recipe-sysroot  recipe-sysroot-native  temp

$ ls  downloads/git2/
github.com.cpb-.git-lfs-and-yocto-project       
github.com.cpb-.git-lfs-and-yocto-project.done
 [...]

Nous allons plutôt ajouter notre code après le déploiement du dépôt Git dans le répertoire de travail, l’étape do_unpack.

$ bitbake  -c unpack  git-lfs-test
 [...]
$ ls  tmp/work/all-poky-linux/git-lfs-test/1.0/git
my-large-file

Par habitude, j’aurais tendance à ajouter dans ma recette une fonction :

do_unpack:append() {
        git -C ${S} lfs pull
}

Toutefois, cela échoue avec une erreur :

TabError: inconsistent use of tabs and spaces in indentation

ERROR: Parsing halted due to errors, see error messages above

La fonction do_unpack() d’origine est en effet écrite en Python. Notre extension ne peut s’écrire qu’en Python également. Bien sûr on pourrait se débrouiller avec os.system(), mais si le script à exécuter était plus compliqué, nous aurions des difficultés à le traduire intégralement en Python.

Une autre solution est décrire une nouvelle fonction que l’on insère dans la chaîne de traîtements de Bitbake. Cette fonction peut contenir des commandes shell :

do_git_lfs() {
        git -C ${S} lfs pull
}
addtask git_lfs after do_unpack before do_patch

L’erreur est moins compréhensible a priori:

error: failed to fetch some objects from 'https://github.com/cpb-/git-lfs-and-yocto-project.git/info/lfs'

Impossible d’accéder à Github ! En creusant un peu, on s’aperçoit que le code de notre fonction n’a pas accès du tout au réseau. Il existe en effet des flags qui permettent de configurer l’environnement dans lequel la fonction est exécutée (voir le paragraphe Variable Flags dans la documentation de Bitbake pour plus de détails). Il nous faut activer le flag « network » en ajoutant la ligne suivante après la ligne addtask :

do_git_lfs[network] = "1"

Cette fois, enfin nous obtenons le résultat désiré :

$ bitbake git-lfs-test
  [...]

$ ls -l tmp/work/all-poky-linux/git-lfs-test/1.0/image/data/
total 1048580
-rw-r--r-- 1 cpb cpb 1073741824 juin   1 13:32 my-large-file

À noter : il est possible de préciser également dans quel repertoire doit s’exécuter une fonction. Nous pouvons ainsi éviter l’option -C ${S} de git. Au final, ma recette devient :

SUMMARY = "Small recipe to test `git lfs` downloading"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"

SRC_URI += "git://github.com/cpb-/git-lfs-and-yocto-project;protocol=https;branch=master"
SRCREV = "${AUTOREV}"

S = "${WORKDIR}/git"

inherit allarch

do_git_lfs() {
        git lfs pull
}
addtask git_lfs after do_unpack before do_patch
do_git_lfs[network] = "1"
do_git_lfs[dirs] = "${S}"

do_install() {
        install -d ${D}/data
        install -m 0644 ${S}/my-large-file ${D}/data
}

FILES:${PN} += "data/my-large-file"

Conclusion

L’utilisation de repositories LFS de git avec Yocto Project n’est pas très compliquée, il suffit de rajouter les quelques lignes ci-dessus. Néanmoins les étapes ne sont pas très intuitives.

Merci à mon ami Alexandre Grosset pour m’avoir posé ce problème et aidé à le résoudre.

URL de trackback pour cette page