Back in 2017 I did work to setup a cross-building toolchain for QT Creator, that takes advantage of Debian's packaging for all the dependency ecosystem.
It ended with cbqt which is a little script that sets up a chroot to hold cross-build-dependencies, to avoid conflicting with packages in the host system, and sets up a qmake alternative to make use of them.
Today I'm dusting off that work, to ensure it works on Debian bullseye.
Resetting QT Creator
To make things reproducible, I wanted to reset QT Creator's configuration.
Besides purging and reinstalling the package, one needs to manually remove:
~/.config/QtProject
~/.cache/QtProject/
/usr/share/qtcreator/QtProject
which is where configuration is stored if you used sdktool to programmatically configure Qt Creator (see for example this post and see Debian bug #1012561.
Updating cbqt
Easy start, change the distribution for the chroot:
-DIST_CODENAME = "stretch"
+DIST_CODENAME = "bullseye"
Adding LIBDIR
Something else does not work:
Test$ qmake-armhf -makefile
Info: creating stash file …/Test/.qmake.stash
Test$ make
[...]
/usr/bin/arm-linux-gnueabihf-g++ -Wl,-O1 -Wl,-rpath-link,…/armhf/lib/arm-linux-gnueabihf -Wl,-rpath-link,…/armhf/usr/lib/arm-linux-gnueabihf -Wl,-rpath-link,…/armhf/usr/lib/ -o Test main.o mainwindow.o moc_mainwindow.o …/armhf/usr/lib/arm-linux-gnueabihf/libQt5Widgets.so …/armhf/usr/lib/arm-linux-gnueabihf/libQt5Gui.so …/armhf/usr/lib/arm-linux-gnueabihf/libQt5Core.so -lGLESv2 -lpthread
/usr/lib/gcc-cross/arm-linux-gnueabihf/10/../../../../arm-linux-gnueabihf/bin/ld: cannot find -lGLESv2
collect2: error: ld returned 1 exit status
make: *** [Makefile:146: Test] Error 1
I figured that now I also need to set QMAKE_LIBDIR
and not just
QMAKE_RPATHLINKDIR
:
--- a/cbqt
+++ b/cbqt
@@ -241,18 +241,21 @@ include(../common/linux.conf)
include(../common/gcc-base-unix.conf)
include(../common/g++-unix.conf)
+QMAKE_LIBDIR += {chroot.abspath}/lib/arm-linux-gnueabihf
+QMAKE_LIBDIR += {chroot.abspath}/usr/lib/arm-linux-gnueabihf
+QMAKE_LIBDIR += {chroot.abspath}/usr/lib/
QMAKE_RPATHLINKDIR += {chroot.abspath}/lib/arm-linux-gnueabihf
QMAKE_RPATHLINKDIR += {chroot.abspath}/usr/lib/arm-linux-gnueabihf
QMAKE_RPATHLINKDIR += {chroot.abspath}/usr/lib/
Now it links again:
Test$ qmake-armhf -makefile
Test$ make
/usr/bin/arm-linux-gnueabihf-g++ -Wl,-O1 -Wl,-rpath-link,…/armhf/lib/arm-linux-gnueabihf -Wl,-rpath-link,…/armhf/usr/lib/arm-linux-gnueabihf -Wl,-rpath-link,…/armhf/usr/lib/ -o Test main.o mainwindow.o moc_mainwindow.o -L…/armhf/lib/arm-linux-gnueabihf -L…/armhf/usr/lib/arm-linux-gnueabihf -L…/armhf/usr/lib/ …/armhf/usr/lib/arm-linux-gnueabihf/libQt5Widgets.so …/armhf/usr/lib/arm-linux-gnueabihf/libQt5Gui.so …/armhf/usr/lib/arm-linux-gnueabihf/libQt5Core.so -lGLESv2 -lpthread
Making it work in Qt Creator
Time to try it in Qt Creator, and sadly it fails:
…/armhf/usr/lib/arm-linux-gnueabihf/qt5/mkspecs/features/toolchain.prf:76: Variable QMAKE_CXX.COMPILER_MACROS is not defined.
QMAKE_CXX.COMPILER_MACROS
is not defined
I traced it to this bit in
armhf/usr/lib/arm-linux-gnueabihf/qt5/mkspecs/features/toolchain.prf
(nonrelevant bits deleted):
isEmpty($${target_prefix}.COMPILER_MACROS) {
msvc {
# …
} else: gcc|ghs {
vars = $$qtVariablesFromGCC($$QMAKE_CXX)
}
for (v, vars) {
# …
$${target_prefix}.COMPILER_MACROS += $$v
}
cache($${target_prefix}.COMPILER_MACROS, set stash)
} else {
# …
}
It turns out that qmake is not able to realise that the compiler is gcc, so
vars does not get set, nothing is set in COMPILER_MACROS
, and qmake fails.
Reproducing it on the command line
When run manually, however, qmake-armhf
worked, so it would be good to know
how Qt Creator is actually running qmake
. Since it frustratingly does not
show what commands it runs, I'll have to strace
it:
strace -e trace=execve --string-limit=123456 -o qtcreator.trace -f qtcreator
And there it is:
$ grep qmake- qtcreator.trace
1015841 execve("/usr/local/bin/qmake-armhf", ["/usr/local/bin/qmake-armhf", "-query"], 0x56096e923040 /* 54 vars */) = 0
1015865 execve("/usr/local/bin/qmake-armhf", ["/usr/local/bin/qmake-armhf", "…/Test/Test.pro", "-spec", "arm-linux-gnueabihf", "CONFIG+=debug", "CONFIG+=qml_debug"], 0x7f5cb4023e20 /* 55 vars */) = 0
I run the command manually and indeed I reproduce the problem:
$ /usr/local/bin/qmake-armhf Test.pro -spec arm-linux-gnueabihf CONFIG+=debug CONFIG+=qml_debug
…/armhf/usr/lib/arm-linux-gnueabihf/qt5/mkspecs/features/toolchain.prf:76: Variable QMAKE_CXX.COMPILER_MACROS is not defined.
I try removing options until I find the one that breaks it and... now it's always broken! Even manually running qmake-armhf, like I did earlier, stopped working:
$ rm .qmake.stash
$ qmake-armhf -makefile
…/armhf/usr/lib/arm-linux-gnueabihf/qt5/mkspecs/features/toolchain.prf:76: Variable QMAKE_CXX.COMPILER_MACROS is not defined.
Debugging toolchain.prf
I tried purging and reinstalling qtcreator, and recreating the chroot, but
qmake-armhf
is staying broken. I'll let that be, and try to debug
toolchain.prf
.
By grepping gcc
in the mkspecs
directory, I managed to figure out that:
- The
} else: gcc|ghs {
test is matching the value(s) ofQMAKE_COMPILER
QMAKE_COMPILER
can have multiple values, separated by space- If in
armhf/usr/lib/arm-linux-gnueabihf/qt5/mkspecs/arm-linux-gnueabihf/qmake.conf
I setQMAKE_COMPILER = gcc arm-linux-gnueabihf-gcc
, then things work again.
Sadly, I failed to find reference documentation for QMAKE_COMPILER
's syntax
and behaviour. I also failed to find why qmake-armhf
worked earlier, and I am
also failing to restore the system to a situation where it works again. Maybe I
dreamt that it worked? I had some manual change laying around from some
previous fiddling with things?
Anyway at least now I have the fix:
--- a/cbqt
+++ b/cbqt
@@ -248,7 +248,7 @@ QMAKE_RPATHLINKDIR += {chroot.abspath}/lib/arm-linux-gnueabihf
QMAKE_RPATHLINKDIR += {chroot.abspath}/usr/lib/arm-linux-gnueabihf
QMAKE_RPATHLINKDIR += {chroot.abspath}/usr/lib/
-QMAKE_COMPILER = {chroot.arch_triplet}-gcc
+QMAKE_COMPILER = gcc {chroot.arch_triplet}-gcc
QMAKE_CC = /usr/bin/{chroot.arch_triplet}-gcc
Fixing a compiler mismatch warning
In setting up the kit, Qt Creator also complained that the compiler from qmake
did not match the one configured in the kit. That was easy to fix, by pointing
at the host system cross-compiler in qmake.conf
:
QMAKE_COMPILER = {chroot.arch_triplet}-gcc
-QMAKE_CC = {chroot.arch_triplet}-gcc
+QMAKE_CC = /usr/bin/{chroot.arch_triplet}-gcc
QMAKE_LINK_C = $$QMAKE_CC
QMAKE_LINK_C_SHLIB = $$QMAKE_CC
-QMAKE_CXX = {chroot.arch_triplet}-g++
+QMAKE_CXX = /usr/bin/{chroot.arch_triplet}-g++
QMAKE_LINK = $$QMAKE_CXX
QMAKE_LINK_SHLIB = $$QMAKE_CXX
Updated setup instructions
Create an armhf environment:
sudo cbqt ./armhf --create --verbose
Create a qmake wrapper that builds with this environment:
sudo ./cbqt ./armhf --qmake -o /usr/local/bin/qmake-armhf
Install the build-dependencies that you need:
# Note: :arch is added automatically to package names if no arch is explicitly specified
sudo ./cbqt ./armhf --install libqt5svg5-dev libmosquittopp-dev qtwebengine5-dev
Build with qmake
Use qmake-armhf
instead of qmake
and it works perfectly:
qmake-armhf -makefile
make
Set up Qt Creator
Configure a new Kit in Qt Creator:
- Tools/Options, then Kits, then Add
- Name:
armhf
(or anything you like) - In the Qt Versions tab, click Add then set the path of the new Qt to
/usr/local/bin/qmake-armhf
. Click Apply. - Back in the Kits, select the Qt version you just created in the Qt version field
- In Compilers, select the ARM versions of GCC. If they do not appear,
install
crossbuild-essential-armhf
, then in the Compilers tab click Re-detect and then Apply to make them available for selection - Dismiss the dialog with "OK": the new kit is ready
Now you can choose the default kit to build and run locally, and the armhf
kit for remote cross-development.
I tried looking at sdktool to automate this step, and it requires a nontrivial amount of work to do it reliably, so these manual instructions will have to do.
Credits
This has been done as part of my work with Truelite.