Search by Tags

Torizon Qt Creator

 

Article updated at 14 Oct 2020
Subscribe for this article updates

This article complies to the Typographic Conventions for Torizon Documentation.

Introduction

Toradex provides How to Use GPIO with Torizon Visual Studio Extension (C++) and How to Use UART with Torizon Visual Studio Code Extension (.NET Core) extensions to allow developing applications under Torizon. This article shows a way how you can use Qt Creator with Torizon. However, please note that this is more a proof of concept than an official howto. It may happen that because of version changes some parts may work slightly different than written in this article.

Attention: Please note that this article is more a proof of concept and not a 1:1 howto.

Prerequisites

Idea

We use a toolchain installed in a container image to develop applications with the Qt Creator. To speed up compilation we use the feature of Debian multiarch. We don't do native compilation for arm with qemu but instead have two separate containers one for development (with multiarch) and one which runs on the target. This looks as follows:


  • Qt Creator Torizon Containers

The Qt Creator doesn't invoke qmake, gcc, and make directly but uses some wrapper scripts to run a container instead. The container needs access to the qt project on the host system which can be done with a bind mount. However, it will link against the libraries in the development container. On the target it will then use the same libraries but this time the whole container image is made for ARM. The target container however will not include the header files and compilers which would unnecessarily increase the size of the image.

Wrapper scripts

The idea is to wrap docker calls for Qt Creator so that they look like normal application calls. All wrapper and Dockerfiles can be downloaded from Qt Creator Docker. Here a step by step guide on how you can create them manually with some explanation.

Create a directory containing all these wrapper scripts:

$ mkdir ~/bin-torizon

Create an environment file:

environment
# Sysroot where the rootfs of the development container is exported QT_SYSROOT="/home/stefan/projects/torizon/qt-dev" # The name of the docker image for developement export DOCKER_IMAGE=qt5-dev # The project directory which is mounted into the Docker container. Can also be more than just the project directory (for example whole home). export PROJECT_DIR=$HOME # Which qmake should be used inside the container (arch specific!) QMAKE="/usr/bin/arm-linux-gnueabihf-qmake"

Create a helper file:

helper
bin_dir=$(dirname $0) . $bin_dir/environment docker_run() { environment=$(tempfile) env | grep -v PATH >> $environment # Hack for iMX8 devices echo ACCEPT_FSL_EULA=1 >> $environment # Run the command in a container docker run --rm --env-file $environment -v $PROJECT_DIR:$PROJECT_DIR $DOCKER_IMAGE /bin/bash -c "$@" rm $environment }

Create a wrapper file for make:

make
#!/bin/bash bin_dir=$(dirname $0) . $bin_dir/helper ARGS="$@" # Run make inside the container docker_run "cd $PWD && make $ARGS"

Create a wrapper file for gcc:

gcc
#!/bin/bash bin_dir=$(dirname $0) . $bin_dir/helper ARGS="$@" # Prepend sysroot to all directories under /usr... Qt uses this for syntax check with libclang docker_run "cd $PWD && \${CROSS_COMPILE}gcc $ARGS" 2> >(sed -e "s+/usr/+$QT_SYSROOT/usr/+g" >&2)

Create a wrapper file for g++:

g++
#!/bin/bash bin_dir=$(dirname $0) . $bin_dir/helper ARGS="$@" # Prepend sysroot to all directories under /usr... Qt uses this for syntax check with libclang docker_run "cd $PWD && \${CROSS_COMPILE}g++ $ARGS" 2> >(sed -e "s+/usr/+$QT_SYSROOT/usr/+g" >&2)

Create a wrapper file for qmake:

qmake
#!/bin/bash bin_dir=$(dirname $0) . $bin_dir/helper ARGS="$@" # Fix the paths if you run qmake -query. This is done by qtcreator for finding the include and lib directories which are then used by libclang for syntax check. # Also fix the QMAKE_OBJCOPY which is used by qmake when run from Qt Creator if [ "$1" == "-query" ]; then for line in $(docker_run "cd $PWD && echo QMAKE_OBJCOPY=arm-linux-gnueabihf-objcopy >> /usr/lib/arm-linux-gnueabihf/qt5/mkspecs/linux-g++/qmake.conf && $QMAKE $ARGS" 2>/dev/null); do line=$(echo $line | sed -e "s+QT_HOST_BINS:.*+QT_HOST_BINS:$bin_dir+g") line=$(echo $line | sed -e "s+QT_HOST_DATA:+QT_HOST_DATA:$QT_SYSROOT+g") line=$(echo $line | sed -e "s+QT_HOST_LIBS:+QT_HOST_LIBS:$QT_SYSROOT+g") line=$(echo $line | sed -e "s+QT_HOST_PREFIX:+QT_HOST_PREFIX:$QT_SYSROOT+g") line=$(echo $line | sed -e "s+QT_INSTALL_ARCHDATA:+QT_INSTALL_ARCHDATA:$QT_SYSROOT+g") line=$(echo $line | sed -e "s+QT_INSTALL_BINS:+QT_INSTALL_BINS:$QT_SYSROOT+g") line=$(echo $line | sed -e "s+QT_INSTALL_CONFIGURATION:+QT_INSTALL_CONFIGURATION:$QT_SYSROOT+g") line=$(echo $line | sed -e "s+QT_INSTALL_DATA:+QT_INSTALL_DATA:$QT_SYSROOT+g") line=$(echo $line | sed -e "s+QT_INSTALL_DEMOS:+QT_INSTALL_DEMOS:$QT_SYSROOT+g") line=$(echo $line | sed -e "s+QT_INSTALL_DOCS:+QT_INSTALL_DOCS:$QT_SYSROOT+g") line=$(echo $line | sed -e "s+QT_INSTALL_EXAMPLES:+QT_INSTALL_EXAMPLES:$QT_SYSROOT+g") line=$(echo $line | sed -e "s+QT_INSTALL_HEADERS:+QT_INSTALL_HEADERS:$QT_SYSROOT+g") line=$(echo $line | sed -e "s+QT_INSTALL_IMPORTS:+QT_INSTALL_IMPORTS:$QT_SYSROOT+g") line=$(echo $line | sed -e "s+QT_INSTALL_LIBEXECS:+QT_INSTALL_LIBEXECS:$QT_SYSROOT+g") line=$(echo $line | sed -e "s+QT_INSTALL_LIBS:+QT_INSTALL_LIBS:$QT_SYSROOT+g") line=$(echo $line | sed -e "s+QT_INSTALL_PLUGINS:+QT_INSTALL_PLUGINS:$QT_SYSROOT+g") line=$(echo $line | sed -e "s+QT_INSTALL_PREFIX:+QT_INSTALL_PREFIX:$QT_SYSROOT+g") line=$(echo $line | sed -e "s+QT_INSTALL_QML:+QT_INSTALL_QML:$QT_SYSROOT+g") line=$(echo $line | sed -e "s+QT_INSTALL_TESTS:+QT_INSTALL_TESTS:$QT_SYSROOT+g") line=$(echo $line | sed -e "s+QT_INSTALL_TRANSLATIONS:+QT_INSTALL_TRANSLATIONS:$QT_SYSROOT+g") line=$(echo $line | sed -e "s+QT_SYSROOT:+QT_SYSROOT:$QT_SYSROOT+g") echo $line done else docker_run "cd $PWD && echo QMAKE_OBJCOPY=arm-linux-gnueabihf-objcopy >> /usr/lib/arm-linux-gnueabihf/qt5/mkspecs/linux-g++/qmake.conf && $QMAKE $ARGS" fi

Create a wrapper for qmlcachegen:

qmlcachegen
#!/bin/bash bin_dir=$(dirname $0) . $bin_dir/helper ARGS="$@" # Run qmlcachgen inside the container docker_run "/usr/lib/qt5/bin/qmlcachegen $ARGS"

Create a wrapper for qmlscene:

qmlscene
#!/bin/bash bin_dir=$(dirname $0) . $bin_dir/helper ARGS="$@" # Run qmlcachgen inside the container docker_run "/usr/lib/qt5/bin/qmlscene $ARGS"

Create a wrapper for rcc:

rcc
#!/bin/bash bin_dir=$(dirname $0) . $bin_dir/helper ARGS="$@" # Run rcc inside the container docker_run "/usr/lib/qt5/bin/rcc $ARGS" > >(tee -a /tmp/rcc-output) 2> >(tee -a /tmp/rcc-stderr >&2)

Create a script which allows to export the rootfs of a container image:

docker-export-image
#!/bin/bash usage() { echo "docker-export-image " echo echo "Will export the rootfs of the development container to the export directory" } bin_dir=$(dirname $0) . $bin_dir/environment test "$1" == "" && { usage; exit 1; } test -d $1 || { usage; exit 1; } container_name=dev$(echo $RANDOM) # Run a container just to allow exporting docker run --name=$container_name --rm -d -ti -e ACCEPT_FSL_EULA=1 -v $PROJECT_DIR:$PROJECT_DIR $DOCKER_IMAGE /bin/bash # Export container docker export $container_name | tar -x -C $1 # Stop exported container again docker stop $container_name

Dockerfiles

The idea is to have two different containers. One container runs on the host machine and allows to cross compile qt applications while the other runs on the target machine and will allow debugging. This means normally that the cross compile container is a native x86/amd64 container while the other is a armhf or arm64 container.

Development Container

Dockerfile.dev
ARG ARCH_ARG=armhf FROM torizon/debian-cross-toolchain-$ARCH_ARG # We also need the ARCH_ARG in the first build stage ARG ARCH_ARG=armhf ARG USER_ID_ARG=1000 # Remove Toradex feeds for now because of broken qt5 dependencies on iMX8 RUN sed -i 's/\(.*\)feeds.toradex.com\(.*\)/# \1feeds.toradex.com\2/g' /etc/apt/sources.list # Install required packages RUN apt-get -q -y update \ && apt-get -q -y install \ qt5-qmake:${ARCH_ARG} \ && apt-get clean \ && apt-get autoremove \ && rm -rf /var/lib/apt/lists/* # Install almost all available qt packages. This makes the development container a more general purpose. RUN apt-get -q -y update \ && apt-get -q -y install \ libaccounts-qt5-dev:${ARCH_ARG} \ libdbusextended-qt5-dev:${ARCH_ARG} \ libdbusmenu-qt5-dev:${ARCH_ARG} \ libfcitx-qt5-dev:${ARCH_ARG} \ libgrantlee5-dev:${ARCH_ARG} \ libgwengui-qt5-dev:${ARCH_ARG} \ libjreen-qt5-dev:${ARCH_ARG} \ liblastfm5-dev:${ARCH_ARG} \ libmpris-qt5-dev:${ARCH_ARG} \ libodsstream-qt5-dev:${ARCH_ARG} \ libpackagekitqt5-dev:${ARCH_ARG} \ libphonon4qt5-dev:${ARCH_ARG} \ libpoppler-qt5-dev:${ARCH_ARG} \ libqaccessibilityclient-qt5-dev:${ARCH_ARG} \ libqjdns-qt5-dev:${ARCH_ARG} \ libqscintilla2-qt5-dev:${ARCH_ARG} \ libqt5charts5-dev:${ARCH_ARG} \ libqt5datavisualization5-dev:${ARCH_ARG} \ libqt5gamepad5-dev:${ARCH_ARG} \ libqt5networkauth5-dev:${ARCH_ARG} \ libqt5opengl5-dev:${ARCH_ARG} \ libqt5sensors5-dev:${ARCH_ARG} \ libqt5serialbus5-dev:${ARCH_ARG} \ libqt5serialport5-dev:${ARCH_ARG} \ libqt5svg5-dev:${ARCH_ARG} \ libqt5texttospeech5-dev:${ARCH_ARG} \ libqt5waylandclient5-dev:${ARCH_ARG} \ libqt5waylandcompositor5-dev:${ARCH_ARG} \ libqt5webchannel5-dev:${ARCH_ARG} \ libqt5webkit5-dev:${ARCH_ARG} \ libqt5websockets5-dev:${ARCH_ARG} \ libqt5webview5-dev:${ARCH_ARG} \ libqt5x11extras5-dev:${ARCH_ARG} \ libqt5xmlpatterns5-dev:${ARCH_ARG} \ libqtspell-qt5-dev:${ARCH_ARG} \ libquazip5-dev:${ARCH_ARG} \ libqwt-qt5-dev:${ARCH_ARG} \ libqwtmathml-qt5-dev:${ARCH_ARG} \ libsignon-qt5-dev:${ARCH_ARG} \ libtelepathy-qt5-dev:${ARCH_ARG} \ qt3d5-dev:${ARCH_ARG} \ qt3d5-dev-tools:${ARCH_ARG} \ qtbase5-dev:${ARCH_ARG} \ qtbase5-dev-tools:${ARCH_ARG} \ qtconnectivity5-dev:${ARCH_ARG} \ qtdeclarative5-dev:${ARCH_ARG} \ qtdeclarative5-dev-tools:${ARCH_ARG} \ qtlocation5-dev:${ARCH_ARG} \ qtmultimedia5-dev:${ARCH_ARG} \ qtpositioning5-dev:${ARCH_ARG} \ qtquickcontrols2-5-dev:${ARCH_ARG} \ qtscript5-dev:${ARCH_ARG} \ qttools5-dev:${ARCH_ARG} \ qttools5-dev-tools:${ARCH_ARG} \ qtwayland5-dev-tools:${ARCH_ARG} \ qtwebengine5-dev:${ARCH_ARG} \ qtwebengine5-dev-tools:${ARCH_ARG} \ qtxmlpatterns5-dev-tools:${ARCH_ARG} \ \ qtbase5-dev:${ARCH_ARG} \ qtwayland5:${ARCH_ARG} \ libqt5websockets5-dev:${ARCH_ARG} \ qtdeclarative5-dev:${ARCH_ARG} \ qtdeclarative5-dev:${ARCH_ARG} \ qt3d5-dev:${ARCH_ARG} \ libqt5serialport5-dev:${ARCH_ARG} \ qtquickcontrols2-5-dev:${ARCH_ARG} \ qml-module-qtquick2:${ARCH_ARG} \ qml-module-qtquick-controls2:${ARCH_ARG} \ qml-module-qt-websockets:${ARCH_ARG} \ qml-module-qt3d:${ARCH_ARG} \ qml-module-qtaudioengine:${ARCH_ARG} \ qml-module-qtav:${ARCH_ARG} \ qml-module-qtbluetooth:${ARCH_ARG} \ qml-module-qtcharts:${ARCH_ARG} \ qml-module-qtdatavisualization:${ARCH_ARG} \ qml-module-qtgraphicaleffects:${ARCH_ARG} \ qml-module-qtgstreamer:${ARCH_ARG} \ qml-module-qtlocation:${ARCH_ARG} \ qml-module-qtmultimedia:${ARCH_ARG} \ qml-module-qtnfc:${ARCH_ARG} \ qml-module-qtpositioning:${ARCH_ARG} \ qml-module-qtqml-models2:${ARCH_ARG} \ qml-module-qtqml-statemachine:${ARCH_ARG} \ qml-module-qtquick-controls:${ARCH_ARG} \ qml-module-qtquick-controls2:${ARCH_ARG} \ qml-module-qtquick-dialogs:${ARCH_ARG} \ qml-module-qtquick-extras:${ARCH_ARG} \ qml-module-qtquick-layouts:${ARCH_ARG} \ qml-module-qtquick-localstorage:${ARCH_ARG} \ qml-module-qtquick-particles2:${ARCH_ARG} \ qml-module-qtquick-privatewidgets:${ARCH_ARG} \ qml-module-qtquick-scene2d:${ARCH_ARG} \ qml-module-qtquick-scene3d:${ARCH_ARG} \ qml-module-qtquick-shapes:${ARCH_ARG} \ qml-module-qtquick-templates2:${ARCH_ARG} \ qml-module-qtquick-virtualkeyboard:${ARCH_ARG} \ qml-module-qtquick-window2:${ARCH_ARG} \ qml-module-qtquick-xmllistmodel:${ARCH_ARG} \ qml-module-qtquick2:${ARCH_ARG} \ qml-module-qtsensors:${ARCH_ARG} \ qml-module-qttest:${ARCH_ARG} \ qml-module-qtwayland-compositor:${ARCH_ARG} \ qml-module-qtwebchannel:${ARCH_ARG} \ qml-module-qtwebengine:${ARCH_ARG} \ qml-module-qtwebkit:${ARCH_ARG} \ qml-module-qtwebsockets:${ARCH_ARG} \ qml-module-qtwebview:${ARCH_ARG} \ \ qmlscene:${ARCH_ARG} \ && apt-get clean && apt-get autoremove && rm -rf /var/lib/apt/lists/* # Overwrite qt_lib_gui by setting opengles2 instead of opengl flags COPY qt_lib_gui.pri /usr/lib/aarch64-linux-gnu/qt5/mkspecs/modules/qt_lib_gui.pri # Make sure that the right objcopy will be called RUN cross_arg=$(echo ${CROSS_COMPILE} | sed 's/-$//g') \ && echo QMAKE_OBJCOPY=${CROSS_COMPILE}objcopy >> /usr/lib/$cross_arg/qt5/mkspecs/linux-g++/qmake.conf # Remove torizon and replace it with a build user with the same ID as the host user RUN userdel torizon # Add a group which has the same gid as the customers uid RUN groupadd -g ${USER_ID_ARG} build # Add a user with the uid from the args RUN useradd -u ${USER_ID_ARG} -p "$1$Yx4IlJWd$8YS1kb37eyHMbIBoO12Br." -g build build USER build

This container contains all tools to cross compile as well as the development files for the target architecture.

Target Container

Dockerfile
ARG BASE_NAME=arm32v7-debian-qt5-wayland FROM torizon/$BASE_NAME:buster SHELL ["/bin/bash", "-c"] RUN apt-get -y update && apt-get install -y --no-install-recommends \ qml-module-qtquick-controls2 \ qml-module-qt-websockets \ qml-module-qt3d \ qml-module-qtaudioengine \ qml-module-qtav \ qml-module-qtbluetooth \ qml-module-qtcharts \ qml-module-qtdatavisualization \ qml-module-qtgraphicaleffects \ qml-module-qtgstreamer \ qml-module-qtlocation \ qml-module-qtmultimedia \ qml-module-qtnfc \ qml-module-qtpositioning \ qml-module-qtqml-statemachine \ qml-module-qtquick-controls \ qml-module-qtquick-controls2 \ qml-module-qtquick-extras \ qml-module-qtquick-scene2d \ qml-module-qtquick-scene3d \ qml-module-qtquick-shapes \ qml-module-qtquick-templates2 \ qml-module-qtquick-virtualkeyboard \ qml-module-qtsensors \ qml-module-qtwayland-compositor \ qml-module-qtwebchannel \ qml-module-qtwebengine \ qml-module-qtwebkit \ qml-module-qtwebsockets \ qml-module-qtwebview \ qt5-qmltooling-plugins \ openssh-server \ gdbserver \ rsync \ && apt-get clean && apt-get autoremove && rm -rf /var/lib/apt/lists/* # Allow empty password hack (only for developement) # sed -i /etc/ssh/sshd_config -e 's/UsePAM yes/UsePAM no/g' RUN sed -i 's/nullok_secure/nullok/g' /etc/pam.d/common-auth && \ echo "PermitEmptyPasswords yes" >> /etc/ssh/sshd_config && \ echo "PermitRootLogin yes" >> /etc/ssh/sshd_config && \ sed -i /etc/shadow -e 's/root:.*/root::18254:0:99999:7:::/g' RUN test $(arch) == "aarch64" && { cd /usr/lib/aarch64-linux-gnu/ && ln -s libGL.so.1.7.0 libGL.so.1.2; } || true COPY entrypoint.sh /entrypoint.sh RUN echo "set -o allexport; . /etc/environment; set +o allexport;" >> /etc/profile ENTRYPOINT ["/entrypoint.sh"]

Note: Please note we recommend having an additional container for production which includes only the bare minimum. Remove all unnecessary modules and also gdbserver, sshd and rscync. The Dockerfile above should only be used during development.

The entrypoint is very simple. It just spawns sshd and then waits for other commands on bash. For debugging GUI applications it is expected to run weston. See the section "Start the Qt Container on the Target" for how you start the container for debugging.

entrypoint.sh
#!/bin/bash mkdir /run/sshd && /usr/sbin/sshd& /bin/bash $@

Building the container

Based on the Dockerfile we now need to create the container. This is done by calling docker build:

$ docker build -t qt5-dev -f Dockerfile.dev .
$ docker build -t qt5-target -f Dockerfile .
$ docker build -t qt5-dev --build-arg USER_ID_ARG=$(id -u) --build-arg ARCH_ARG=arm64 -f Dockerfile.dev .
$ docker build -t qt5-target -build-arg BASE_NAME=arm64v8-debian-qt5-wayland-vivant -f Dockerfile .

You can then transfer the target container to the module with the following command:

$ docker save qt5-target | ssh torizon@<IP> docker-load

Filesystem Export

Qt Creator needs to see the actual filesystem of the container so that it can index the header files. Therefore, it is necessary to export the content of the development container to a directory. The docker-export-image script allows to do that.

$ docker-export-image qt5-dev /home/<user>/torizon/qt-dev

Start the Qt Container on the Target

We transferred the qt container to the target and now need to start it. This can be done with docker run:

# docker run -ti --rm --name=qt-debug --cap-add CAP_SYS_TTY_CONFIG \
             -v /dev:/dev -v /tmp:/tmp -v /run/udev/:/run/udev/ \
             -p 2222:22 -p 10000-10010:10000-10010 \
             --device-cgroup-rule='c 4:* rmw' --device-cgroup-rule='c 13:* rmw'  --device-cgroup-rule='c 226:* rmw'\
              qt-target
# docker run -e ACCEPT_FSL_EULA=1 -p 2222:22 -p 10000-10010:10000-10010 --rm  \
              -it --name=qt5 -v /tmp:/tmp -v /dev/dri:/dev/dri -v /dev/galcore:/dev/galcore \
               --device-cgroup-rule='c 199:* rmw' --device-cgroup-rule='c 226:* rmw' qt5-target

The entrypoint script will automatically start ssh inside the container and then spawns a bash console. Inside the container, you need to start weston if you want to write a graphical application. You can launch weston in a separate container as shown in the Debian Containers for Torizon article. Now you are ready to start debugging with the Qt Creator.

Setup Qt Creator

In Qt Creator we need to create a new kit which allows us to use the wrapped qmake.

First you need to install gdb-multiarch as debugger. On Debian based distributions you can do:

$ sudo apt get install gdb-multiarch

Then you need to start Qt Creator. Open Tools/Options and select Kits. Under Qt Version click on "Add" and select the qmake wrapper created in the section above:


  • Qt Versions

Under Debuggers you can add the previously installed gdb-multiarch from the host system:


  • Debuggers

Under Compilers you can add the wrapped gcc and g++:


  • Compilers

Under Devices press Add and choose Generic Linux Device. Make sure you change the ssh port to 2222 in the end and to only define 10 ports for gdb:


  • Devices

After that, you can create a Kit. Select your defined Qt Version, gdb-multiarch, gcc and g++ as well as the Device:


  • Kits

Start Development

Create a new project as usual. Select "Qt Torizon" when you are asked about the kit to use. You should be able to compile and debug your Qt Application as it would be a normal embedded Qt Linux project. See the Qt Creator manual for reference.

References