Fortschrittliche Entwicklung leicht gemacht

Rust in Embedded Linux und Yocto

Rust hat sich längst im Mainstream etabliert und ist inzwischen auch ein fixer Bestandteil von Embedded-Linux-Systemen. Dank der Integration in Yocto seit der Version Honister sind grundlegende Toolchains direkt verfügbar. Doch was tun, wenn eine aktuellere Rust-Version benötigt wird? Dieser Artikel präsentiert effektive Strategien zur Einbindung moderner Rust-Anwendungen in Yocto-Builds.

Rust ist bei Desktop- und Cloudanwendungen im Mainstream angekommen. Das heisst, dass Komponenten wie Compiler, Build-Tools, etc. einfach installiert werden können und dass die Integration ins gewünschte Betriebssystem ohne Schwierigkeiten funktioniert. Rust ist auch Teil des Linux-Kernels geworden und erlaubt damit die Entwicklung von Kernel-Modulen in Rust.

Wie steht es aber um den Einsatz von Rust auf Embedded-Linux-Plattformen und -Distributionen, die beispielsweise mit Yocto erstellt werden?

Wie kriege ich Rust in meinen Yocto-Build?

Rust ist schon da!

Vermutlich ist Rust und die nötige Toolchain bereits in Ihrem Yocto-Build vorhanden. Denn seit Yocto Honister ist Rust Teil von openembedded-core. Damit braucht man keine zusätzlichen Layer zu verwenden. Alle Abhängigkeiten und Rezepte sind bereits in poky/meta vorhanden.

Rust VersionYocto Version (Auswahl)
1.84.1Walnascar
1.75Scarthgap
1.59Kirkstone
1.54Honister

Ich möchte Rust in einer neueren Version verwenden

Wenn man darauf angewiesen ist, sowohl eine ältere Yocto-Version als auch eine neue Rust-Version zu verwenden, zum Beispiel weil man die Edition 2024 von Rust braucht, können spezielle Yocto-Layer verwendet werden.

LTS Mixins

Mit dem Layer lts-rust-mixin kann eine jeweils aktuelle Rust-Version verwendet werden. In diesem Layer gibt es für jeweils ein paar ältere Yocto-Versionen Backports von aktuellen Rust-Versionen. Der Layer kann wie üblich zu BBLAYERS in bblayers.conf hinzugefügt werden, mit dem passenden Commit ausgecheckt.

Rust Binary

Der Layer meta-rust-bin verfolgt auch das Ziel eines Backports von modernem Rust zu älteren Yocto-Versionen. Im Gegensatz zum üblichen Ansatz in Yocto alle Tools auf dem Host zu kompilieren, bringt dieser Layer direkt Binaries mit – mit allen Vor- und Nachteilen von vorkompilierten Tools. Das Hinzufügen des Layers in bblayers.conf funktioniert wie oben. In Rezepten muss man darauf achten nicht inherit cargo, sondern inherit cargo_bin zu verwenden, um klar zu spezifizieren, welche Variante man verwenden möchte.

Meta Rust

Der historische Layer meta-rust muss nicht (und sollte auch nicht) in einer normalen Yocto-Distribution verwendet werden. Der Code darin ist in anderen Layers bereits enthalten.

Rust Prgramming Language Quiz

Bereit für die Rust-Challenge?

Rust Programming Language Quiz

Stellen Sie Ihr Wissen auf die Probe! Machen Sie unser Quiz für Softwareentwickler und finden Sie heraus, wie fit Sie in Rust sind.

Was muss ich unternehmen, um eine Rust-Applikation in mein System zu integrieren?

Die Integration einer Rust-Applikation in ein System ist nicht fundamental anders als die Integration einer «C/C++»-Applikation. Um ein sinnvolles Szenario zu beschreiben, werden ein paar Annahmen getroffen:

Projekt-Setup

In bblayers.conf muss der lts-rust-mixin-Layer hinzugefügt werden.

In local.conf, oder besser in einem eigenen distro.conf, wird Rust aktiviert und die Version festgelegt. Eine Angabe der Rust-Version ist nicht zwingend nötig, aber hilfreich, da es eine Fehlermeldung gibt, wenn der lts-rust-mixin-Layer falsch konfiguriert ist.

DISTRO_FEATURES:append = " rust"
RUST_VERSION = "1.85.1"
CARGO_VERSION = "1.85.1"

Yocto-Rezept

Das Rezept ist unabhängig davon, wie Rust für dieses System aktiviert wird. Hier ist ein minimales, aber komplettes Rezept, wie die Applikation kompiliert und auf dem Zielsystem installiert wird.

(1)
SUMMARY = "Simple Webserver"
DESCRIPTION = "A simple webserver in async rust"
HOMEPAGE = "https://github.com/<user>/simple_srv_rs"
LICENSE = "MIT"
LIC_FILES_CHKSUM = file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302

(2)
TAG = "0.1.6"
SRC_URI = "git://github.com/<user>/simple_srv_rs.git;branch=async_server;tag=v${TAG};protocol=https"
S = "${WORKDIR}/git"

(3)
TARGET_BIN_NAME= "webserver-rs" # Must match the project name in Cargo.toml

(4)
inherit cargo
do_fetch[network] = "1"
do_compile[network] = "1"
CARGO_BUILD_FLAGS:remove = "--frozen"
CARGO_DISABLE_BITBAKE_VENDORING = "1"

(5)
do_install() {
    install -d ${D}${bindir}
    install -m 0755 ${B}/target/${RUST_TARGET_SYS}/release/${TARGET_BIN_NAME} ${D}${bindir}/${TARGET_BIN_NAME}
    # --- other install tasks ---
}
FILES:${PN} += "${bindir}/${TARGET_BIN_NAME}"
  1. Header mit den nötigen Namen und der Lizenz mit dem passenden Hash.
  2. Der gewünschte git-Tag vom gewünschten Branch wird verwendet.
  3. Der Name der Applikation wird von Bitbake nur unzulänglich erraten. Die Variable wird verwendet, um die Applikation zu installieren.
  4. Ableiten von der cargo-Klasse, damit die folgenden Befehle und Variablen bekannt sind. Die rust-Klasse braucht man in der Regel nicht direkt.
    Netzwerkzugang wird benötigt, um Crates herunterzuladen.
    CARGO_BUILD_FLAGS:remove = "—frozen"
    Das Flag —frozen bedeutet —locked und —offline zusammen. Wenn im Rezept —frozen entfernt wird, hat Cargo Zugriff auf das Web, um Crates herunterzuladen. Für den Build in einer CI, oder aus anderen Gründen, kann man den Download Bitbake überlassen. Mehr dazu später.
    Ausgeschaltetes Vendoring bedeutet, dass alle Dependencies heruntergeladen müssen.
  5. Die Applikation wird auf dem Zielsystem installiert. RUST_TARGET_SYS wird aus dem Rust-Target-Triple hergeleitet, also etwas wie armv7-unknown-linux-gnueabihf. Per Default wird ein release-Build erstellt.
Icon zu Rust Transition Service

Zukunftssicher bleiben

Rust Transition Service

Der regulatorische Druck, Software sicherer, effizienter und wirtschaftlicher zu gestalten wird immer grösser – Rust nimmt Ihnen diesen Druck.

Dependencies von Yocto verwaltet

Oft möchte man, dass die Crates von Yocto verwaltet werden. Zum Beispiel, wenn in der CI/CD-Pipeline alle Abhängigkeiten cached werden sollen. Damit dies möglich ist, müssen die Abhängigkeiten im Rezept konkret angegeben werden in der Form:

SRC_URI += " \
    crate://crates.io/addr2line/0.24.2 \
"
SRC_URI[addr2line-0.24.2.sha256sum] = "dfb..1c1"

Eine nicht-triviale Applikation hat schon mehrere Dutzend Abhängigkeiten. Damit das Rezept lesbar bleibt und, um Automatisierung zu ermöglichen, drängt es sich auf, diese Abhängigkeiten in ein <rezept>-crates.inc File zu packen.

Es gibt verschiedene Varianten, um die Abhängigkeiten zusammen mit ihrer Checksumme in diesem File aufzulisten.

Hier das Rezept von vorhin, das aber diesen Ansatz verfolgt:

SUMMARY = "Simple Webserver"  
DESCRIPTION = "A simple webserver in async rust"  
HOMEPAGE = "https://github.com/<user>/simple_srv_rs"  
LICENSE = "MIT"  
LIC_FILES_CHKSUM = file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302  
 
(1)
inherit cargo
inherit cargo-update-recipe-crates
(2)
require ${BPN}-crates.inc

TAG = "0.1.6"  
SRC_URI = "git://github.com/<user>/simple_srv_rs.git;branch=async_server;tag=v${TAG};protocol=https"  
S = "${WORKDIR}/git"  

TARGET_BIN_NAME = "webserver-rs"
(3)
# Keep network access during fetch so that the initial download is possible
do_fetch[network] = "1"
# Remove network access during build since we have all dependencies
do_compile[network] = "0"

do_install() {  
    install -d ${D}${bindir}  
    install -m 0755 ${B}/target/${RUST_TARGET_SYS}/release/${TARGET_BIN_NAME} ${D}${bindir}/${TARGET_BIN_NAME}  
    # --- other install tasks ---  
}  
FILES:${PN} += "${bindir}/${TARGET_BIN_NAME}"
  1. inherit cargo-update-recipe-crates wird benötigt, um das bitbake -c update_crates <rezept> Kommando auszuführen.
  2. Das File mit den Abhängigkeiten wird verwendet
  3. Während dem Kompilieren wird der Netzwerkzugang nicht mehr benötigt. Um die Crates initial zu erhalten, braucht es aber den Netzwerkzugang beim Fetch.
    CARGO_DISABLE_BITBAKE_VENDORING ist per default auf false.

Fazit

Rust ist auch auf Embedded-Linux-Systemen angekommen. Mit dem beschriebenen Vorgehen kann man eine einfache, aber komplette Applikation mit Yocto in ein Linux-System einbinden. Die langen Abhängigkeitslisten muss man Pflegen. Die richtigen Tools nehmen einem aber die manuelle Arbeit ab.

Portrait Oliver With
Der Experte

Oliver With

Oliver With ist Spezialist für Embedded Software. Als Senior-Entwickler ist er überzeugt, dass eng zusammenarbeitende Teams komplexe Probleme am besten lösen. Kreativität in der Lösungsfindung vereint er mit Qualität in der Entwicklung, um erfolgreiche Produkte zu schaffen. Er ist Rust-Enthusiast, weil es mit Rust erstmals eine Sprache gibt, die Sicherheit, Performance, Akzeptanz in der Industrie und Ergonomie für Entwickler kombiniert.

Unser Wissen im Abo

Attention!

Sorry, so far we got only content in English for this section.