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.