Intro

Himmel, Arsch und Zwirn! Wer kennt nicht diesen Hans-Entertainment-Moment, wenn man eine gute Woche damit verbringt, seinem Rechner beim Compilen zuzugucken, nur um immer wieder zu sehen, wie er abkackt. – Nein. Ich will nicht mehr. Ich kann nicht mehr. Ich halte das alles nicht mehr aus. Irgendwie muss das doch gehen. Eine andere Lösung muss her. Und zwar eine (objektiv gesehen) stümpfere! Denn Stumpf ist Trumpf!

Der Linker ist verwirrt? Ich auch!

Konkret ging es bei mir darum, ein rust-Projekt von einem amd64-Host nach aarch64 zu kompilieren. Was kann da schon schief gehen? So einiges, wenn das besagte Projekt von externen C-Bibliotheken abhängt, die auf dem CISC-Host vorliegen, in der RISC-Toolchain aber nicht. Oder andersherum. Oder wenn CISC-Host Libraries zum Herunterladen von Quellen zum Bauen von RISC-Libraries benötigt werden. Genauer: ein build.rs-Skript, welches mittels dem curl-crate auf openssl (C-API, amd64-Host) aufbaut, um Quellen für das aarch64-Projekt vorzubereiten… 😠

Oder wie auch immer. Solche Probleme gäbe es jedenfalls nicht, wenn besagte C-Bibiliotheken einfach in Rust vorlägen. Deshalb -> Have you considered rewriting it in rust? \s

Zurück in die Realität

Ein informierter rust-Hacker wird wahscheinlich wissen, dass man sich bei solchen cross-compile Komplikationen mittels dem crate cross kurieren kann.1 Und tatsächlich habe ich jenes auch immer regelmäßig verwendet, um meinen Discord-Bot2 zu übersetzen. Nach dem besagten, vom Scheitern gezeichneten Zeitraum habe ich jedoch eine deutlich stümpfere Klinge gewählt:

Ich emuliere einfach ein komplettes aarch64-System, von vorne bis hinten.

Andocken 👉👌

In meinem Fall habe ich mich dafür entschieden, Dockerfiles zu verwenden; Bis dato für mich ziemliches “Neuland”. Hier das Produkt: (Props für die Inspiration hierhin!)

Hier können nach belieben noch weitere Abhängigkeiten hinzugefügt werden; Eben genau diejenigen, welche man beim Compilen auf einem 64-Bit Raspberry verwenden würde.

Aus diesem Dockerfile muss nun ein Image gebaut werden. Zusätzlich wird jedoch QEMU als Emulator benötigt.

-> Quick-Start für Arch:

$ pacman -Syu qemu-system-aarch64 qemu-user-static qemu-user-static-binfmt

Und natürlich Docker selbst, falls noch nicht vorhanden: (Änderungen mittels usermod werden erst nach einem re-login wirksam)

$ pacman -Syu docker
$ usermod -aG docker $USER
$ systemctl enable --now docker.socket

-> Image bauen:

$ docker build -t rpios64 -f Dockerfile .

Wobei rpios64 das “tag”, mehr oder weniger der “Name” des erstellten Images ist, Dockerfile der Pfad zum Dockerfile und der Punkt . am Ende das Build-Directory des Docker-Images festlegt. Wenn man sonst keine externen Dateien in sein Image spielt, ist dieser Verzeichnispfad aber nicht unbedingt weiter interessant.

-> Emulierte cargo-binary aufrufen

$ cd in/das/rust/projekt
$ docker run --volume .:/build rpios64:latest \
  cargo build --manifest-path=/build/Cargo.toml \
              --target-dir=/build/target/rpios64

Enter drücken und beten!

Anmerkungen zu den Argumenten:

  • --volume .:/build: Da der Quellcode im Docker-Image nicht vorhanden ist, wird hiermit das Projektverzeichnis . im Image unter dem absoluten Pfad /build eigehängt.
  • --manifest-path=/build/Cargo.toml: Damit cargo im Image weiß, wo unser eingehängtes Projekt liegt.
  • --target-dir=/build/target/rpios64: Hiermit wird das Ausgabeverzeichnis festgelegt. Besonders nützlich, da bei der Emulation cargo nicht automatisch Ordner für die angegebenen Targets anglegt, wie es sonst der Fall mit cross-rs wäre; Ohne --target-dir würden die Buildartefakte in ./target/debug liegen – also die gleiche Stelle, wo Artefakte von cargo build ohne jegliche Emulation zu finden wären. Save your build-cache people!

Eine Nebenwirkung dieser Methode ist, dass ./target/rpios64 nun root gehört. Warum? Noch nicht rausgefunden. Lässt sich aber einfach lösen:

$ chown -R $USER:$USER target

??? Profit (?)

Eine weitere Nebenwirkung dieser Methode ist, dass das Bauen wirklich ewig dauert. Vorallem wird das im Vergleich zu einer vernünftig konfigurierten cross-toolchain deutlich, die halt dann doch Compiler im nativen Befehlssatz des Hosts zur Verfügung stellt.

Aber hey: ich finde einen erfolgreichen Build, auf den man etwas länger gewartet hat besser, als ein paar schnellere, die fehlschlagen. In der Zwischenzeit kann man ja schließlich einfach mal die Beine hochlegen. 😉

Oder Kaffee kochen. ☕

Oder an die frische Luft gehen?? 🤔