Der ultimative Leitfaden: Raspberry Pi 4 GUI-Programmierung mit Qt 6 in WSL v2

In diesem Artikel geht es um die Einrichtung einer Entwicklungsumgebung zur GUI-Programmierung auf einem Raspberry Pi 4 mit Qt 6 (Qt Widgets und Qt Quick) unter WSL v2.
Der Artikel befindet sich aktuell noch im Aufbau.
1) Einrichtung von Ubuntu 22.04 in WSL v2 unter Windows 11
Um Qt und unsere Qt-Projekte für die ARM64-basierte Linux-Architektur des Raspberry Pi zu kompilieren, benötigen wir eine Cross-Build-Umgebung. Diese besteht hauptsächlich aus einer Toolchain mit Cross-Compiler (aarch64-linux-gnu-) und einem Sysroot, das auf dem Host-System alle benötigten Softwarebibliotheken in einem ARM64-basierten Dateiformat bereitstellt.
Als Grundlage nutzen wir hierfür das Windows Subsystem for Linux (WSL) in der Version 2 unter Windows 11. Das WSL ermöglicht die Nutzung eines Linux-Betriebssystems direkt unter Windows, ohne weitere Tools wie VirtualBox oder VMware. Für unsere Raspberry Pi Build-Umgebung installieren und konfigurieren wir Ubuntu 22.04 in wenigen Schritten.
Schritt 1: Ausführen der Windows PowerShell mit Administratorrechten
Um die PowerShell zu öffnen, drücke die Windows-Taste und gebe anschließend „Powershell“ ein. Wähle in der linken Spalte bei „Höchste Übereinstimmung“ den Eintrag „Windows PowerShell“ aus und klicke in der rechten Spalte auf „Als Administrator ausführen“.
Schritt 2: Installation des WSL v2 Grundsystems unter Windows 11
Um das Basissystem von WSL v2 erstmalig zu installieren, führe in der zuvor geöffneten PowerShell den folgenden Befehl aus:
> wsl.exe --install
PowerShellSchritt 3: Installation von Ubuntu 22.04 in WSL v2
Nachdem WSL erfolgreich eingerichtet wurde, muss nun die Ubuntu 22.02 Distribution installiert werden:
> wsl.exe --install Ubuntu-22.04
PowerShellDer Installationsvorgang von Ubuntu wird automatisiert im Hintergrund durchgeführt und kann einige Minuten dauern. Während des Prozesses wird ein Linux-Benutzer eingerichtet, für den ein Benutzername und ein Passwort festgelegt werden müssen. Diese Daten werden für spätere Vorgänge benötigt.
Nach erfolgreicher Installation sollte die Ausgabe in der PowerShell wie folgt aussehen:
Das Linux-Betriebssystem und damit die Ubuntu Bash wurden automatisch in der Powershell gestartet. Ab jetzt können Linux-Befehle eingegeben werden. Zurück zur Powershell gelangt man mit dem Befehl „exit“. Zurück zu Ubuntu gelangt man mit dem Befehl „wsl.exe“.
Schritt 4: Aktualisierung des Ubuntu 22.04 Betriebssystems und Installation von Abhängigkeiten
Da das von Microsoft bereitgestellte Ubuntu-Installations-Image nicht immer auf dem neuesten Stand ist, aktualisieren wir das System in der Ubuntu Bash mit den folgenden apt-Befehlen:
$ sudo apt update && sudo apt upgrade -y
BashBefehle mit dem Vorwort „sudo“ werden mit Administratorrechten („Root-Rechten“) ausgeführt. Zur Autorisierung der Ausführung mit Administratorrechten wird das zuvor bei Schritt 3 festgelegte Passwort abgefragt.
Je nachdem, wie viele Pakete aktualisiert werden müssen, kann der Aktualisierungsvorgang einige Minuten dauern.
Nach erfolgreichem Abschluss der Systemaktualisierung müssen noch Softwarepakete installiert werden, die für den Kompilierungsprozess von Qt notwendig sind:
$ sudo apt-get install -y make cmake build-essential \
libclang-dev clang ninja-build gcc git bison python3 \
gperf pkg-config libfontconfig1-dev libfreetype6-dev \
libx11-dev libx11-xcb-dev libxext-dev libxfixes-dev \
libxi-dev libxrender-dev libxcb1-dev libxcb-glx0-dev \
libxcb-keysyms1-dev libxcb-image0-dev libxcb-shm0-dev \
libxcb-icccm4-dev libxcb-sync-dev libxcb-xfixes0-dev \
libxcb-shape0-dev libxcb-randr0-dev libxcb-render-util0-dev \
libxcb-util-dev libxcb-xinerama0-dev libxcb-xkb-dev \
libxkbcommon-dev libxkbcommon-x11-dev libatspi2.0-dev \
libgl1-mesa-dev libglu1-mesa-dev freeglut3-dev \
python-is-python3
BashAußerdem muss ein Cross-Compiler für die ARM64/aarch64 Architektur installiert werden. Dieser ermöglicht das Kompilieren von auf dem Raspberry Pi ausführbaren Dateien unter dem Ubuntu Host-PC.
$ sudo apt install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
BashSchritt 5: Einrichtung der Ordner Struktur für die Qt Buildumgebung
Im letzten Schritt legen wir als Vorbereitung noch folgende Ordnerstruktur an. Jede Zeile muss einzeln von oben nach unten in der Ubuntu Kommandozeile ausgeführt werden. Die Ordner werden im weiteren Verlauf des Leitfadens mit Inhalt gefüllt.
$ mkdir qt # Hauptverzeichnis unserer Buildumgebung
$ mkdir qt/sysroot # Softwarebibliotheken des Raspberry Pi
$ mkdir qt/host-build # Buildumgebung für Qt auf dem Host
$ mkdir qt/rpi-build # Buildumgebung für Qt auf dem Raspberry Pi
$ mkdir qt/host # Kompiliertes Qt 6 für Ubuntu Host (ausführbare Binärdateien)
$ mkdir qt/rpi # Cross-kompiliertes Qt 6 für Raspberry Pi (ausführbare Binärdateien)
BashHerzlichen Glückwunsch! Du hast Ubuntu erfolgreich mit WSL v2 eingerichtet und bist dem Ziel der GUI-Programmierung mit Qt auf dem Raspberry Pi ein Stück näher gekommen!
2) Einrichtung des Raspberry Pi 4 als sysroot in Ubuntu
Um Qt Anwendungen unter Ubuntu für den Raspberry Pi zu kompilieren benötigen wir auf dem Host Rechner ein sysroot. Dieses besteht aus den Software Bibliotheken (Libraries) die auf dem Raspi installiert sind. Um möglichst einfach ein funktionsfähiges sysroot zu erstellen, werden wir ein frisches Raspberry Pi Image einrichten und uns die notwendigen Daten von dort in unser zuvor eingerichtetes Ubuntu 22.04 downloaden.
Schritt 1: MicroSD-Karte mit Hilfe des Raspberry Pi Imagers vorbereiten
Lade den Raspberry Pi Imager herunter und installiere ihn. Nach dem Ausführen des Tools muss das korrekte Raspberry Pi Modell („RASPBERRY PI 4“), das korrekte Betriebssystem („RASPBERRY PI OS LITE (64-BIT)“) und der Speicherort gewählt werden. Die korrekten Einstellungen sind im folgenden Screenshot festgehalten:
Nach dem Klicken auf „WEITER“ erscheint ein Dialog, der uns weitere Einstellungen für das Image – z. B. WiFi Setup, SSH Setup, etc. – ermöglicht. Das festgelegte Passwort für den Nutzer „pi“ kann von dir festgelegt werden – wir brauchen dies zu einem späteren Zeitpunkt wieder. Die SSID für das WLAN musst du für dein WLAN anpassen. Die restlichen Einstellungen müssen wie auf den folgenden Bildern eingestellt werden:


Nach dem Drücken auf „Speichern“ wird das Raspberry Pi Minimal OS auf die SD Karte beschrieben. Dieser Prozess dauert einige Zeit.
Nach erfolgreichem Abschluss kann die MicroSD-Karte aus dem Kartenleser gezogen und in den Raspberry Pi gesteckt werden. Die Spannungsversorgung kann jetzt eingesteckt werden.
Schritt 2: SSH Verbindung zum Raspberry Pi herstellen
Nach wenigen Minuten ist das Raspberry Pi hochgefahren und ist im Netzwerk per SSH erreichbar. Falls du noch keinen SSH Client unter Windows installiert hast, ist „PuTTY“ ein seit Jahren beliebtes kostenloses und bewährtes Tool. Zum Download gelangst du hier. Dort muss die Variante MSI 64-bit x86 heruntergeladen und installiert werden.
Nach dem Start von PuTTY muss im Feld „Host Name“ die IP-Adresse bzw. der Hostname des Raspberry Pis eingegeben werden. Falls du eine FRITZ!Box nutzt lautet der korrekte Hostname in unserem Setup „rpiqt.fritz.box„. Bei anderen Routern muss die IP-Adresse am Besten über das Webinterface des Routers ermittelt werden. Die restlichen Einstellungen bleiben unverändert und wir starten den Verbindungsaufbau indem auf die Schaltfläche „Open“ gedrückt wird.
Schritt 3: Raspberry Pi updaten und Abhängigkeiten zum Kompilieren von Qt installieren
Obwohl wir ein frisch eingerichtetes Image mit dem Raspberry Pi Imager installiert haben, bedeutet das nicht zwingend, dass alle Softwarekomponenten auf dem neuesten Stand sind. Um das zu gewährleisten starten wir – ähnlich wie zuvor bei der Einrichtung von Ubuntu – ein Update mittels apt. Da die Aktualisierung Root-Rechte benötigt, wirst du nach Eingabe des Befehls zur Eingabe des zuvor im Raspberry Pi Imager festgelegten Passworts aufgefordert.
$ sudo apt update && sudo apt upgrade -y
BashNachdem der Update Prozess erfolgreich abgeschlossen ist, müssen die Abhängigkeiten zum Kompilieren von Qt installiert werden. Bitte beachte beim Kopieren des nachfolgenden Befehls, dass dieser in einem Stück in PuTTY eingefügt wird.
$ sudo apt install -y libboost-all-dev libudev-dev libinput-dev \
libts-dev libmtdev-dev libjpeg-dev libfontconfig1-dev libssl-dev \
libdbus-1-dev libglib2.0-dev libxkbcommon-dev libegl1-mesa-dev \
libgbm-dev libgles2-mesa-dev mesa-common-dev libasound2-dev \
libpulse-dev gstreamer1.0-omx-generic libgstreamer1.0-dev \
libgstreamer-plugins-base1.0-dev gstreamer1.0-alsa libvpx-dev \
libsrtp2-dev libsnappy-dev libnss3-dev "^libxcb.*" flex bison \
libxslt-dev ruby gperf libbz2-dev libcups2-dev libatkmm-1.6-dev \
libxi6 libxcomposite1 libfreetype6-dev libicu-dev libsqlite3-dev \
libxslt1-dev libavcodec-dev libavformat-dev libswscale-dev libx11-dev \
freetds-dev libsqlite3-dev libpq-dev libiodbc2-dev firebird-dev \
libgst-dev libxext-dev libxcb1 libxcb1-dev libx11-xcb1 libx11-xcb-dev \
libxcb-keysyms1 libxcb-keysyms1-dev libxcb-image0 libxcb-image0-dev \
libxcb-shm0 libxcb-shm0-dev libxcb-icccm4 libxcb-icccm4-dev \
libxcb-sync1 libxcb-sync-dev libxcb-render-util0 libxcb-render-util0-dev \
libxcb-xfixes0-dev libxrender-dev libxcb-shape0-dev libxcb-randr0-dev \
libxcb-glx0-dev libxi-dev libdrm-dev libxcb-xinerama0 libxcb-xinerama0-dev \
libatspi2.0-dev libxcursor-dev libxcomposite-dev libxdamage-dev \
libxss-dev libxtst-dev libpci-dev libcap-dev libxrandr-dev \
libdirectfb-dev libaudio-dev libxkbcommon-x11-dev
BashNach erfolgreichem Abschluss der Installation ist die Einrichtung des Raspberry Pis abgeschlossen und die SSH-Verbindung in PuTTY wird vorerst nicht mehr benötigt.
Schritt 4: Einrichtung des Raspberry Pi sysroots in Ubuntu
Nachdem in Schritt 3 alle Abhängigkeiten zum Kompilieren von Qt 6 für die aarch64 ARM-Architektur des Raspis installiert wurden, müssen diese im letzten Schritt noch auf das Host-System heruntergeladen werden.
Dazu musst du in der Ubuntu Bash folgende Befehle nacheinander ausführen:
$ cd ~/qt/sysroot
$ rsync -avz pi@rpiqt.fritz.box:/lib .
$ rsync -avz pi@rpiqt.fritz.box:/usr/include usr
$ rsync -avz pi@rpiqt.fritz.box:/usr/lib usr
BashDie Bibliotheken sind jetzt in unserem Ubuntu Host und das Raspberry Pi wird vorerst nicht mehr benötigt.
Durch das Herunterladen der Dateien gehen leider symbolische Verknüpfungen kaputt und verweisen ins Leere. Um das Problem zu beheben muss ein Skript heruntergeladen und ausgeführt werden, welches die symbolischen Links wieder repariert:
$ wget https://raw.githubusercontent.com/riscv/riscv-poky/master/scripts/sysroot-relativelinks.py
$ chmod +x sysroot-relativelinks.py
$ ./sysroot-relativelinks.py .
$ cd ..
BashHerzlichen Glückwunsch! Das Raspberry Pi sysroot ist jetzt voll funktionstauglich in deinem Ubuntu Host verfügbar. Als nächstes kann Qt 6 cross-kompiliert werden.
4) Kompilieren von Qt
Um Qt sowohl für den Ubuntu Host als auch für den Raspberry Pi zu kompilieren sind nur noch wenige Schritte notwendig.
Wir müssen den aktuellen Quellcode von Qt mit dem Tool git in unsere Ubuntu Umgebung herunterladen. Dazu sind folgende Befehle auszuführen:
$ cd ~/qt
$ git clone https://github.com/qt/qt5.git src
BashNach dem Download sollte man sich für eine Qt Version entscheiden – dazu am Besten direkt bei Qt nachschauen und sich die dort angegebene Version merken. Während ich das Tutorial schreibe ist dort die Version Qt 6.6.2 aktuell. Mit den nachfolgenden Befehlen bringen wir unseren heruntergeladen Quellcode auf den entsprechenden Release Stand:
$ cd ~/qt/src
$ git switch 6.6.2
BashAls letzten Schritt müssen die Submodules des git Repositories initialisiert werden. Wir wählen hierfür lediglich eine minimale Auswahl an Submodules aus, da der Build Prozess sonst sehr lange dauern würde. Falls Du dennoch alle Submodules kompilieren möchtest, lasse einfach den Zusatz „–module-subset=…“ weg. Falls du weitere Submodules hinzufügen möchtest, findest du hier eine Übersicht.
$ perl init-repository --module-subset=qtbase,qtshadertools,qtdeclarative
BashNach einer Weile ist das Initialisieren der Submodules abgeschlossen und der Quellcode ist für die Kompilierung bereit.
5) Kompilieren von Qt für den Ubuntu Host
$ cd ~/qt/host-build
$ cmake ../src -GNinja -DCMAKE_BUILD_TYPE=Release -DQT_BUILD_EXAMPLES=OFF -DQT_BUILD_TESTS=OFF -DCMAKE_INSTALL_PREFIX=$HOME/qt/host
$ cmake --build . --parallel 8
$ cmake --install .
Bash6) Cross-Kompilieren von Qt 6 für den Raspberry Pi 4
$ cd ~/qt
$ nano toolchain.cmake
BashInhalt von Qt Wiki einfügen.
cmake_minimum_required(VERSION 3.18)
include_guard(GLOBAL)
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(TARGET_SYSROOT $ENV{HOME}/qt/sysroot)
set(CMAKE_SYSROOT ${TARGET_SYSROOT})
set(ENV{PKG_CONFIG_PATH} $PKG_CONFIG_PATH:/usr/lib/aarch64-linux-gnu/pkgconfig)
set(ENV{PKG_CONFIG_LIBDIR} /usr/lib/pkgconfig:/usr/share/pkgconfig/:${TARGET_SYSROOT}/usr/lib/aarch64-linux-gnu/pkgconfig:${TARGET_SYSROOT}/usr/lib/pkgconfig)
set(ENV{PKG_CONFIG_SYSROOT_DIR} ${CMAKE_SYSROOT})
set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I${TARGET_SYSROOT}/usr/include")
set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS}")
set(QT_COMPILER_FLAGS "-march=armv8-a")
set(QT_COMPILER_FLAGS_RELEASE "-O2 -pipe")
set(QT_LINKER_FLAGS "-Wl,-O1 -Wl,--hash-style=gnu -Wl,--as-needed")
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
set(CMAKE_BUILD_RPATH ${TARGET_SYSROOT})
include(CMakeInitializeConfigs)
function(cmake_initialize_per_config_variable _PREFIX _DOCSTRING)
if (_PREFIX MATCHES "CMAKE_(C|CXX|ASM)_FLAGS")
set(CMAKE_${CMAKE_MATCH_1}_FLAGS_INIT "${QT_COMPILER_FLAGS}")
foreach (config DEBUG RELEASE MINSIZEREL RELWITHDEBINFO)
if (DEFINED QT_COMPILER_FLAGS_${config})
set(CMAKE_${CMAKE_MATCH_1}_FLAGS_${config}_INIT "${QT_COMPILER_FLAGS_${config}}")
endif()
endforeach()
endif()
if (_PREFIX MATCHES "CMAKE_(SHARED|MODULE|EXE)_LINKER_FLAGS")
foreach (config SHARED MODULE EXE)
set(CMAKE_${config}_LINKER_FLAGS_INIT "${QT_LINKER_FLAGS}")
endforeach()
endif()
_cmake_initialize_per_config_variable(${ARGV})
endfunction()
set(XCB_PATH_VARIABLE ${TARGET_SYSROOT})
set(GL_INC_DIR ${TARGET_SYSROOT}/usr/include)
set(GL_LIB_DIR ${TARGET_SYSROOT}:${TARGET_SYSROOT}/usr/lib/aarch64-linux-gnu/:${TARGET_SYSROOT}/usr:${TARGET_SYSROOT}/usr/lib)
set(EGL_INCLUDE_DIR ${GL_INC_DIR})
set(EGL_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libEGL.so)
set(OPENGL_INCLUDE_DIR ${GL_INC_DIR})
set(OPENGL_opengl_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libOpenGL.so)
set(GLESv2_INCLUDE_DIR ${GL_INC_DIR})
set(GLESv2_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libGLESv2.so)
set(gbm_INCLUDE_DIR ${GL_INC_DIR})
set(gbm_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libgbm.so)
set(Libdrm_INCLUDE_DIR ${GL_INC_DIR})
set(Libdrm_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libdrm.so)
set(XCB_XCB_INCLUDE_DIR ${GL_INC_DIR})
set(XCB_XCB_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libxcb.so)
CMakeAbschließend mit der Tastenkombination Strg + O speichern, mit Enter bestätigen und mit Strg + X den Editor wieder schließen.
$ cd ~/qt/pi-build
$ cmake ../src/ -GNinja -DCMAKE_BUILD_TYPE=Release -DINPUT_opengl=es2 -DQT_BUILD_EXAMPLES=OFF -DQT_BUILD_TESTS=OFF -DQT_HOST_PATH=$HOME/qt/host -DCMAKE_STAGING_PREFIX=$HOME/qt/rpi -DCMAKE_INSTALL_PREFIX=/usr/local/qt6 -DCMAKE_TOOLCHAIN_FILE=$HOME/qt/toolchain.cmake -DQT_QMAKE_TARGET_MKSPEC=devices/linux-rasp-pi4-aarch64 -DQT_FEATURE_xcb=ON -DFEATURE_xcb_xlib=ON -DQT_FEATURE_xlib=ON
$ cmake --build . --parallel 8
$ cmake --install .
$ # Inhalt von ~/qt/rpi/ nach /usr/local/qt6 auf dem Raspberry Pi kopieren
Bash