Du liest meine Anleitung, wie ich Qt 5.15 samt QtWebEngine für einen Raspberry Pi 3 cross-kompiliert habe, so, dass die Standard-Grafikbibliotheken von Broadcom genutzt werden, die bei RaspiOS dabei sind. Die ganze Situation mit den Grafikbibliotheken ist ziemlich kompliziert, und ich versuche, sie im Anschluss zu erklären, aber hier erstmal die Anleitung:
Qt Build vorbereiten
Auf dem Raspberry Pi
- Den Pi updaten.
rpi-update
ist nicht nötig! - Abhängigkeiten intallieren
- Ein Verzeichnis für Qt anlegen
sudo apt update sudo apt full-upgrade sudo apt install libfontconfig1-dev libdbus-1-dev libnss3-dev libxkbcommon-dev libjpeg-dev libasound2-dev libudev-dev libgles2-mesa-dev sudo mkdir /usr/local/qt5pi sudo chown -R pi:pi /usr/local/qt5pi
Die Abhängigkeiten sind:
fontconfig | stellt Schriftarten für Qt bereit |
dbus | Inter-Prozess-Kommunikation |
nss | SSL und so |
xkbcommon | Tastatur-Handling |
jpeg | jpeg halt… |
asound | ALSA. Wird nur benötigt, wenn Sound erwünscht ist. |
udev | Unterstützung für Maus-/Tastatur-Hotplugging |
gles2-mesa | Die Mesa-Version der Grafiktreiber, mitsamt Headern. Mehr dazu später. |
Auf dem Host-Rechner
- Abhängigkeiten installieren
sudo apt-get install g++-multilib python pkg-config gperf bison flex libnss3-dev
- Cross-Kompilierungs-Toolchain installieren
sudo mkdir /opt/raspiToolchains sudo chown 1000:1000 /opt/raspiToolchains cd /opt/raspiToolchains wget https://releases.linaro.org/components/toolchain/binaries/7.5-2019.12/arm-linux-gnueabihf/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz tar xf gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz rm gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz
https://github.com/raspberrypi/tools ist total veraltet und verwendet noch gcc 4.8, also verwende lieber eine von Linaro. Weil C++ recht strikt ist, was Abwärtskompatibilität angeht, kannst du einfach die neuste Version nehmen und alles sollte funktionieren.
- Ein Build-Verzeichnis anlegen
sudo mkdir /opt/qt5pi sudo chown 1000:1000 /opt/qt5pi cd /opt/qt5pi
- Ein Tool für Symlinks runterladen
wget https://raw.githubusercontent.com/riscv/riscv-poky/master/scripts/sysroot-relativelinks.py chmod +x sysroot-relativelinks.py
Das ist einfach ein nützliches kleines Skript von jemandem auf Github, das wir später benutzen werden, um absolute Verknüpfungen zu relativen zu machen.
- Den Qt-Quellcode runterladen
git clone https://github.com/qt/qt5.git cd qt5 git checkout 5.15 ./init-repository --module-subset=essential,qtwebengine
Ich nehme lieber das git-Repo als den qt-everywhere-Download, weil es modularer ist und auch einfacher aktuell zu halten. Wenn ich einen branch auschecke, nehme ich lieber 5.15 als z.B. 5.15.0, weil 5.15 schneller aktualisiert wird und z.B. Sicherheitspatches für Chromium früher bekommt.
init-repository
ist ein kleines Tool von Qt, um den Quellcode der angegebenen Module zu fetchen. Das “super-repo” selbst ist winzig, und alles weitere wird in git submodules verwaltet. Ich gebe hier an, dass ich alle essentiellen Module und QtWebEngine runterladen möchte. Welche Module es gibt steht hier, und nähere Infos zu dem Tool gibt’s mit init-repository --help
oder hier.
Qt Build (bzw. Update)
Diese Schritte als nächstes ausführen, oder auch jedes mal, wenn du Qt aktualisieren möchtest.
Auf dem Host-Rechner
- Den (aktualisierten) sysroot vom Pi holen und Verknüpfungen relativ machen
cd /opt/qt5pi/ rsync -avzR pi@192.168.1.101:/lib :/usr/include :/usr/lib :/usr/share/pkgconfig/ :/opt/vc sysroot ./sysroot-relativelinks.py sysroot
Das kopiert die angegebenen Verzeichnisse von deinem Pi über’s Netzwerk in ein Verzeichnis “sysroot”. Darin enthalten sind Bibliotheken, Header und pkg-config-Dateien, die wir später brauchen werden.
- Qt-Quellcode aktualisieren (falls nicht gerade erst runtergeladen)
cd /opt/qt5pi/qt5 (a) Ich möchte im gleichen branch (5.15) bleiben und neuere Änderungen pullen: git pull git submodule update --recursive (b) Ich möchte zu einem anderen branch wechseln: git fetch git checkout 5.15 git submodule update --recursive (c) Normalerweise ist explizit ein commit von QtWebEngine im super-Repository ausgewählt. Wenn ich einen neueren haben möchte, muss ich einen branch von qtwebengine auschecken: git fetch cd qtwebengine git checkout 5.15 git submodule update (d) Wenn ich bereits einen branch ausgecheckt habe, für den es neuere commits gibt: cd qtwebengine git pull git submodule update
- Qt Essentials konfigurieren
cd /opt/qt5pi/qt5/qtbase/mkspecs/devices cp -r linux-rasp-pi3-g++/ linux-rasp-pi3-brcm-g++/ nano linux-rasp-pi3-brcm-g++/qmake.conf
qmake.conf muss dazu angepasst werden. Diese Datei ist eine veränderte Version derer, die Qt mitbringt, und enthält einige Einstellungen zum kompilieren. So sollte sie aussehen:
# qmake configuration for the Raspberry Pi 3 using the Broadcom graphics stack # MODIFIED to use the library names that are shipped since 2016 # # Also setting the linker flags according to the pkg-config files that are shipped with the libraries. # Remove egl.pc and glesv2.pc from /usr/lib/arm-linux-gnueabihf/pkgconfig/ for these to take effect. # The reason for using static values instead of supplying the correct pkg-config files is that configure ignores -pthread from there # # Including headers from /opt/vc first, but also from mesa version of EGL and GLES, since the /opt/vc headers are from 2009 and don't work with qtwebengine. # This way you can make mesa headers available by manually removing directories EGL, GLES and GLES2 from /opt/vc/include before building. include(../common/linux_device_pre.conf) # Let the linker know about /opt/vc/lib. Required for EGL config.test (at least) because brcmGLESv2 also needs brcmEGL. QMAKE_RPATHLINKDIR_POST += $$[QT_SYSROOT]/opt/vc/lib VC_LIBRARY_PATH = $$[QT_SYSROOT]/opt/vc/lib VC_INCLUDE_PATH = $$[QT_SYSROOT]/opt/vc/include MESA_INCLUDE_PATH = $$[QT_SYSROOT]/usr/include QMAKE_LIBDIR_OPENGL_ES2 = $${VC_LIBRARY_PATH} QMAKE_LIBDIR_EGL = $$QMAKE_LIBDIR_OPENGL_ES2 QMAKE_LIBDIR_OPENVG = $$QMAKE_LIBDIR_OPENGL_ES2 QMAKE_LIBDIR_BCM_HOST = $$VC_LIBRARY_PATH QMAKE_INCDIR_EGL = \ $${VC_INCLUDE_PATH} \ $${MESA_INCLUDE_PATH} \ $${VC_INCLUDE_PATH}/interface/vcos/pthreads \ $${VC_INCLUDE_PATH}/interface/vmcs_host/linux QMAKE_INCDIR_OPENGL_ES2 = $${QMAKE_INCDIR_EGL} QMAKE_INCDIR_BCM_HOST = $$VC_INCLUDE_PATH # recreating pkg-config --libs glesv2 QMAKE_LIBS_OPENGL_ES2 = -L$${VC_LIBRARY_PATH} -lbrcmGLESv2 -lbcm_host -lvcos -lvchiq_arm -pthread # recreating pkg-config --libs egl QMAKE_LIBS_EGL = -L$${VC_LIBRARY_PATH} -lbrcmEGL -lbrcmGLESv2 -lbcm_host -lvchostif -lbcm_host -lvcos -lvchiq_arm -pthread #recreating pkg-config --libs bcm_host QMAKE_LIBS_BCM_HOST = -L$${VC_LIBRARY_PATH} -lbcm_host -lvcos -lvchiq_arm -pthread QMAKE_CFLAGS = -march=armv8-a -mtune=cortex-a53 -mfpu=crypto-neon-fp-armv8 QMAKE_CXXFLAGS = $$QMAKE_CFLAGS DISTRO_OPTS += hard-float DISTRO_OPTS += deb-multi-arch EGLFS_DEVICE_INTEGRATION= eglfs_brcm include(../common/linux_arm_device_post.conf) load(qt_config)
Qt benutzt pkg-config, um herauszufinden, ob eine bestimmte Bibliothek auf dem Pi installiert ist und wenn ja, wo. Genau genommen gibt es oft mehrere Methoden dafür, und die letzte ist in der Regel die Verwendung der statischen Definitionen aus der Datei qmake.conf, die wir gerade gesetzt haben. Das heißt, dass wir alle .pc-Dateien entfernen müssen, damit configure nur unsere Definitionen bleiben und damit es nicht die Standard-Definitionen via pkg-config benutzt, die auf Mesa zeigen.
cd /opt/qt5pi/sysroot/usr/lib/arm-linux-gnueabihf/pkgconfig/ mv egl.pc egl.pc.mesa mv glesv2.pc glesv2.pc.mesa cd /opt/qt5pi mkdir build cd build
Jetzt können wir Qt für den Build konfigurieren, bzw. erstmal alles bis auf QtWebEngine (Erläuterungen rechts):
../qt5/configure \ -opengl es2 -device linux-rasp-pi3-brcm-g++ \ -device-option CROSS_COMPILE=/opt/raspiToolchains/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf- \ -sysroot /opt/qt5pi/sysroot \ -prefix /usr/local/qt5pi \ -extprefix /opt/qt5pi/targetBinaries \ -hostprefix /opt/qt5pi/hostBinaries \ -opensource -confirm-license \ -release -v \ -nomake examples \ -no-use-gold-linker \ -recheck-all \ -skip qtwebengine \ 2>&1 | tee ../configure$(date +"%Y-%m-%d_%H-%M").log
>Führe das configure-Tool aus >mit unserer qmake.conf >mit diesem compiler/toolchain (Pfade ggf. anpassen) >hier liegt der sysroot >das wird später der Pfad auf dem Pi sein >für den Pi bestimmte binaries hierhin legen >für den Host bestimmte binaries hierhin legen >Opensource-Variante von Qt verwenden >release build, verbose logs >nur libraries builden >gold linker funktioniert nicht >bei erneuter Ausführung alle Tests neu prüfen >QtWebEngine überspringen – vorerst >alles ins Protokoll schreiben
Überprüfe die Ausgabe; meine sah so aus.
- Build von Qt Essentials
Jetzt können wir den Build starten. Der make
-Schritt dauert ca. eine halbe Stunde.
make -j4 2>&1 | tee ../make$(date +"%Y-%m-%d_%H-%M").log make install 2>&1 | tee ../install$(date +"%Y-%m-%d_%H-%M").log
- QtWebEngine konfigurieren
Die Broadcom-Bibliotheken, die beim Pi dabei sind, sind zu alt, um QtWebEngine fehlerfrei zu builden – ihnen fehlt z.B. die GLES3-Unterstützung. Deshalb müssen wir sicherstellen, dass stattdessen die Mesa-Header gefunden werden. (Eigentlich ganz schön schräg, mehr dazu später.)
Außerdem muss der Build in einem separaten Verzeichnis durchgeführt werden.
cd /opt/qt5pi/sysroot/opt/vc/include mv EGL EGL_brcm mv GLES GLES_brcm mv GLES2 GLES2_brcm mkdir /opt/qt5pi/buildWebengine cd /opt/qt5pi/buildWebengine ../hostBinaries/bin/qmake ../qt5/qtwebengine/ -- \ -webengine-pepper-plugins -webengine-proprietary-codecs -v \ 2>&1 | tee ../configWebengine$(date +"%Y-%m-%d_%H-%M").log
Pepper Plugins wird benötigt, um später Widevine zu laden, und die Codecs sind für die Medienwiedergabe. Du kannst beides weglassen.
Überprüfe wieder die Ausgabe; meine sieht so aus.
- Build von QtWebEngine
Jetzt können wir den Build von QtWebEngine starten. Der make
-Schritt dauert ca. 4 Stunden.
make -j4 2>&1 | tee ../makeWebengine$(date +"%Y-%m-%d_%H-%M").log make install 2>&1 | tee ../installWebengine$(date +"%Y-%m-%d_%H-%M").log
- Upload auf den Pi
rsync -avz /opt/qt5pi/targetBinaries/ pi@192.168.1.101:/usr/local/qt5pi/
Die nachgestellten Schrägstriche sind wichtig!
Wenn du bis hierher gekommen bist, herzlichen Glückwunsch! Du solltest jetzt eine funktionierende Qt-Bibliothek auf deinem Raspberry Pi installiert haben. Ich erläutere noch kurz, wie man den Qt Creator einrichtet, um Programme ganz einfach cross-zu-kompilieren und über das Netzwerk auf den Pi zu bringen.
Qt Creator einrichten
Öffne den Projekte-Tab und gehe auf Kits verwalten….
Gehe auf den Qt Versionen-Reiter und füge eine neue Version hinzu, indem du das qmake auswählst, das du gerade kompiliert hast (/opt/qt5pi/hostBinaries/bin/qmake
).
Füge unter dem Compiler-Reiter ein neues “GCC” für C und C++ hinzu.
Für C++ ist der Compiler-Pfad /opt/raspiToolchains/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-g++
, und ich nenne ihn G++ RasPi Cross 7.5.0 (Linaro)
.
Für C ist der Compiler-Pfad /opt/raspiToolchains/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc
, und ich nenne ihn GCC RasPi Cross 7.5.0 (Linaro)
.
Gehe auf den Geräte-Tab und lege deinen Pi als neues “Generic Linux Device” an. Du wirst nach der IP des PIs und nach einem Benutzernamen gefragt.
Gehe zurück zum Kits-Tab und erstelle im Kits-Reiter ein neues Kit. Ich nenne meins RasPi Cross 5.15
. Wähle die Compiler (C und C++) und die Qt Version aus, die du gerade erstellt hast. Der Device Type ist Generic Linux Device, und dann kannst du unter Device das Gerät auswählen, das du gerade angelegt hast.
Jetzt kannst du das neue Kit aktivieren. Als letztes musst du noch dafür sorgen, dass in der .pro-Datei deines Projektes die folgenden Zeilen stehen:
# Default rules for deployment. qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /home/pi/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target
Jetzt sollte der Build funktionieren. Wenn du auf Ausführen klickst, wird Qt Creator das fertig kompilierte Projekt auf den Pi hochladen, ausführen und die Konsolenausgabe zu dir zurück leiten. Genial!
Die Situation mit den Grafik-Bibliotheken
Beim Raspberry Pi 3 sind normalerweise Grafiktreiber vom Chip-Hersteller Broadcom dabei und standardmäßig aktiviert. Für unsere Belange bestehen diese aus Bibliotheken für EGL (Embedded Graphics Library) und GLES (OpenGL for Embedded Systems) und liegen unter /opt/vc/lib als libbrcmEGL.so und libbrcmGLESv2.so.
Alternativ gibt es eine Open-Source-Version von Mesa. Das ist, was wir vorhin via libgles2-mesa-dev installiert haben. Diese liegen unter /usr/lib/arm-linux-gnueabihf/ als libEGL.so und libGLESv2.so.
Offensichtlich sind die beiden Versionen unterschiedlich benannt. Das geht zurück auf eine Entscheidung der RasPi-Foundation im Jahr 2016. Ist nicht so super, aber so ist’s nunmal. Es sei denn, man führt irgendwann rpi-update
aus. Das Firmware-Update bringt die Bibliotheken tatsächlich mit den Standard-Namen im gleichen Ordner zurück, beide Bibliotheken erscheinen ansonsten identisch zu sein. Ich habe keine Ahnung, warum diese Entscheidung getroffen wurde, aber wenn man so will, heißt das, dass wir mit drei verschiedenen Versionen der Bibliothek umgehen müssen.
Zu den Bibliotheken gehören auch die Header-Dateien, die benötigt werden, um Software zu builden, die diese benutzt. Praktischerweise enthalten die ein Revisionsdatum:
Broadcom (/opt/vc/include/EGL/eglext.h)
/* Header file version number */ /* Current version at http://www.khronos.org/registry/egl/ */ /* $Revision: 7244 $ on $Date: 2009-01-20 17:06:59 -0800 (Tue, 20 Jan 2009) $ */
Mesa (/usr/include/EGL/eglext.h)
/* ** This header is generated from the Khronos EGL XML API Registry. ** The current version of the Registry, generator scripts ** used to make the header, and the header can be found at ** http://www.khronos.org/registry/egl ** ** Khronos $Git commit SHA1: cb927ca98d $ on $Git commit date: 2019-08-08 01:05:38 -0700 $ */
Die VC-Header sind von 2009 und ihnen fehlt, um nur ein Beispiel zu nennen, EGLSetBlobFuncANDROID. Außerdem fehlt GLESv2 der komplette Ordner GLES3/. Das heißt im Grunde, diese Header-Sammlung ist nutzlos, um etwas wie Chromium zu builden, was unausweichlich irgendwelche der neueren Definitionen benutzen wird.
Glücklicherweise habe ich herausgefunden, dass man einfach die neueren Header von Mesa benutzen kann, und trotzdem die Broadcom-Treiber zum “linken” verwenden kann. Das ist, was in Schritt 13 passiert. Wie zukunftsträchtig das ist, weiß man natürlich nicht, aber aktuell funktioniert es! Würde Chromium irgendeine der neuen Funktionen benutzen wollen, ginge das natürlich nicht. Ich konnte aber bisher keine Probleme damit feststellen. Chromium spamt ab und zu ein paar Fehlermeldungen, die evtl. damit zusammenhängen könnten, aber sie scheinen nichts kaputtzumachen.
Mit den Schritten, die ich beschrieben habe, kann man also QtBase und den Rest der Qt Essential-Module kompilieren und gegen die Standard-Bibliotheken von Broadcom linken, und im Anschluss QtWebEngine gegen die neuere Version dieser Bibliothek. Somit kann man die Standard-Grafiktreiber von RaspiOS aktiviert lassen und dennoch Qt mit QtWebEngine benutzen!
Vielleicht schaue ich mir in Zukunft mal an, komplett zu den Mesa-Treibern zu wechseln. 🙂 Bis dahin finden sich bestimmt auch etliche andere Tutorials zur Verwendung der Mesa-Treiber im Internet.
Referenzen
Ich würde gerne auf ein paar Webseiten, Blogs und Tutorials verweisen, die mir sehr geholfen haben, so weit zu kommen. Vielleicht findest du dort Hilfe, falls du nicht weiterkommst. Außerdem bin ich natürlich super froh darüber, dass es sie gibt, denn ansonsten hätte ich das alles vermutlich nicht hinbekommen. Sie verdienen also auf alle Fälle eine lobende Erwähnung!
https://www.raspberrypi.org/forums/viewtopic.php?t=204778
https://ottycodes.wordpress.com/2019/02/21/cross-compiling-qt5-12-for-raspberry-pi3/
https://wiki.qt.io/RaspberryPi2EGLFS
https://wiki.qt.io/RaspberryPiWithWebEngine
https://www.tal.org/tutorials/building-qt-512-raspberry-pi
https://mechatronicsblog.com/cross-compile-and-deploy-qt-5-12-for-raspberry-pi/
http://www.bildungsgueter.de/RaspberryIntro/Pages/GPULibs001.htm
https://github.com/UvinduW/Cross-Compiling-Qt-for-Raspberry-Pi-4 [Dank an Manish Buttan für diesen Link; RPi4 und ohne QTWebEngine, aber ansonsten gut strukturiert und aktuell]
Leider komme ich bei Punkt 9 nicht weiter!
Fehlermeldung:
pi@raspberrypi:/opt/qt5pi $ rsync -avzR pi@192.168.178.101:/lib :/usr/include :/usr/lib :/usr/share/pkgconfig/ :/opt/vc sysroot
ssh: connect to host 192.168.178.101 port 22: No route to host
rsync: connection unexpectedly closed (0 bytes received so far) [Receiver]
rsync error: unexplained error (code 255) at io.c(228) [Receiver=3.2.3]
Bis dahin konnte ich alles nach Anleitung installieren!?
Grüße
Marco
Hi Marco!
Das klingt nach einem überschaubaren Problem.
rsync ist ein tool, um Dateien vom Pi über ssh auf deinen Rechner zu kopieren, und dabei kommt es anscheinend zu einem Fehler.
Meine Debug-Schritte wären:
1. Ist dein Pi an und im Netzwerk?
2. Ist die IP richtig?
3. Kannst du dich normal über SSH verbinden? Dazu muss der SSH-Server aktiviert sein. Vielleicht ist er das nicht, wenn du deinen Pi nicht “Headless” aufgesetzt hast.
Wie das geht findest du auch schnell selbst raus. Ansonsten: https://www.raspberrypi.com/documentation/computers/remote-access.html#ssh
4. Wenn die SSH-Verbindung ansonsten funktioniert, sollte auch rsync klappen; d.h. du könntest probieren, ob du eine einzelne Test-Datei via rsync kopieren kannst.
Anleitung für rsync findest du wieder im Internet, z.B: https://www.digitalocean.com/community/tutorials/how-to-use-rsync-to-sync-local-and-remote-directories
Wenn das alles soweit funktioniert, sollte auch der Schritt 9 (Sysroot kopieren) klappen.
Viel Erfolg! 🙂