Cross-Compile Qt 6 for Raspberry Pi: Difference between revisions

From Qt Wiki
Jump to navigation Jump to search
m (minor naming fix)
(Add some additional build dependencies needed for building from a clean Ubuntu install and update directories for consistency)
(33 intermediate revisions by 6 users not shown)
Line 1: Line 1:
==Introduction==
==Introduction==
This is a step-by-step guide to cross-compile Qt 6 for Raspberry Pi OS. The end result of this guide allows you to compile Qt 6 applications on a host machine and deploy it on the Raspberry Pi 4 Model B 2GB running a Raspberry Pi OS image. The instructions in this guide are targeted for beginners, but it should be easy to follow for everyone. It was tested on Ubuntu 20.04 targeting Raspberry Pi OS, but should work with any Debian-based OS.
This is a step-by-step guide to cross-compile Qt 6 for '''Raspberry Pi OS.''' The end result of this guide allows you to compile Qt 6 applications on a host machine and deploy it on the Raspberry Pi running a Raspberry Pi OS image. The instructions in this guide are targeted for beginners, but it should be easy to follow for everyone.  
 
The steps on this guide was tested on '''Ubuntu 20.04''' targeting '''Raspberry Pi 4 Model B 2GB (64-bit)''' running Raspberry Pi OS, but it should work with any Debian-based OS.


For an older guide, check this [https://wiki.qt.io/Raspberry_Pi_Beginners_Guide Qt Wiki guide] or [https://wiki.qt.io/RaspberryPi2EGLFS this one].
For an older guide, check this [https://wiki.qt.io/Raspberry_Pi_Beginners_Guide Qt Wiki guide] or [https://wiki.qt.io/RaspberryPi2EGLFS this one].
Line 9: Line 11:
At its most basic setup, we need '''at least the following components''' in a host machine to cross-compile Qt:
At its most basic setup, we need '''at least the following components''' in a host machine to cross-compile Qt:


*'''Sysroot,''' which basically is the scaled down version of our target's filesystem inside our host machine. For this example, we will store it on <code>~/rpi-sysroot</code>
*'''Sysroot,''' which basically is the scaled down version of our target's filesystem inside our host machine. For this example, we will name this <code>~/rpi-sysroot</code>
*'''A cross-compiler.''' To make it as straightforward as possible, for this guide we will use the cross-compiler for Aarch64 from the Ubuntu repository.
*'''A cross-compiler on host machine.''' To make it as straightforward as possible, for this guide we will use the cross-compiler for Aarch64 '''from the official Ubuntu package repository'''.
*'''Qt source code with its submodules, at least the qtbase submodule.''' For this guide, we will download Qt source code from Qt official GitHub repository. By default, it will be stored in <code>~/qt5</code>.
*'''Qt source code with its submodules, at least the qtbase submodule.''' For this guide, we will download Qt source code from Qt official GitHub repository. By default, it will be stored in <code>~/qt5</code>.
*'''CMake toolchain.''' CMake is the recommended build generator for Qt 6. Inside this toolchain file, we will specify in detail about the exact specifications of our cross-compiled Qt build. We will store this <code>toolchain.cmake</code> file on our <code>$HOME</code> directory.
*'''CMake toolchain.''' CMake is the recommended build generator for Qt 6. Inside this toolchain file, we will specify in detail about the exact specifications of our cross-compiled Qt build. We will store this <code>toolchain.cmake</code> file on our <code>$HOME</code> directory.
Line 17: Line 19:


*We will store '''a build of Qt 6 for host machine''' on <code>~/qt-host</code>. This is necessary because when building a Qt for our Raspberry Pi target, we will refer to this build of Qt.
*We will store '''a build of Qt 6 for host machine''' on <code>~/qt-host</code>. This is necessary because when building a Qt for our Raspberry Pi target, we will refer to this build of Qt.
*Finally, our '''Qt 6 build for Raspberry Pi''' will be stored on <code>~/qt-raspi</code> in the host machine temporarily before we move it to a location in our Raspberry Pi (for example, <code>/usr/local/Qt 6</code>).
*Finally, our '''Qt 6 build for Raspberry Pi''' will be stored on <code>~/qt-raspi</code> in the host machine temporarily before we move it to a location in our Raspberry Pi (for example, <code>/usr/local/qt6</code>).


Following the best practices of building C/C++ applications, we also want to make sure that our final build directories are clean from all build configuration files that CMake will create during the configuration phase. For that, we will store '''build configuration files''' on <code>~/qthost-build</code> for configuration files of Qt for our host machine and <code>~/qtpi-build</code> for configuration files of Qt for Raspberry Pi.
Following the best practices of building C/C++ applications, we also want to make sure that our final build directories are clean from all build configuration files that CMake will create during the configuration phase. For that, we will store '''build configuration files''' on <code>~/qt-hostbuild</code> for configuration files of Qt for our host machine and <code>~/qtpi-build</code> for configuration files of Qt for Raspberry Pi.


After we decided these locations, let's create them all!
After we decided these locations, let's create them all!
Line 26: Line 28:
$ cd ~
$ cd ~
$ mkdir rpi-sysroot rpi-sysroot/usr rpi-sysroot/opt
$ mkdir rpi-sysroot rpi-sysroot/usr rpi-sysroot/opt
$ mkdir qt-host qt-raspi qthost-build qtpi-build
$ mkdir qt-host qt-raspi qt-hostbuild qtpi-build
</syntaxhighlight>The second line will create the sysroot directory with its necessary subfolder, while the third line will create the directory for the rest of locations defined previously.
</syntaxhighlight>The second line will create the sysroot directory with its necessary subfolder, while the third line will create the directory for the rest of locations defined previously.


==Installing Raspberry Pi OS==
==Installing Raspberry Pi OS==
Let's now install an image of Raspberry Pi OS. You can download the latest Raspberry Pi Imager from https://www.raspberrypi.com/software/, and then flash an image of Raspberry Pi OS to your microSD card with only a few clicks on the Imager's GUI. This one should be straightforward.
Let's now download and install/flash an image of Raspberry Pi OS to a microSD card. The easiest way to do this is to use the official Raspberry Pi Imager. You can download the latest Imager from https://www.raspberrypi.com/software/, and then flash an image of Raspberry Pi OS to your microSD card with only a few clicks on the Imager's GUI.


==Setup Raspberry Pi==
==Setting up Raspberry Pi==
After successfully flashing an image of Raspberry Pi OS to a microSD card, insert it into the microSD card reader of our Raspberry Pi, connect a keyboard and monitor to the device and boot it up. The system will ask for a basic first-time setup. Please follow the setup according to the instruction.
After successfully flashing an image of Raspberry Pi OS to a microSD card, insert it into the microSD card reader of our Raspberry Pi, connect a keyboard and monitor to the device and boot it up. The system will ask for a basic first-time setup. Please follow the setup according to the instruction.


After successfully setting up the device, the system will reboot to the home desktop. From here, open Terminal. We want to install a few packages needed by Qt 6.
After successfully setting up the device, the system will reboot to the home desktop. From here, open Terminal. We want to set our Raspberry Pi to be able to run Qt apps.


===Install Dependencies===
===Install Dependencies===
Line 48: Line 50:
</syntaxhighlight>This will install a bunch of libraries from the Debian repository that Qt needed to deploy various kinds of applications on Raspberry Pi.
</syntaxhighlight>This will install a bunch of libraries from the Debian repository that Qt needed to deploy various kinds of applications on Raspberry Pi.


We also will make a directory that will hold Qt binaries for Raspberry Pi, as specified in the previous section. Actually this can be done at every time before transferring Qt 6 build from host machine to the Raspberry Pi, but in this guide we will create it at this stage
We also will make a directory that will hold the Qt installation targeting Raspberry Pi, as specified in the previous section. Actually, this can be done at every time before sending Qt 6 build from host machine to the Raspberry Pi, but in this guide we will create it now:


<code>sudo mkdir /usr/local/Qt 6</code>
<code>$ sudo mkdir /usr/local/qt6</code>


==Setup Host Machine==
==Setting up Host Machine==
Let's leave the Raspberry Pi for a moment and move to our host machine. As mentioned in the Introduction section, the steps below was tested for '''Ubuntu 20.04''' running on a host machine with '''x86_64''' architecture, but it should works with any Debian-based distributions running on x86_64 architecture.


===Update System===
===Update System===
Line 63: Line 66:


Next, we will install some more package dependencies. Some of these packages are build tools needed to compile Qt 6 that you might or might not have.<syntaxhighlight lang="bash">
Next, we will install some more package dependencies. Some of these packages are build tools needed to compile Qt 6 that you might or might not have.<syntaxhighlight lang="bash">
$ sudo apt-get install make build-essential libclang-dev 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
$ sudo apt-get install 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
</syntaxhighlight>
</syntaxhighlight>


Line 72: Line 75:


You can also download the cross-compiler from somewhere on the internet. If you want to do so, my recommendation is to check the [https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/downloads ARM official download page] and choose which one fits your purpose.
You can also download the cross-compiler from somewhere on the internet. If you want to do so, my recommendation is to check the [https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/downloads ARM official download page] and choose which one fits your purpose.
==Setup SSH Connection==
==Setting up SSH Connection==
We also want to setup an SSH connection between Raspberry Pi and a host machine. This is needed to transfer data between both devices. [https://www.raspberrypi.com/documentation/computers/remote-access.html#ssh The official Raspberry Pi documentation page] provided a very detailed and thorough instruction on how to setup SSH between Raspberry Pi and other device, so you can refer this step into that page.
We also want to setup an SSH connection between Raspberry Pi and a host machine. This is needed to transfer data between both devices. [https://www.raspberrypi.com/documentation/computers/remote-access.html#ssh The official Raspberry Pi documentation page] provided a very detailed and thorough instruction on how to setup SSH between Raspberry Pi and other device, so you can refer this step into that page.


==Build Sysroot==
==Building Sysroot from Device==
We need to build a Sysroot from our own Raspberry Pi device in the host machine. To do this, we will copy and paste a few directories from Raspberry Pi using rsync through SSH. If rsync is not installed yet in your host machine, simply install it by executing <code>sudo apt install rsync</code>.
We need to build a Sysroot from our own Raspberry Pi device in the host machine. To do this, we will copy and paste a few directories from Raspberry Pi using rsync through SSH. If rsync is not installed yet in your host machine, simply install it by executing <code>sudo apt install rsync</code>.


After the host machine and Raspberry Pi is connected,<syntaxhighlight lang="bash">
After the host machine and Raspberry Pi is connected,<syntaxhighlight lang="bash">
$ rsync -avz --rsync-path="sudo rsync" --delete <pi_username>@<pi_ip_address>:/lib rpi-sysroot
$ cd $HOME
$ rsync -avz --rsync-path="sudo rsync" --delete <pi_username>@<pi_ip_address>:/usr/include rpi-sysroot/usr
$ rsync -avzS --rsync-path="rsync" --delete <pi_username>@<pi_ip_address>:/lib/* rpi-sysroot/lib
$ rsync -avz --rsync-path="sudo rsync" --delete <pi_username>@<pi_ip_address>:/usr/lib rpi-sysroot/usr
$ mkdir $HOME/rpi-sysroot/usr
$ rsync -avz --rsync-path="sudo rsync" --delete <pi_username>@<pi_ip_address>:/opt/vc rpi-sysroot/opt
$ rsync -avzS --rsync-path="rsync" --delete <pi_username>@<pi_ip_address>:/usr/include/* rpi-sysroot/usr/include
$ rsync -avzS --rsync-path="rsync" --delete <pi_username>@<pi_ip_address>:/usr/lib/* rpi-sysroot/usr/lib
$ mkdir $HOME/rpi-sysroot/opt
$ rsync -avzS --rsync-path="rsync" --delete <pi_username>@<pi_ip_address>:/opt/vc rpi-sysroot/opt/vc
</syntaxhighlight>Change <code><pi_username></code> and <code><pi_ip_address></code> with your Raspberry Pi username and IP Address, respectively.  
</syntaxhighlight>Change <code><pi_username></code> and <code><pi_ip_address></code> with your Raspberry Pi username and IP Address, respectively.  
'''Quick explanation''':
This line uses <code>rsync</code> to copy all the files and subdirectories in the <code>/lib</code> directory on the Raspberry Pi to a local directory called <code>rpi-sysroot/lib</code>. The <code>-a</code> option preserves file permissions, ownership, and timestamps, <code>-v</code> enables verbose output, <code>-z</code> compresses the data during transfer, <code>-S</code> optimizes data transfer for sparse files, and <code>--delete</code> removes files from the destination that are no longer present on the source. The <code>--rsync-path</code> option is used to specify the path to the <code>rsync</code> executable on the Raspberry Pi. 


'''Note:''' Your Raspberry Pi might not have a directory named <code>/opt/vc</code>, and it is fine. Usually this directory contains proprietary Broadcom libraries, but during the testing the author did not find any issue with the lack of this directory.  
'''Note:''' Your Raspberry Pi might not have a directory named <code>/opt/vc</code>, and it is fine. Usually this directory contains proprietary Broadcom libraries, but during the testing the author did not find any issue with the lack of this directory.  
Line 92: Line 102:
$ symlinks -rc rpi-sysroot
$ symlinks -rc rpi-sysroot
</syntaxhighlight>Check the [https://linux.die.net/man/8/symlinks symlinks documentation] to learn more.
</syntaxhighlight>Check the [https://linux.die.net/man/8/symlinks symlinks documentation] to learn more.
Note: Make sure that there are no errors in the terminal. You may need to use '''sudo''' here.


==Building Qt 6==
==Building Qt 6==
Line 97: Line 109:


===Building Qt 6 for Host Machine===
===Building Qt 6 for Host Machine===
For the detailed step-by-steps of this process, I will refer to this great [https://wiki.qt.io/Building_Qt_6_from_Git wiki article] that explained the whole process in detail. One thing to remember is that we will install Qt on <code>~/qt-host</code>, and we want to store all build configuration files on <code>~/qt-hostbuild</code>.  
<syntaxhighlight lang="bash">
cd $HOME
git clone "https://codereview.qt-project.org/qt/qt5"
cd qt5/
git checkout 6.4.0
perl init-repository -f
cd ..
mkdir $HOME/qt-hostbuild
cd $HOME/qt-hostbuild/
cmake ../qt5/ -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 .


Notice that you can also choose to install only Qt submodules that you need. This is good if you have a limited space in your host machine. At minimum, you only need the <code>qtbase</code> submodule to be able to develop and run a simple Qt Widget application, but to access other Qt features you need to install other Qt submodules. In this guide, I installed the following submodules: <code>qtbase,qtsvg,qtdeclarative,qtquick3d,qtshadertools,qtimageformats,qtquicktimeline</code>.  
</syntaxhighlight>
One thing to remember is that, in this guide, we will install Qt on folder <code>~/qt-host</code> and we will store all build configuration files on <code>~/qt-hostbuild</code>.  


The rest of the guide is quite straightforward, so I will leave you to read it.  
Notice that you can also choose to install only Qt submodules that you need. This is good if you have a limited space in your host machine. At minimum, you only need the <code>qtbase</code> submodule to be able to develop and run a simple Qt Widget application. But to access other Qt features you need to install other or all Qt submodules.


===Building Qt 6 for Target Device===
===Building Qt 6 for Target Device ===
This is the interesting part. We will compile Qt 6 on a host machine and install it to the Raspberry Pi. The instruction for configuring and building are similar to building Qt 6 for host machine, but there are a few things we need to do to get the intended results.
This is the interesting part. We will compile Qt 6 on a host machine and install it to the Raspberry Pi. The instruction for configuring and building are similar to building Qt 6 for host machine, but there are a few things we need to do to get the intended results.


Line 122: Line 146:
set(ENV{PKG_CONFIG_SYSROOT_DIR} ${CMAKE_SYSROOT})
set(ENV{PKG_CONFIG_SYSROOT_DIR} ${CMAKE_SYSROOT})


# if you use other version of gcc and g++ than gcc/g++ 9, you can change the following variables
# if you use other version of gcc and g++ than gcc/g++ 9, you must change the following variables
set(CMAKE_C_COMPILER /usr/bin/aarch64-linux-gnu-gcc-9)
set(CMAKE_C_COMPILER /usr/bin/aarch64-linux-gnu-gcc-9)
set(CMAKE_CXX_COMPILER /usr/bin/aarch64-linux-gnu-g++-9)
set(CMAKE_CXX_COMPILER /usr/bin/aarch64-linux-gnu-g++-9)
Line 144: Line 168:


function(cmake_initialize_per_config_variable _PREFIX _DOCSTRING)
function(cmake_initialize_per_config_variable _PREFIX _DOCSTRING)
    if (_PREFIX MATCHES "CMAKE_(C|CXX|ASM)_FLAGS")
  if (_PREFIX MATCHES "CMAKE_(C|CXX|ASM)_FLAGS")
        set(CMAKE_${CMAKE_MATCH_1}_FLAGS_INIT "${QT_COMPILER_FLAGS}")
    set(CMAKE_${CMAKE_MATCH_1}_FLAGS_INIT "${QT_COMPILER_FLAGS}")
          
          
        foreach (config DEBUG RELEASE MINSIZEREL RELWITHDEBINFO)
    foreach (config DEBUG RELEASE MINSIZEREL RELWITHDEBINFO)
            if (DEFINED QT_COMPILER_FLAGS_${config})
      if (DEFINED QT_COMPILER_FLAGS_${config})
                set(CMAKE_${CMAKE_MATCH_1}_FLAGS_${config}_INIT "${QT_COMPILER_FLAGS_${config}}")
        set(CMAKE_${CMAKE_MATCH_1}_FLAGS_${config}_INIT "${QT_COMPILER_FLAGS_${config}}")
            endif()
      endif()
        endforeach()
    endforeach()
    endif()
  endif()




    if (_PREFIX MATCHES "CMAKE_(SHARED|MODULE|EXE)_LINKER_FLAGS")
  if (_PREFIX MATCHES "CMAKE_(SHARED|MODULE|EXE)_LINKER_FLAGS")
        foreach (config SHARED MODULE EXE)
    foreach (config SHARED MODULE EXE)
            set(CMAKE_${config}_LINKER_FLAGS_INIT "${QT_LINKER_FLAGS}")
      set(CMAKE_${config}_LINKER_FLAGS_INIT "${QT_LINKER_FLAGS}")
        endforeach()
    endforeach()
    endif()
  endif()


    _cmake_initialize_per_config_variable(${ARGV})
  _cmake_initialize_per_config_variable(${ARGV})
endfunction()
endfunction()


Line 174: Line 198:
set(OPENGL_INCLUDE_DIR ${GL_INC_DIR})
set(OPENGL_INCLUDE_DIR ${GL_INC_DIR})
set(OPENGL_opengl_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libOpenGL.so)
set(OPENGL_opengl_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libOpenGL.so)
set(GLESv2_INCLUDE_DIR ${GL_INC_DIR})
set(GLIB_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libGLESv2.so)


set(GLESv2_INCLUDE_DIR ${GL_INC_DIR})
set(GLESv2_INCLUDE_DIR ${GL_INC_DIR})
Line 189: Line 210:
set(XCB_XCB_INCLUDE_DIR ${GL_INC_DIR})
set(XCB_XCB_INCLUDE_DIR ${GL_INC_DIR})
set(XCB_XCB_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libxcb.so)
set(XCB_XCB_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libxcb.so)
set(XCB_RENDER_INCLUDE_DIR ${GL_INC_DIR})
set(XCB_RENDER_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libxcb-render.so)
set(XCB_SHAPE_INCLUDE_DIR ${GL_INC_DIR})
set(XCB_SHAPE_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libxcb-shape.so)
set(XCB_XFIXES_INCLUDE_DIR ${GL_INC_DIR})
set(XCB_XFIXES_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libxcb-xfixes.so)
set(XCB_SHM_INCLUDE_DIR ${GL_INC_DIR})
set(XCB_SHM_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libxcb-shm.so)
set(XCB_COMPOSITE_INCLUDE_DIR ${GL_INC_DIR})
set(XCB_COMPOSITE_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libxcb-composite.so)
set(XCB_CURSOR_INCLUDE_DIR ${GL_INC_DIR})
set(XCB_CURSOR_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libxcb-cursor.so)
set(XCB_DAMAGE_INCLUDE_DIR ${GL_INC_DIR})
set(XCB_DAMAGE_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libxcb-damage.so)
set(XCB_DPMS_INCLUDE_DIR ${GL_INC_DIR})
set(XCB_DPMS_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libxcb-dpms.so)
set(XCB_GLX_INCLUDE_DIR ${GL_INC_DIR})
set(XCB_GLX_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libxcb-glx.so)
set(XCB_ICCCM_INCLUDE_DIR ${GL_INC_DIR})
set(XCB_ICCCM_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libxcb-icccm.so)
set(XCB_IMAGE_INCLUDE_DIR ${GL_INC_DIR})
set(XCB_IMAGE_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libxcb-image.so)
set(XCB_KEYSYMS_INCLUDE_DIR ${GL_INC_DIR})
set(XCB_KEYSYMS_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libxcb-keysyms.so)
set(XCB_PRESENT_INCLUDE_DIR ${GL_INC_DIR})
set(XCB_PRESENT_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libxcb-present.so)
set(XCB_RANDR_INCLUDE_DIR ${GL_INC_DIR})
set(XCB_RANDR_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libxcb-randr.so)
set(XCB_RECORD_INCLUDE_DIR ${GL_INC_DIR})
set(XCB_RECORD_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libxcb-record.so)
set(XCB_RENDERUTIL_INCLUDE_DIR ${GL_INC_DIR})
set(XCB_RENDERUTIL_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libxcb-render-util.so)
set(XCB_RES_INCLUDE_DIR ${GL_INC_DIR})
set(XCB_RES_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libxcb-res.so)
set(XCB_SCREENSAVER_INCLUDE_DIR ${GL_INC_DIR})
set(XCB_SCREENSAVER_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libxcb-screensaver.so)
set(XCB_SYNC_INCLUDE_DIR ${GL_INC_DIR})
set(XCB_SYNC_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libxcb-sync.so)
set(XCB_UTIL_INCLUDE_DIR ${GL_INC_DIR})
set(XCB_UTIL_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libxcb-util.so)
set(XCB_XF86DRI_INCLUDE_DIR ${GL_INC_DIR})
set(XCB_XF86DRI_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libxcb-xf86dri.so)
set(XCB_XKB_INCLUDE_DIR ${GL_INC_DIR})
set(XCB_XKB_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libxcb-xkb.so)
set(XCB_XTEST_INCLUDE_DIR ${GL_INC_DIR})
set(XCB_XTEST_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libxcb-xtest.so)
set(XCB_XV_INCLUDE_DIR ${GL_INC_DIR})
set(XCB_XV_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libxcb-xv.so)
set(XCB_XVMC_INCLUDE_DIR ${GL_INC_DIR})
set(XCB_XVMC_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libxcb-xvmc.so)
set(XCB_XINPUT_INCLUDE_DIR ${GL_INC_DIR})
set(XCB_XINPUT_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libxcb-xinput.so)
set(XKB_INCLUDE_DIR ${GL_INC_DIR})
set(XKB_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libxkbcommon.so)
set(XCB_DRI2_INCLUDE_DIR ${GL_INC_DIR})
set(XCB_DRI2_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libxcb-dri2.so)
set(XCB_DRI3_INCLUDE_DIR ${GL_INC_DIR})
set(XCB_DRI3_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libxcb-dri3.so)
set(X11_XCB_INCLUDE_DIR ${GL_INC_DIR})
set(X11_XCB_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libX11-xcb.so)
set(X11_X11_INCLUDE_PATH ${GL_INC_DIR})
set(X11_X11_LIB ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libX11.so)
</syntaxhighlight>Now that we have our toolchain, as well as the cross compiler and sysroot, we can continue with configuring and building Qt for our Raspberry Pi!
</syntaxhighlight>Now that we have our toolchain, as well as the cross compiler and sysroot, we can continue with configuring and building Qt for our Raspberry Pi!


Line 288: Line 216:
<code>$ cd ~/qtpi-build</code>
<code>$ cd ~/qtpi-build</code>


Now, we can run the configuration command inside this directory. <syntaxhighlight lang="shell">
Now inside this directory, we can run this configuration command<syntaxhighlight lang="shell">
$ ../qt5/configure -release -opengl es2 -nomake examples -nomake tests -qt-host-path $HOME/qt-host -extprefix $HOME/qt-raspi -prefix /usr/local/qt6 -- -DCMAKE_TOOLCHAIN_FILE=$HOME/toolchain.cmake -DQT_FEATURE_brotli=OFF -DQT_FEATURE_libudev=OFF -DQT_QMAKE_TARGET_MKSPEC=devices/linux-rasp-pi4-v3d-g++ -DQT_FEATURE_xcb=ON -DFEATURE_xcb_xlib=ON -DQT_FEATURE_xlib=ON -DFEATURE_dbus=OFF
$ ../qt5/configure -release -opengl es2 -nomake examples -nomake tests -qt-host-path $HOME/qt-host -extprefix $HOME/qt-raspi -prefix /usr/local/qt6 -device linux-rasp-pi4-aarch64 -device-option CROSS_COMPILE=aarch64-linux-gnu- -- -DCMAKE_TOOLCHAIN_FILE=$HOME/toolchain.cmake -DQT_FEATURE_xcb=ON -DFEATURE_xcb_xlib=ON -DQT_FEATURE_xlib=ON
</syntaxhighlight>Command above will run the configuration file that came with the Qt source code and complementing it with the toolchain that we just created. This is equivalent to this CMake command:<syntaxhighlight lang="shell">
</syntaxhighlight>The command above will run a configuration script that came with the Qt source code and complementing it with the CMake toolchain that we just created. It is equivalent to this CMake command: <syntaxhighlight lang="shell">
$ cmake ../qt5/ -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-raspi -DCMAKE_INSTALL_PREFIX=/usr/local/qt6 -DCMAKE_TOOLCHAIN_FILE=$HOME/toolchain.cmake -DQT_FEATURE_brotli=OFF -DQT_FEATURE_libudev=OFF -DQT_QMAKE_TARGET_MKSPEC=devices/linux-rasp-pi4-v3d-g++ -DQT_FEATURE_xcb=ON -DFEATURE_xcb_xlib=ON -DQT_FEATURE_xlib=ON -DFEATURE_dbus=OFF
$ cmake ../qt5/ -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-raspi -DCMAKE_INSTALL_PREFIX=/usr/local/qt6 -DCMAKE_TOOLCHAIN_FILE=$HOME/toolchain.cmake -DQT_QMAKE_TARGET_MKSPEC=devices/linux-rasp-pi4-aarch64 -DQT_FEATURE_xcb=ON -DFEATURE_xcb_xlib=ON -DQT_FEATURE_xlib=ON
</syntaxhighlight>These commands will configure Qt to be installed on <code>qt-raspi</code> folder on the host machine, with expectation that it will populate <code>usr/local/Qt 6</code> on the Raspberry Pi. The Qt installation from this build will be configured to run on the ARM64 architecture. You can make sure by running <code>file ~/qt-raspi/bin/qt-cmake</code> on terminal after installing.
</syntaxhighlight>'''Note:''' Both commands will do exactly the same thing: configuring Qt to be installed in folder <code>qt-raspi</code> on the host machine, with expectation that it will populate <code>usr/local/qt6</code> on the Raspberry Pi. If you already run the first command, then there is no need to run the second command.  


Then proceeds with building and installing. <syntaxhighlight lang="shell">
The Qt installation from this build will be suitable to run Qt ARM64 applications using X11 as the windowing system. You can check [https://doc.qt.io/qt-6/linux-requirements.html this page] from Qt Docs to learn more about xcb plugins. 
 
In more advanced cases, check [https://doc.qt.io/qt-6/embedded-linux.html this page] from Qt Documentation if you want to use other windowing systems such as Wayland or EGLFS. 
 
We can then proceed with building and installing. <syntaxhighlight lang="shell">
$ cmake --build . --parallel 4
$ cmake --build . --parallel 4
$ cmake --install .
$ cmake --install .
Line 303: Line 235:
To use <code>rsync</code>:
To use <code>rsync</code>:


<code>rsync -avz --rsync-path="sudo rsync" /path/to/qt-raspi <pi_username>@<pi_ip_address>:/usr/local/Qt 6</code>
<code>rsync -avz --rsync-path="sudo rsync" /path/to/qt-raspi/* <pi_username>@<pi_ip_address>:/usr/local/qt6</code>


To use <code>scp</code>:<syntaxhighlight lang="shell">
To use <code>scp</code>:<syntaxhighlight lang="shell">
Line 319: Line 251:
</syntaxhighlight>This is the most important environment variable that we need to set, as it will enable Raspberry Pi to find our Qt 6 library path. If you intend to run the application through SSH from your host machine and see the application running on a monitor that is connected to the Raspberry Pi, you need to also set <code>DISPLAY</code> variable with <code>export DISPLAY=:0</code> in addition to the commands above.
</syntaxhighlight>This is the most important environment variable that we need to set, as it will enable Raspberry Pi to find our Qt 6 library path. If you intend to run the application through SSH from your host machine and see the application running on a monitor that is connected to the Raspberry Pi, you need to also set <code>DISPLAY</code> variable with <code>export DISPLAY=:0</code> in addition to the commands above.


==Compiling an Application==
==Compiling and Running Qt Project==
Now we can compile a Qt application and get a binary that is suitable to deploy on Raspberry Pi.
Now we can compile a Qt application and get a binary that is suitable to deploy on Raspberry Pi. There are two methods to achieve this: with Terminal or with Qt Creator. For both methods, we will use the analogclock example project.


We can try with one of the examples from the Qt source code.<syntaxhighlight lang="shell">
===With Terminal===
<syntaxhighlight lang="shell">
$ cd ~/qt5/qtbase/examples/gui/analogclock/
$ cd ~/qt5/qtbase/examples/gui/analogclock/
$ ls ## make sure there is a CMakeLists.txt file
$ ls ## make sure there is a CMakeLists.txt file
Line 331: Line 264:
$ cmake --build . --parallel 4
$ cmake --build . --parallel 4
$ cmake --install .
$ cmake --install .
</syntaxhighlight>We now have the binary of our application ready to be run on the Raspberry Pi. We can then send the binary to our Raspberry Pi and run the application (either through SSH or directly on the Raspberry Pi with keyboard and mouse connected).<syntaxhighlight lang="shell">
</syntaxhighlight>We now have the binary of our application ready to be run on the Raspberry Pi. We can then send the binary to our Raspberry Pi using either scp or rsync and run the application (either through SSH or directly on the Raspberry Pi with keyboard and mouse connected).<syntaxhighlight lang="shell">
$ scp -r gui_analogclock <pi_username>@<pi_ip_address>:/home/pi
$ scp -r gui_analogclock <pi_username>@<pi_ip_address>:/home/pi
$ ssh <pi_username>@<pi_ip_address>
$ ssh <pi_username>@<pi_ip_address>
Line 337: Line 270:
$ ./gui_analogclock
$ ./gui_analogclock
</syntaxhighlight>
</syntaxhighlight>
===With Qt Creator ===
Open Qt Creator. On the main toolbar, go to '''Tools > Options'''.
*Inside '''Kits''' menu, select '''Qt Versions''' then click '''Add.''' Locate the ''qmake'' file inside <code>~/qt-raspi/bin</code>. Then click '''Apply.'''
* We also have to make sure that our cross-compiler is already detected by the Qt Creator. Select '''Compilers''' tab, then check if our compiler (''aarch64-linux-gnu-gcc'' and ''aarch64-linux-gnu-g++'') is already listed. If not, you must add a GCC compiler and locate the location of the compiler. Then click '''Apply.'''
Another component that we need to set in Qt Creator is our own device. Still in '''Options''' menu, select '''Devices''' on the sidebar.
*Under '''Devices''' tab, select '''Add'''. Inside the '''Device Configuration Wizard Selection''', select '''Generic Linux Device''', then click '''Start Wizard.'''
*Specify an identifiable name, your Raspberry Pi IP address that you use to SSH, and the user name of the Raspberry Pi.
*You will be prompted to deploy public key to the target device. This will speed up the deployment process as you don't need to input a password every time Qt Creator is attempting to send file to the target.
*Click '''Next''', and then '''Finish.''' Qt Creator will test the connection upon finishing and will tell you whether a connection to the target device can be established.
After setting all the necessary components, we can now make a new kit. Go back to '''Kits''' menu on the sidebar.
*Click '''Add.''' Name your kit with a distinguishable name.
* On '''Device type''' option, select '''Generic Linux Device.'''
*On '''Device''' option, select the name of the device that you already set in previous step.
*On '''Compiler''' option, select the cross-compiler (in this guide, ''aarch64-linux-gnu-gcc'' and ''aarch64-linux-gnu-g++'') for both C and C++ compilers.
*On '''Qt version''' option, select the name of the cross-compiled Qt version that you set in the previous step.
*On '''CMake Configuration''' option, click Change and add <code>-DCMAKE_TOOLCHAIN_FILE:UNINITIALIZED=/path/to/qt-raspi/lib/cmake/Qt6/qt.toolchain.cmake</code> to the end of the line. Apply and then OK.
Click '''Apply''' and then '''OK.''' Now go to '''File > Open File or Project''', then open ''CMakeLists.txt'' of the project (for this example, it is located in  <code>~/qt5/qtbase/examples/gui/analogclock/CMakeLists.txt</code>). Qt Creator will try to configure the project using CMake.
Before running and deploying the application, we need to customise a few deployment/run settings. Go to Projects sidebar menu, then under the kit name click Run.
*Under '''Run''' section, on '''X11 Forwarding''' tick "Forward to local display" checkbox and input <code>:0</code> to the text field. This will forward the application window to be opened on host device rather than on Raspberry Pi monitor (the application instance is still running on Raspberry Pi).
* Sometimes Qt Creator does not fetch the device environment properly. This would cause the library of our Qt installation undetected by the Qt application. Under '''Environment''' section, click '''Details''' to expand the environment option. Click '''Add''', then on '''Variable''' column type <code>LD_LIBRARY_PATH</code>. On the '''Value''' column, type the location of Qt installation inside the Raspberry Pi preceded by a colon (:), in our case it is <code>:/usr/local/qt6/lib/</code>.
After configuring these settings. run the application (Ctrl+R). The application window should be displayed on host's monitor display.
==Known Issues==
===Issue with libdbus===
During testing, we found a compilation error related to <code>libdbus</code>.<syntaxhighlight lang="shell">
...
/usr/lib/gcc-cross/aarch64-linux-gnu/9/../../../../aarch64-linux-gnu/bin/ld: /home/uraihan/rpi-sysroot/usr/lib/aarch64-linux-gnu/libdbus-1.a(libdbus_1_la-dbus-message.o): relocation R_AARCH64_ADR_PREL_PG_HI21 against symbol `dbus_message_unref' which may bind externally can not be used when making a shared object; recompile with -fPIC
...
</syntaxhighlight>This could happen when the libdbus inside the initial sysroot is not a dynamically shared objects. This is normally fixable by doing a clean reinstall of Raspberry Pi OS (reformat microSD card, then install Raspberry Pi OS again through the Imager). Another recommendation is to try to reinstall <code>libdbus-1-dev</code> package. But if the problem persists and you don't need dbus, you can disable the dbus feature by adding <code>-DFEATURE_dbus=OFF</code> flag in the CMake configuration command.
Another walkaround worth trying is to change 
from:<syntaxhighlight lang="cmake">
set(QT_LINKER_FLAGS "-Wl,-O1 -Wl,--hash-style=gnu -Wl,--as-needed")
</syntaxhighlight>to<syntaxhighlight lang="cmake">
set(QT_LINKER_FLAGS "-Wl,-O1 -Wl,--hash-style=gnu -Wl,--as-needed -ldbus-1")
</syntaxhighlight>Additionally make sure that link to rpi-sysroot/usr/lib/aarch64-linux-gnu/libdbus-1.so is valid. It should be linking to libdbus-1.so.3.19.13 in the same directory.
Note: You'll need DBus to have Bluetooth module working.
===Issue with -fuse-ld=lld not found===
Add "'''-Wl,-fuse-ld=gold" to QT_LINKER_FLAGS.'''<syntaxhighlight lang="cmake">
set(QT_LINKER_FLAGS "-Wl,-O1 -Wl,--hash-style=gnu -Wl,--as-needed -Wl,-fuse-ld=gold")
</syntaxhighlight>
===Issue with QtWayland not compiling:===
Add -skip qtwayland to ./configure script like so:<syntaxhighlight lang="shell">
$ ../qt5/configure -release -opengl es2 -nomake examples -nomake tests -skip qtwayland -qt-host-path $HOME/qt-host -extprefix $HOME/qt-raspi -prefix /usr/local/qt6 -device linux-rasp-pi3-g++ -device-option CROSS_COMPILE=aarch64-linux-gnu- -- -DCMAKE_TOOLCHAIN_FILE=$HOME/toolchain.cmake -DQT_FEATURE_xcb=ON -DFEATURE_xcb_xlib=ON -DQT_FEATURE_xlib=ON
</syntaxhighlight>
===Environment variables are not permanent:===
Environment variables are not persistent during ssh session if you use export command.
You can add this in ~/.bashrc file<syntaxhighlight lang="shell">
$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/qt6/lib/
</syntaxhighlight>
===Compiling app in QtCreator for device linux-rasp-pi3-g++ throws various errors:===
Open qt-raspi/mkspecs/devices/linux-rasp-pi3-g++/qmake.conf
Remove -mfpu=crypto-neon-fp-armv8
Change include(../common/linux_arm_device_post.conf) to include(../common/linux_device_post.conf)
===Various libraries not found when launching app on Raspberry Pi:===
Make sure that directory '''/usr/local/qt6/lib/''' exists. You may need to change your LD_LIBRARY_PATH to '''/usr/local/qt6/qt-raspi/lib/'''


==Further Readings==
==Further Readings==
For further readings, go check:


*Qt Docs: [https://doc-snapshots.qt.io/qt6-6.3/embedded-linux.html Qt for Embedded Device] (alongside a guideline on [https://doc-snapshots.qt.io/qt6-6.3/configure-linux-device.html Configuring an Embedded Linux Device]).
*Qt Docs:  
**[https://doc-snapshots.qt.io/qt6-6.3/embedded-linux.html Qt for Embedded Device] (alongside a guideline on [https://doc-snapshots.qt.io/qt6-6.3/configure-linux-device.html Configuring an Embedded Linux Device]).
**[https://doc.qt.io/qt-6/linux-requirements.html XCB plugins for running Qt on X11]
*CMake Docs: [https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html cmake-toolchains].
*CMake Docs: [https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html cmake-toolchains].

Revision as of 21:51, 4 February 2024

Introduction

This is a step-by-step guide to cross-compile Qt 6 for Raspberry Pi OS. The end result of this guide allows you to compile Qt 6 applications on a host machine and deploy it on the Raspberry Pi running a Raspberry Pi OS image. The instructions in this guide are targeted for beginners, but it should be easy to follow for everyone.

The steps on this guide was tested on Ubuntu 20.04 targeting Raspberry Pi 4 Model B 2GB (64-bit) running Raspberry Pi OS, but it should work with any Debian-based OS.

For an older guide, check this Qt Wiki guide or this one.

Getting Started: Planning Phase

Before we get our hands dirty and mess around with the configurations, let's familiarise ourselves with essential components needed to cross-compile Qt and decide which directory names we want to use to store these components. This way, we can easily track the location of these important components when we are typing commands in the terminal.

At its most basic setup, we need at least the following components in a host machine to cross-compile Qt:

  • Sysroot, which basically is the scaled down version of our target's filesystem inside our host machine. For this example, we will name this
    ~/rpi-sysroot
    
  • A cross-compiler on host machine. To make it as straightforward as possible, for this guide we will use the cross-compiler for Aarch64 from the official Ubuntu package repository.
  • Qt source code with its submodules, at least the qtbase submodule. For this guide, we will download Qt source code from Qt official GitHub repository. By default, it will be stored in
    ~/qt5
    
    .
  • CMake toolchain. CMake is the recommended build generator for Qt 6. Inside this toolchain file, we will specify in detail about the exact specifications of our cross-compiled Qt build. We will store this
    toolchain.cmake
    
    file on our
    $HOME
    
    directory.

We also have to decide the locations of our Qt builds, one for host machine and one for our Raspberry Pi in our PC before we transfer it to the Raspberry Pi.

  • We will store a build of Qt 6 for host machine on
    ~/qt-host
    
    . This is necessary because when building a Qt for our Raspberry Pi target, we will refer to this build of Qt.
  • Finally, our Qt 6 build for Raspberry Pi will be stored on
    ~/qt-raspi
    
    in the host machine temporarily before we move it to a location in our Raspberry Pi (for example,
    /usr/local/qt6
    
    ).

Following the best practices of building C/C++ applications, we also want to make sure that our final build directories are clean from all build configuration files that CMake will create during the configuration phase. For that, we will store build configuration files on

~/qt-hostbuild

for configuration files of Qt for our host machine and

~/qtpi-build

for configuration files of Qt for Raspberry Pi.

After we decided these locations, let's create them all!

On host machine,

$ cd ~
$ mkdir rpi-sysroot rpi-sysroot/usr rpi-sysroot/opt
$ mkdir qt-host qt-raspi qt-hostbuild qtpi-build

The second line will create the sysroot directory with its necessary subfolder, while the third line will create the directory for the rest of locations defined previously.

Installing Raspberry Pi OS

Let's now download and install/flash an image of Raspberry Pi OS to a microSD card. The easiest way to do this is to use the official Raspberry Pi Imager. You can download the latest Imager from https://www.raspberrypi.com/software/, and then flash an image of Raspberry Pi OS to your microSD card with only a few clicks on the Imager's GUI.

Setting up Raspberry Pi

After successfully flashing an image of Raspberry Pi OS to a microSD card, insert it into the microSD card reader of our Raspberry Pi, connect a keyboard and monitor to the device and boot it up. The system will ask for a basic first-time setup. Please follow the setup according to the instruction.

After successfully setting up the device, the system will reboot to the home desktop. From here, open Terminal. We want to set our Raspberry Pi to be able to run Qt apps.

Install Dependencies

Update and upgrade to the newest version.

$ sudo apt update
$ sudo apt full-upgrade
$ sudo reboot

Next, install the package dependencies.

$ sudo apt-get install 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 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

$ sudo apt-get install 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

This will install a bunch of libraries from the Debian repository that Qt needed to deploy various kinds of applications on Raspberry Pi.

We also will make a directory that will hold the Qt installation targeting Raspberry Pi, as specified in the previous section. Actually, this can be done at every time before sending Qt 6 build from host machine to the Raspberry Pi, but in this guide we will create it now:

$ sudo mkdir /usr/local/qt6

Setting up Host Machine

Let's leave the Raspberry Pi for a moment and move to our host machine. As mentioned in the Introduction section, the steps below was tested for Ubuntu 20.04 running on a host machine with x86_64 architecture, but it should works with any Debian-based distributions running on x86_64 architecture.

Update System

Let's configure our Ubuntu/Debian host machine. First, let's update our packages to the latest version.

$ sudo apt update
$ sudo apt upgrade

Install Qt Dependencies

Next, we will install some more package dependencies. Some of these packages are build tools needed to compile Qt 6 that you might or might not have.

$ sudo apt-get install 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

Install Cross Compiler

Next, let's get our cross-compiler. For this guide, we will install it from the Ubuntu/Debian package repository as this is the easiest way to get an ARM64 cross-compiler.

$ sudo apt install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu

You can also download the cross-compiler from somewhere on the internet. If you want to do so, my recommendation is to check the ARM official download page and choose which one fits your purpose.

Setting up SSH Connection

We also want to setup an SSH connection between Raspberry Pi and a host machine. This is needed to transfer data between both devices. The official Raspberry Pi documentation page provided a very detailed and thorough instruction on how to setup SSH between Raspberry Pi and other device, so you can refer this step into that page.

Building Sysroot from Device

We need to build a Sysroot from our own Raspberry Pi device in the host machine. To do this, we will copy and paste a few directories from Raspberry Pi using rsync through SSH. If rsync is not installed yet in your host machine, simply install it by executing

sudo apt install rsync

. After the host machine and Raspberry Pi is connected,

$ cd $HOME
$ rsync -avzS --rsync-path="rsync" --delete <pi_username>@<pi_ip_address>:/lib/* rpi-sysroot/lib
$ mkdir $HOME/rpi-sysroot/usr
$ rsync -avzS --rsync-path="rsync" --delete <pi_username>@<pi_ip_address>:/usr/include/* rpi-sysroot/usr/include
$ rsync -avzS --rsync-path="rsync" --delete <pi_username>@<pi_ip_address>:/usr/lib/* rpi-sysroot/usr/lib
$ mkdir $HOME/rpi-sysroot/opt
$ rsync -avzS --rsync-path="rsync" --delete <pi_username>@<pi_ip_address>:/opt/vc rpi-sysroot/opt/vc

Change

<pi_username>

and

<pi_ip_address>

with your Raspberry Pi username and IP Address, respectively.

Quick explanation:

This line uses

rsync

to copy all the files and subdirectories in the

/lib

directory on the Raspberry Pi to a local directory called

rpi-sysroot/lib

. The

-a

option preserves file permissions, ownership, and timestamps,

-v

enables verbose output,

-z

compresses the data during transfer,

-S

optimizes data transfer for sparse files, and

--delete

removes files from the destination that are no longer present on the source. The

--rsync-path

option is used to specify the path to the

rsync

executable on the Raspberry Pi. Note: Your Raspberry Pi might not have a directory named

/opt/vc

, and it is fine. Usually this directory contains proprietary Broadcom libraries, but during the testing the author did not find any issue with the lack of this directory. After building our own sysroot, we need to fix absolute symbolic links that were broken during the syncing process by converting them to relative symlinks. The easiest, fastest way to do this is to use a command-line tool called

symlinks

, which is available from the Ubuntu official repository.

$ sudo apt install symlinks
$ cd ~
$ symlinks -rc rpi-sysroot

Check the symlinks documentation to learn more.

Note: Make sure that there are no errors in the terminal. You may need to use sudo here.

Building Qt 6

After all the setup process, finally we can start building Qt 6! This will be divided into two parts: Building Qt 6 for host machine, and building Qt 6 for our target device (i.e. the Raspberry Pi).

Building Qt 6 for Host Machine

cd $HOME
git clone "https://codereview.qt-project.org/qt/qt5"
cd qt5/
git checkout 6.4.0
perl init-repository -f
cd ..
mkdir $HOME/qt-hostbuild
cd $HOME/qt-hostbuild/
cmake ../qt5/ -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 .

One thing to remember is that, in this guide, we will install Qt on folder

~/qt-host

and we will store all build configuration files on

~/qt-hostbuild

. Notice that you can also choose to install only Qt submodules that you need. This is good if you have a limited space in your host machine. At minimum, you only need the

qtbase

submodule to be able to develop and run a simple Qt Widget application. But to access other Qt features you need to install other or all Qt submodules.

Building Qt 6 for Target Device

This is the interesting part. We will compile Qt 6 on a host machine and install it to the Raspberry Pi. The instruction for configuring and building are similar to building Qt 6 for host machine, but there are a few things we need to do to get the intended results.

Create a Toolchain File

First, we want to create our own CMake toolchain file. This file is important to link the right compilers and libraries so that CMake can produces the suitable Qt build for Raspberry Pi. For this guide, we will use the following CMake code and save it as

toolchain.cmake

.

cmake_minimum_required(VERSION 3.18)
include_guard(GLOBAL)

set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)

set(TARGET_SYSROOT /path/to/your/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})

# if you use other version of gcc and g++ than gcc/g++ 9, you must change the following variables
set(CMAKE_C_COMPILER /usr/bin/aarch64-linux-gnu-gcc-9)
set(CMAKE_CXX_COMPILER /usr/bin/aarch64-linux-gnu-g++-9)

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)

Now that we have our toolchain, as well as the cross compiler and sysroot, we can continue with configuring and building Qt for our Raspberry Pi! As decided in the planning process, we will put all build configuration files on

qtpi-build

, so let's

cd

into that directory:

$ cd ~/qtpi-build

Now inside this directory, we can run this configuration command:

$ ../qt5/configure -release -opengl es2 -nomake examples -nomake tests -qt-host-path $HOME/qt-host -extprefix $HOME/qt-raspi -prefix /usr/local/qt6 -device linux-rasp-pi4-aarch64 -device-option CROSS_COMPILE=aarch64-linux-gnu- -- -DCMAKE_TOOLCHAIN_FILE=$HOME/toolchain.cmake -DQT_FEATURE_xcb=ON -DFEATURE_xcb_xlib=ON -DQT_FEATURE_xlib=ON

The command above will run a configuration script that came with the Qt source code and complementing it with the CMake toolchain that we just created. It is equivalent to this CMake command:

$ cmake ../qt5/ -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-raspi -DCMAKE_INSTALL_PREFIX=/usr/local/qt6 -DCMAKE_TOOLCHAIN_FILE=$HOME/toolchain.cmake -DQT_QMAKE_TARGET_MKSPEC=devices/linux-rasp-pi4-aarch64 -DQT_FEATURE_xcb=ON -DFEATURE_xcb_xlib=ON -DQT_FEATURE_xlib=ON

Note: Both commands will do exactly the same thing: configuring Qt to be installed in folder

qt-raspi

on the host machine, with expectation that it will populate

usr/local/qt6

on the Raspberry Pi. If you already run the first command, then there is no need to run the second command.

The Qt installation from this build will be suitable to run Qt ARM64 applications using X11 as the windowing system. You can check this page from Qt Docs to learn more about xcb plugins.

In more advanced cases, check this page from Qt Documentation if you want to use other windowing systems such as Wayland or EGLFS.

We can then proceed with building and installing.

$ cmake --build . --parallel 4
$ cmake --install .

After Qt is successfully installed on

~/qt-raspi

, we will then send this Qt installation back to the Raspberry Pi. To do this, we can use

rsync

as we did when building sysroot, or

scp

. Both will accomplish the same thing, and there should be no significant difference between both of them. To use

rsync

:

rsync -avz --rsync-path="sudo rsync" /path/to/qt-raspi/* <pi_username>@<pi_ip_address>:/usr/local/qt6

To use

scp

:

$ cd ~/qt-raspi
$ scp -r * <pi_username>@<pi_ip_address>:/usr/local/qt6

Final Configuration on Raspberry Pi

To ensure our Qt 6 installation can be run on Raspberry Pi, we need to setup a few environment variables. All of this can be done on the host device via SSH.

$ ssh pi@169.254.187.77

## enter your raspberry pi user password, if prompted

$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/qt6/lib/

This is the most important environment variable that we need to set, as it will enable Raspberry Pi to find our Qt 6 library path. If you intend to run the application through SSH from your host machine and see the application running on a monitor that is connected to the Raspberry Pi, you need to also set

DISPLAY

variable with

export DISPLAY=:0

in addition to the commands above.

Compiling and Running Qt Project

Now we can compile a Qt application and get a binary that is suitable to deploy on Raspberry Pi. There are two methods to achieve this: with Terminal or with Qt Creator. For both methods, we will use the analogclock example project.

With Terminal

$ cd ~/qt5/qtbase/examples/gui/analogclock/
$ ls ## make sure there is a CMakeLists.txt file
$ ~/qt-raspi/bin/qt-cmake CMakeLists.txt

### CMake is configuring here. After it is done without any error, we can proceed to build and install it

$ cmake --build . --parallel 4
$ cmake --install .

We now have the binary of our application ready to be run on the Raspberry Pi. We can then send the binary to our Raspberry Pi using either scp or rsync and run the application (either through SSH or directly on the Raspberry Pi with keyboard and mouse connected).

$ scp -r gui_analogclock <pi_username>@<pi_ip_address>:/home/pi
$ ssh <pi_username>@<pi_ip_address>
$ cd ~ ## or cd to the directory where you send the binary
$ ./gui_analogclock

With Qt Creator

Open Qt Creator. On the main toolbar, go to Tools > Options.

  • Inside Kits menu, select Qt Versions then click Add. Locate the qmake file inside
    ~/qt-raspi/bin
    
    . Then click Apply.
  • We also have to make sure that our cross-compiler is already detected by the Qt Creator. Select Compilers tab, then check if our compiler (aarch64-linux-gnu-gcc and aarch64-linux-gnu-g++) is already listed. If not, you must add a GCC compiler and locate the location of the compiler. Then click Apply.

Another component that we need to set in Qt Creator is our own device. Still in Options menu, select Devices on the sidebar.

  • Under Devices tab, select Add. Inside the Device Configuration Wizard Selection, select Generic Linux Device, then click Start Wizard.
  • Specify an identifiable name, your Raspberry Pi IP address that you use to SSH, and the user name of the Raspberry Pi.
  • You will be prompted to deploy public key to the target device. This will speed up the deployment process as you don't need to input a password every time Qt Creator is attempting to send file to the target.
  • Click Next, and then Finish. Qt Creator will test the connection upon finishing and will tell you whether a connection to the target device can be established.

After setting all the necessary components, we can now make a new kit. Go back to Kits menu on the sidebar.

  • Click Add. Name your kit with a distinguishable name.
  • On Device type option, select Generic Linux Device.
  • On Device option, select the name of the device that you already set in previous step.
  • On Compiler option, select the cross-compiler (in this guide, aarch64-linux-gnu-gcc and aarch64-linux-gnu-g++) for both C and C++ compilers.
  • On Qt version option, select the name of the cross-compiled Qt version that you set in the previous step.
  • On CMake Configuration option, click Change and add
    -DCMAKE_TOOLCHAIN_FILE:UNINITIALIZED=/path/to/qt-raspi/lib/cmake/Qt6/qt.toolchain.cmake
    
    to the end of the line. Apply and then OK.

Click Apply and then OK. Now go to File > Open File or Project, then open CMakeLists.txt of the project (for this example, it is located in

~/qt5/qtbase/examples/gui/analogclock/CMakeLists.txt

). Qt Creator will try to configure the project using CMake.

Before running and deploying the application, we need to customise a few deployment/run settings. Go to Projects sidebar menu, then under the kit name click Run.

  • Under Run section, on X11 Forwarding tick "Forward to local display" checkbox and input
    :0
    
    to the text field. This will forward the application window to be opened on host device rather than on Raspberry Pi monitor (the application instance is still running on Raspberry Pi).
  • Sometimes Qt Creator does not fetch the device environment properly. This would cause the library of our Qt installation undetected by the Qt application. Under Environment section, click Details to expand the environment option. Click Add, then on Variable column type
    LD_LIBRARY_PATH
    
    . On the Value column, type the location of Qt installation inside the Raspberry Pi preceded by a colon (:), in our case it is
    :/usr/local/qt6/lib/
    
    .

After configuring these settings. run the application (Ctrl+R). The application window should be displayed on host's monitor display.

Known Issues

Issue with libdbus

During testing, we found a compilation error related to

libdbus

.

...
/usr/lib/gcc-cross/aarch64-linux-gnu/9/../../../../aarch64-linux-gnu/bin/ld: /home/uraihan/rpi-sysroot/usr/lib/aarch64-linux-gnu/libdbus-1.a(libdbus_1_la-dbus-message.o): relocation R_AARCH64_ADR_PREL_PG_HI21 against symbol `dbus_message_unref' which may bind externally can not be used when making a shared object; recompile with -fPIC
...

This could happen when the libdbus inside the initial sysroot is not a dynamically shared objects. This is normally fixable by doing a clean reinstall of Raspberry Pi OS (reformat microSD card, then install Raspberry Pi OS again through the Imager). Another recommendation is to try to reinstall

libdbus-1-dev

package. But if the problem persists and you don't need dbus, you can disable the dbus feature by adding

-DFEATURE_dbus=OFF

flag in the CMake configuration command.

Another walkaround worth trying is to change

from:

set(QT_LINKER_FLAGS "-Wl,-O1 -Wl,--hash-style=gnu -Wl,--as-needed")

to

set(QT_LINKER_FLAGS "-Wl,-O1 -Wl,--hash-style=gnu -Wl,--as-needed -ldbus-1")

Additionally make sure that link to rpi-sysroot/usr/lib/aarch64-linux-gnu/libdbus-1.so is valid. It should be linking to libdbus-1.so.3.19.13 in the same directory.

Note: You'll need DBus to have Bluetooth module working.

Issue with -fuse-ld=lld not found

Add "-Wl,-fuse-ld=gold" to QT_LINKER_FLAGS.

set(QT_LINKER_FLAGS "-Wl,-O1 -Wl,--hash-style=gnu -Wl,--as-needed -Wl,-fuse-ld=gold")

Issue with QtWayland not compiling:

Add -skip qtwayland to ./configure script like so:

$ ../qt5/configure -release -opengl es2 -nomake examples -nomake tests -skip qtwayland -qt-host-path $HOME/qt-host -extprefix $HOME/qt-raspi -prefix /usr/local/qt6 -device linux-rasp-pi3-g++ -device-option CROSS_COMPILE=aarch64-linux-gnu- -- -DCMAKE_TOOLCHAIN_FILE=$HOME/toolchain.cmake -DQT_FEATURE_xcb=ON -DFEATURE_xcb_xlib=ON -DQT_FEATURE_xlib=ON

Environment variables are not permanent:

Environment variables are not persistent during ssh session if you use export command.

You can add this in ~/.bashrc file

$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/qt6/lib/

Compiling app in QtCreator for device linux-rasp-pi3-g++ throws various errors:

Open qt-raspi/mkspecs/devices/linux-rasp-pi3-g++/qmake.conf

Remove -mfpu=crypto-neon-fp-armv8

Change include(../common/linux_arm_device_post.conf) to include(../common/linux_device_post.conf)

Various libraries not found when launching app on Raspberry Pi:

Make sure that directory /usr/local/qt6/lib/ exists. You may need to change your LD_LIBRARY_PATH to /usr/local/qt6/qt-raspi/lib/

Further Readings