Cross-compiling Qt6.6 for the OrangePi Zero2, Zero2W, Zero3

via Docker

qt | 2023-11-15

Creating cross-compilation toolchains for Qt targeting other architectures takes some time. We automated it with a Dockerfile, so that we can cross-compile Qt6 apps to OrangePi devices without first spending half a day getting everything setup correctly.

This article covers how to use that Dockerfile. Previously, we shared a Dockerfile for cross-compiling a static Qt5 targeting Windows.

Requirements

You have:

  • A Zero 2, Zero2w, or Zero 3 device with a fresh OS image
    • Allwinner H616 / H618
    • Mali G31 GPU + Mesa Panfrost driver
  • Debian based
    • In our case, Ubuntu server with kernel version 6.1 as provided by OrangePi. Other debian-based images should also work, your mileage may vary.
    • Orangepizero2w_1.0.0_ubuntu_jammy_server_linux6.1.31.7z
    • 651760c54dd6af2ecfad9f9b8d419411783032965a93f4703d73a6fdce2ecb04
  • Your device is reachable on your local network
    • ... as the Docker container will require SSH access to the device during initial setup

What are we installing?

  • Qt 6.6.0
  • Modules: qtbase, qtdeclarative, qtsvg, qtmultimedia, qtquick3d, qtlocation, qtsensors, qtconnectivity, qt3d, qtshadertools, qtimageformats, qtwebsockets, qtcharts, qtgraphs, qthttpserver, qtvirtualkeyboard, qtpositioning
    • To change the default, the Dockerfile supports passing MODULES=...
  • Hardware acceleration (EGLFS via Mesa panfrost)
  • We excluded Qt module D-Bus, as there are some issues.
    • Note: Bluetooth will not work without the D-Bus feature

Our setup script does not fetch files from arbitrary URLs, nor does it add a custom apt mirror. See pi_requirements.sh and the Dockerfile to verify what is installed.

How to install?

There are 2 things you will need to do in order to get started:

  1. Building the Docker base image, which you should only run once
  2. Using this image for subsequent app compiles

Clone our repository to get started:

git clone https://github.com/kroketio/qt6-to-orangepi-zero2-3.git
cd qt6-to-orangepi-zero2-3.git

1. Base image

During the first step, Docker needs to connect to the device in order to do some setup and fetch the device's sysroot. You can tell Docker the IP address of the device via PI_HOST=....

docker build -f Dockerfile --tag qt6:orangepizero2-3 \
    --build-arg THREADS=8 PI_HOST=192.168.1.12 .

Available arguments:

Name Description
THREADS Compilation threads passed to make -j. Default 8
MODULES A list of Qt6 modules that will be passed to ./init-repository. Comma separated.
PI_HOST The IP address of the OrangePi device.
PI_USER The username for the OrangePi device. Default is orangepi.
PI_PASSWORD The password for the OrangePi device. Default is orangepi.


This command runs for ~25min because Qt is installed twice (x86_64, ARM), as well as copying back and forth some files. Grab some coffee.

2. Compiling apps

We now have a Docker image available under tag qt6:orangepizero2-3. We can use this image to cross-compile. As an example, we'll compile a QtQuick program from the Qt6 examples. This way we can confirm that the rendering is handled by the GPU.

cd ~/
git clone https://code.qt.io/qt/qtdoc.git --depth 1 --branch 6.6.0
cp -R qtdoc/examples/demos/rssnews .
cd rssnews

# configure step, this is where you'll put your CMake definitions -DFOO=BAR for your own programs
docker run --rm -it -v $PWD:/app -w /app qt6:orangepizero2-3 bash -c \
    'qt-cmake -Bbuild -DCMAKE_PREFIX_PATH=/z2w/target/ .'

The output should look something like this:

-- The CXX compiler identification is GNU 11.4.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/aarch64-linux-gnu-g++-11 - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Looking for C++ include pthread.h
-- Looking for C++ include pthread.h - found
-- Performing Test CMAKE_HAVE_LIBC_PTHREAD
-- Performing Test CMAKE_HAVE_LIBC_PTHREAD - Success
-- Found Threads: TRUE  
-- Performing Test HAVE_STDATOMIC
-- Performing Test HAVE_STDATOMIC - Success
-- Found WrapAtomic: TRUE  
-- Performing Test HAVE_EGL
-- Performing Test HAVE_EGL - Success
-- Found EGL: /z2w/sysroot/usr/include (found version "1.5") 
-- Performing Test HAVE_GLESv2
-- Performing Test HAVE_GLESv2 - Success
-- Found GLESv2: /z2w/sysroot/usr/include  
-- Found XKB: /z2w/sysroot/usr/lib/aarch64-linux-gnu/libxkbcommon.so (found suitable version "1.4.0", minimum required is "0.5.0") 
-- Configuring done
-- Generating done
-- Build files have been written to: /app/build

Compile the program:

# compile step
docker run --rm -it -v $PWD:/app -w /app qt6:orangepizero2-3 bash -c 'make -Cbuild -j8'

The output should look something like this:

[...]
[ 71%] Building CXX object CMakeFiles/rssnews.dir/main.cpp.o
[ 71%] Building CXX object CMakeFiles/rssnews.dir/rssnews_autogen/mocs_compilation.cpp.o
[ 85%] Building CXX object CMakeFiles/rssnews.dir/build/.rcc/qrc_rssnews.cpp.o
[100%] Linking CXX executable rssnews
make[2]: Leaving directory '/app/build'
[100%] Built target rssnews
make[1]: Leaving directory '/app/build'
make: Leaving directory '/app/build'

$ file build/rssnews 
build/rssnews: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (GNU/Linux), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=f8069f385fd86e8cdc9018422540ece909d8ceff, for GNU/Linux 3.7.0, not stripped

Resulting in binary build/rssnews. Finally, transfer this to the device:

scp build/rssnews root@192.168.1.12:

And execute it:

LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/qt6/lib/ ./rssnews -platform eglfs

Note LD_LIBRARY_PATH to point to Qt6, and also -platform eglfs because we are on Ubuntu server without a desktop environment.

Confirm that scrolling happens smoothly, as low FPS may indicate you forgot to turn on the GPU via the orangepi-config system utility (System -> Hardware -> GPU + reboot). The GPU on these devices are disabled by default.

^ Running on a 1920x1080 touch display.

Dockerfile

For additional information about the Dockerfile, an overview of the steps:

  1. Host: Create a directory structure to facilitate the upcoming steps
  2. Host: Install a long list of dependencies
  3. Host: git clone the Qt6 repo + any Qt modules to include
  4. Host: Add a 'mkspec' for our device (into qt6/qtbase/mkspecs/devices/)
  5. Host: Create a toolchain file that describes our device
  6. Host: Compile a x86_64 version of Qt6
  7. Device: Install a long list of dependencies
  8. Host: Copy the device sysroot to the host
  9. Host: Cross-compile Qt6 for ARM, using the toolchain, mkspec, and sysroot
  10. Host: Copy the newly built Qt6 to the device at /usr/local/qt6/