The Qt version of an embedded Linux system is tied to the Yocto version. Yocto 2.7 (Warrior) comes with Qt 5.12, Yocto 3.1 (Dunfell) with Qt 5.14, Yocto 3.2, 3.3 and 3.4 with Qt 5.15 . There is no Yocto version with Qt 6 yet. How can we build Qt 6.2 against the SDK of old Yocto versions?
Introduction
Yocto releases rarely come with the latest Qt release. Terminal manufacturers are very slow in updating their BSPs to newer Yocto versions. The Qt applications on these terminals depend on older and older Qt versions.
Currently, I am helping a client build the driver terminal for a construction machine. The terminal hardware is a Topcon Opus A8. Topcon’s BSP is based on Yocto 2.7 with Qt 5.12. Naturally, the client wants to work with Qt 6.2 or newer. And that’s exactly what I’ll show you in this post: how to build Qt 6.2.4 against an embedded Linux SDK built with Yocto 2.7.
The approach should work for other combinations of Yocto and Qt versions. The most important precondition is that the C++ compiler from the SDK can build Qt. G++ 8.3 could compile the C++17 code of Qt 6. It spit out a couple of warnings but no errors. I had to replace CMake 3.14 from the SDK with CMake 3.18 or newer. Then, the target build worked fine.
Before we can build Qt 6.2.4, we must build the SDK. We can follow the instructions in Setting Up Yocto Projects with kas or Building a Qt SDK with Yocto.
Building Qt 6 for embedded Linux has changed over Qt 5. It is now a two-step process.
- We build Qt 6 from Git for the host. This gives us Qt tools like moc and rcc that are run during the build on the host computer.
- We cross-build exactly the same Qt 6 version from Git for the target. If we want to build Qt 6.2.4 for the target, we must have built Qt 6.2.4 for the host. The build failed when I used Qt 6.2.0 on the host.
I use the following directory structure for the project DanishDynamite. The working directory <WORKDIR>
is a placeholder for a specific directory, which is /private/Projects/DanishDynamite
in my case.
<WORKDIR>/
build-host-qt6.2.4/ # Host build of Qt 6.2.4
build-target-qt6.2.4/ # Target build of Qt 6.2.4
qt5/ # Qt sources
sdk/ # Yocto SDK installation
terminal-app/ # Sources of terminal application
Fetching the Qt 6 Sources
In our working directory <WORKDIR>
, we clone the Qt repository, switch to version 6.2.4 and initialise the Git submodules.
$ cd <WORKDIR>
$ git clone git://code.qt.io/qt/qt5.git
$ cd qt5
$ git checkout 6.2.4
$ perl init-repository
Building Qt 6 for the Host
We follow the documentation page Building Qt 6 from Git with slight adjustments to build Qt 6.2.4 for the host. We create a directory for the shadow host build in <WORKDIR>
.
$ cd <WORKDIR>
$ mkdir build-host-qt6.2.4
$ cd build-host-qt6.2.4
We configure Qt for the host with the same submodules as Qt for the target later on. So, we can develop Qt applications on the host as well as on the target. We build a -release
version of Qt and install it in the -prefix
directory on the host computer.
$ ../qt5/configure -release -prefix /public/Qt/host-qt6.2.4 \
-submodules qtbase,qtdeclarative,qtimageformats,qtmultimedia,qtremoteobjects,qtscxml,qtserialbus,qtserialport,qtsystems,qttools,qtwayland
$ cmake --build . --parallel
$ cmake --install .
Note: Qt 6.2.0 does not support the option -submodules
yet. Instead of listing the included Qt modules, we must list the excluded Qt modules with a -skip
option for each module.
Note: In an earlier version of this post, I split up the -submodules
option into two -submodules
options. Some modules were not built (e.g., qtmultimedia
). The command line may only contain this option once.
Note: We must include the module qttools
, because it provides the tools lupdate
and lrelease
for translations and the CMake support for these tools. We don’t build qttools
for the target, because we won’t run these tools on the target.
Building Qt 6 for the Target
For the target build, we use the same sources as for the host build. Again, we create a directory for the shadow target build in <WORKDIR>
.
$ cd $WORKDIR
$ mkdir build-target-qt6.2.4
$ cd build-target-qt6.2.4
We set up the build environment for the Yocto SDK by sourcing the environment-setup
script. The script sets environment variables like CC
, CXX
, OECORE_TARGET_SYSROOT
and OECORE_NATIVE_SYSROOT
. For example:
$ . <WORKDIR>/sdk/environment-setup-armv7at2hf-neon-poky-linux-gnueabi
$ env
...
OECORE_NATIVE_SYSROOT=<WORKDIR>/sysroots/x86_64-pokysdk-linux
OECORE_TARGET_SYSROOT=<WORKDIR>/sdk/sysroots/armv7at2hf-neon-poky-linux-gnueabi
OECORE_TARGET_ARCH=arm
OECORE_DISTRO_VERSION=2.7.4
OE_CMAKE_TOOLCHAIN_FILE=<WORKDIR>/sdk/sysroots/x86_64-pokysdk-linux/usr/share/cmake/OEToolchainConfig.cmake
...
The Qt documentation page Configuring an Embedded Linux Device describes how to configure Qt 6.2. The description didn’t work right out of the box for me. I’ll walk you through the problems I encountered and how I fixed them.
The configure
command for the target extends the command for the host by a couple of options (in bold face).
$ cd <WORKDIR>/build-target-qt6.2.4
$ ../qt5/configure -release -sysroot $OECORE_TARGET_SYSROOT \
-prefix /opt/qt-6.2 -extprefix $OECORE_TARGET_SYSROOT/opt/qt-6.2 \
-qt-host-path /public/Qt/host-qt6.2.4 \
-submodules qtbase,qtdeclarative,qtimageformats,qtmultimedia,qtremoteobjects,qtscxml,qtserialbus,qtserialport,qtsystems,qtwayland \
-- -DCMAKE_TOOLCHAIN_FILE=$OE_CMAKE_TOOLCHAIN_FILE
The target build must look for the libraries and headers in the target root filesystem specified by -sysroot $OECORE_TARGET_SYSROOT
. The installation directory on the target device is given by -prefix /opt/qt-6.2
and the staging directory by -extprefix $OECORE_TARGET_SYSROOT/opt/qt-6.2
. The Qt libraries and headers from the staging area are used when we cross-build Qt applications. The option -qt-host-path /public/Qt/host-qt6.2.4
tells the target build where to find host tools like moc
and rcc
.
The last line, which is separated with --
from the rest of the options, is passed to CMake. In Qt 6, the configure
command is translated into a cmake
command. The variable CMAKE_TOOLCHAIN_FILE
is set to the standard CMake toolchain file created for a Yocto SDK.
Running the above configure
command stops with this error:
CMake Error at <WORKDIR>/qt5/qtbase/cmake/QtProcessConfigureArgs.cmake:80 (list):
list does not recognize sub-command POP_FRONT
The CMake 3.15 Release Notes reveal that POP_FRONT
was newly introduced to CMake 3.15. The Yocto 2.7 SDK contains CMake 3.14. Qt 6 requires at least CMake 3.18. We install CMake 3.18 or newer on our build computer, say, in /usr/local/bin
, and link cmake from the SDK to this new version.
$ cd $OECORE_NATIVE_SYSROOT/usr/bin
$ mv cmake cmake-3.14
$ ln -s /usr/local/bin/cmake cmake
This fixes the error about POP_FRONT
. Rerunning configure
now works.
We build Qt for the target and install it into the Yocto SDK at $OECORE_TARGET_SYSROOT/opt/qt-6.2
with the following commands.
$ cmake --build . --parallel
$ cmake --install .
Building Qt 6 Applications for the Target
Section Building Applications for the Target Device of the documentation page Configuring an Embedded Linux Device tells us how to cross-build Qt applications. This only works, if we have sourced the environment-setup
script of the Yocto SDK before.
$ . <WORKDIR>/sdk/environment-setup-armv7at2hf-neon-poky-linux-gnueabi
$ mkdir -p <WORKDIR>/build-terminal-app
$ cd <WORKDIR>/build-terminal-app
$ <WORKDIR>/sdk/sysroots/armv7at2hf-neon-poky-linux-gnueabi/opt/qt-6.2/bin/qt-cmake ../terminal-app/
$ cmake --build . --parallel
We could start QtCreator from the Linux prompt above with all the environment variables preset. However, this would make it impractical to switch between different kits, say, one for the target build and one for the host build of the application. That’s probably the reason why the documentation page Configuring an Embedded Linux provides An Example Toolchain File.
Unfortunately, the example toolchain file is incomplete. It lacks settings for QT_HOST_PATH
and CMAKE_PREFIX_PATH
. Setting the PKG_CONFIG_*
environment variables is not required. The corrected toolchain file looks as follows. Don’t forget to adapt the parts in bold face to your development setup.
cmake_minimum_required(VERSION 3.18)
include_guard(GLOBAL)
set(TARGET_SYSROOT <WORKDIR>/sdk/sysroots/armv7at2hf-neon-poky-linux-gnueabi)
set(HOST_SYSROOT <WORKDIR>/sdk/sysroots/x86_64-pokysdk-linux)
set(QT_HOST_PATH /public/Qt/host-qt6.2.4)
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_SYSROOT ${TARGET_SYSROOT})
set(CMAKE_PREFIX_PATH ${CMAKE_SYSROOT}/opt/qt-6.2/lib/cmake)
set(CMAKE_C_COMPILER ${HOST_SYSROOT}/usr/bin/arm-poky-linux-gnueabi/arm-poky-linux-gnueabi-gcc)
set(CMAKE_CXX_COMPILER ${HOST_SYSROOT}/usr/bin/arm-poky-linux-gnueabi/arm-poky-linux-gnueabi-g++)
set(QT_COMPILER_FLAGS "-march=armv7-a -mthumb -mfpu=neon -mfloat-abi=hard")
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)
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()
My toolchain file is located at <WORKDIR>/terminal-app/cmake/Qt6CMakeToolchainConfig.cmake
. We open a new Linux terminal and build the terminal application with the following commands.
$ cd <WORKDIR>/build-terminal-app
$ rm -r *
$ cmake -DCMAKE_TOOLCHAIN_FILE=../terminal-app/cmake/Qt6CMakeToolchainConfig.cmake \
../terminal-app/
$ cmake --build . --parallel
The values of TARGET_SYSROOT
, HOST_SYSROOT
and QT_HOST_PATH
may differ between development setups. We allow developers to set these variables in their environment. The corresponding lines in the toolchain file change as follows.
set(TARGET_SYSROOT $ENV{TARGET_SYSROOT})
set(HOST_SYSROOT $ENV{HOST_SYSROOT})
set(QT_HOST_PATH $ENV{QT_HOST_PATH})
The QtCreator kit DanishDynamite Qt 6.2.4 is defined as follows:
Environment holds the settings of the environment variables:
HOST_SYSROOT=<WORKDIR>/sdk/sysroots/x86_64-pokysdk-linux
QT_HOST_PATH=/public/Qt/host-qt6.2.4
TARGET_SYSROOT=<WORKDIR>/sdk/sysroots/armv7at2hf-neon-poky-linux-gnueabi
The CMake Configuration points to the new CMake toolchain file:
CMAKE_TOOLCHAIN_FILE:FILEPATH=<WORKDIR>/terminal-app/cmake/Qt6CMakeToolchainConfig.cmake
Running Qt 6 Applications on the Target
In this post, I show how to migrate the terminal application of the ROPA sugar-beet harvesters from Qt 5.12 over Qt 5.15 to Qt 6.2. After I rebuilt the terminal application for the Yocto 2.7 SDK with Qt 6.2, I could run the application on the Topcon Opus A8.
The Run Settings in QtCreator look as follows (see the post Building a Qt SDK with Yocto for a detailed description how to set up QtCreator for running Qt applications on a target device).
Yes, traditionally terminal suppliers have been very slow, but not all of them. How do you see containerizing the QT to separate the platform ( terminal) SW from application specific SW?
This could be a good use of containers on embedded Linux systems. Manufacturers of agricultural and construction machines update the Qt applications most frequently, the Qt libraries less frequently and the basic Linux system rarely (if ever). The container would contain the Qt applications and libraries. It would have a reasonable size, if the Linux system isn’t included in the container.
Great articles, notice the link to this post “Setting Up Yocto Projects with kas” needs updating to this: “https://embeddeduse.com/2022/06/24/setting-up-yocto-projects-with-kas/”
thanks for detailed writing. 🙂
Well spotted. Thank you, Ted.
Thanks a lot for sharing! Great!
Nevertheless I am encountering:
— Searching for tool ‘Qt6::qtwaylandscanner’ in package Qt6WaylandScannerTools.
— Could NOT find Qt6WaylandScannerTools (missing: Qt6WaylandScannerTools_DIR)
CMake Error at qtbase/cmake/QtToolHelpers.cmake:170 (message):
The tool “Qt6::qtwaylandscanner” was not found in the
Qt6WaylandScannerTools package. Package found: 0
Call Stack (most recent call first):
qtwayland/src/qtwaylandscanner/CMakeLists.txt:8 (qt_internal_add_tool)
Only hint I found so far:
https://forum.qt.io/topic/139492/qt6-cross-compile-for-imx6-with-wayland-support
But this didn’t work for me. Any suggestions?