Utiliser Rust avec Buildroot (construction complète)
Dans un article précédent, nous avons vu comment ajouter le support du langage de programmation Rust dans Buildroot, en utiisant des binaires pré-compilés.
Cette fois-ci, nous ajouterons le support pour Rust en construisant un compilateur croisé dans l'environement Buildroot. Nous reprendrons l'exemple précédent, basé sur un système QEMU ARM Versatile Express.
Construction de l'Image Système
Premièrement, récupérez le code source de Buildroot et initialisez la configuration:
# Clonage du dépôt de Buildroot git clone https://git.buildroot.net/buildroot cd buildroot # Configuration pour le système désiré make O=$HOME/build/demo-rust/qemu/arm qemu_arm_vexpress_defconfig
Ensuite, modifiez la configuration:
Allez dans le menu "Toolchain" et sélectionnez "glibc" à la place de "uclibc" en tant que bibliothèque C. Sélectionnez aussi le support du C++. Sauvegardez votre configuration et sortez, puis démarrez la construction:
Construction du Compilateur Rust et de la Bibliothèque Standard
Téléchargement du Code Source
Premièrement, récupérez le code source du compilateur Rust et décompressez-le:
# Récupération du dernier code source pushd dl wget https://static.rust-lang.org/dist/rustc-1.7.0-src.tar.gz popd # Création d'un répertoire de destination mkdir -p $HOME/build/demo-rust/qemu/arm/build/host-rust-1.7.0 # Extraction du code source tar -xzf dl/rustc-1.7.0-src.tar.gz \ -C $HOME/build/demo-rust/qemu/arm/build/host-rust-1.7.0 \ --strip-components=1
Configuration de la Compilation Croisée
En interne, le compilateur Rust utilise LLVM en tant que moteur. LLVM s'attend à ce que le nom de la chaîne de compilation corresponde à un triplet au format GNU: cpu-vendeur-noyau. Comme expliqué dans la documentation Autotools:
Currently configuration names are permitted to have four parts on systems which distinguish the kernel and the operating system, such as GNU/Linux. In these cases, the configuration name is cpu-manufacturer-kernel-operating_system.
Mais, comme le champ "vendeur" a généralement la valeur "unknown" ("inconnu"), les outils GNU autorisent son omission, ce qui entraîne le résultat ambigü "x86_64-linux-gnu" rapporté par gcc -dumpmachine sur un système Debian Jessie (à la place de "x86_64-unknown-linux-gnu"). Sur Fedora, le résultat est "x86_64-redhat-linux" (cette fois, pas de "système").
Afin que le système de construction prenne en compte le compilateur croisé généré par Buildroot (qui n'a pas de nom ambigü), ainsi que la machine cible, de nouveaux fichiers doivent être ajoutés.
Pour cela, allez dès maintenant dans le répertoire des sources de Rust:
Déclaration de la Cible
Le premier fichier requis est un morceau de Makefile,
mk/cfg/arm-buildroot-linux-gnueabihf.mk
, qui déclare la nouvelle cible. Ce
fichier peut facilement être créé en copiant un des fichiers par défaut, qui
corresponde à peu près à la nouvelle cible.
Spécification de la Cible
Ensuite, le système de construction doit connaitre l'architecture de la nouvelle cible (type de CPU, etc): c'est la spécification de la cible. Il y a deux méthodes pour y parvenir.
La première méthode consisterait à ajouter un nouveau fichier source Rust, basé sur un existant, afin de donner les détails de la cible:
sed -e 's/unknown/buildroot/g' \ src/librustc_back/target/arm_unknown_linux_gnueabihf.rs \ > src/librustc_back/target/arm_buildroot_linux_gnueabihf.rs
Le fichier généré ressemble à ceci:
pub fn target() -> Target { let base = super::linux_base::opts(); Target { llvm_target: "arm-buildroot-linux-gnueabihf".to_string(), target_endian: "little".to_string(), target_pointer_width: "32".to_string(), arch: "arm".to_string(), target_os: "linux".to_string(), target_env: "gnueabihf".to_string(), target_vendor: "buildroot".to_string(), options: TargetOptions { features: "+v6,+vfp2".to_string(), .. base } } }
Pour compiler le nouveau module, le patch suivant devrait être appliqué:
Index: host-rust-1.7.0/src/librustc_back/target/mod.rs =================================================================== --- host-rust-1.7.0.orig/src/librustc_back/target/mod.rs +++ host-rust-1.7.0/src/librustc_back/target/mod.rs @@ -417,6 +417,7 @@ impl Target { powerpc64le_unknown_linux_gnu, arm_unknown_linux_gnueabi, arm_unknown_linux_gnueabihf, + arm_buildroot_linux_gnueabihf, aarch64_unknown_linux_gnu, x86_64_unknown_linux_musl,
Cette méthode ne serait pas très pratique pour Buildroot, car il serait nécessaire de fournir et d'appliquer un ensemble de patchs qui déclarent toutes les cibles supportées.
La seconde méthode est meilleure. Au lieu d'ajouter un nouveau module Rust, il est possible de fournir un fichier JSON qui contient les mêmes informations, comme il est expliqué dans la documentation non-officielle de la gestion des cibles par rustc et RFC 0131.
Comme on peut le voir dans src/librustc_back/target/mod.rs
, le fichier JSON
peut être stocké dans un répertoire figurant dans la liste de valeurs, séparées
par des virgules, déclarée par la variable d'environement RUST_TARGET_PATH
.
La destination choisie est $HOME/build/demo-rust/qemu/arm/host/etc/rustc
.
Pour créer le fichier JSON de spécification de la nouvelle cible, exécutez:
mkdir -p $HOME/build/demo-rust/qemu/arm/host/etc/rustc cat <<EOF > $HOME/build/demo-rust/qemu/arm/host/etc/rustc/arm-buildroot-linux-gnueabihf.json { "llvm-target": "arm-buildroot-linux-gnueabihf", "target-endian": "little", "target-pointer-width": "32", "target-env": "gnueabihf", "target-vendor": "buildroot", "arch": "arm", "os": "linux", "features": "+v6,+vfp2", "dynamic-linking": true, "executables": true, "morestack": true, "linker-is-gnu": true, "has-rpath": true, "pre-link-args": [ "-Wl,--as-needed" ], "position-independent-executables": true, "archive-format": "gnu" } EOF
Maintentant, configurez et lancer la construction:
export PATH=$HOME/build/demo-rust/qemu/arm/host/usr/bin:$PATH export RUST_TARGET_PATH=$HOME/build/demo-rust/qemu/arm/host/etc/rustc ./configure --prefix=$HOME/build/demo-rust/qemu/arm/host/usr \ --localstatedir=$HOME/build/demo-rust/qemu/arm/host/var/lib \ --sysconfdir=$HOME/build/demo-rust/qemu/arm/host/etc \ --target=arm-buildroot-linux-gnueabihf make -j8 VERBOSE=1 make install popd
La construction prend un certain temps, car LLVM est compilé avec le support de toutes les architectures, et rustc n'est pas très rapide à se compiler lui-même. Quand tout est fini, vérifiez que l'installation est OK:
$ ls -l $HOME/build/demo-rust/qemu/arm/host/usr/bin rust-gdb rustc rustdoc $ $HOME/build/demo-rust/qemu/arm/host/bin/rustc --version rustc 1.7.0-dev $ ls -1 $HOME/build/demo-rust/qemu/arm/host/usr/lib/rustlib arm-buildroot-linux-gnueabihf components etc install.log manifest-rust-docs manifest-rust-std-arm-buildroot-linux-gnueabihf manifest-rust-std-x86_64-unknown-linux-gnu manifest-rustc rust-installer-version uninstall.sh x86_64-unknown-linux-gnu
Comme on peut le voir, la bibliothèque standard est disponible pour les architectures ARM and x86_64.
Construction d'un Programme de Test
Il est temps de tester le compilateur. Créez un fichier source Rust pour le programme "Hello World":
mkdir -p $HOME/src/hello-rust cat <<EOF > $HOME/src/hello-rust/main.rs fn main() { println!("Hello World!"); } EOF
Pour construire le programme de test hello-rust, exécutez:
Exécution du Programme de Test sur le Système
Reconstruisez l'image système:
Maintenant, vous pouvez démarrer votre système avec QEMU:
qemu-system-arm \ -M vexpress-a9 \ -m 256 \ -kernel $HOME/build/demo-rust/qemu/arm/images/zImage \ -dtb $HOME/build/demo-rust/qemu/arm/images/vexpress-v2p-ca9.dtb \ -drive file=$HOME/build/demo-rust/qemu/arm/images/rootfs.ext2,if=sd,format=raw \ -append "console=ttyAMA0,115200 root=/dev/mmcblk0" \ -serial stdio \ -net nic,model=lan9118 \ -net user
Connectez-vous en tant que "root" (pas de mot de passe) et exécutez le programme de test:
Parfait! Une fois de plus, vous venez d'exécuter un programme en Rust sur un système Linux embarqué (emulé), mais cette fois-ci en construisant tout de bout-en-bout.