Qt6 Headless-Application (without wayland/qml overhead)

Hello Toradex Community

We want to switch from a Torizon-CMake Project to a Torizon-Qt6 Projekt. So we startet a C++ Qt QML Application project, because this is the only Qt6 Template-Project - dispide we only want to create a headless application.

im working with the following setup:

  • Verdin AM62 1GB WB IT (V1.1A)
  • Verdin Development Board (V1.1F)
  • TorizonCore Version: 6.5.0+build.8
  • Host-System Win 10 (WSL2)

We tried to throw all unrequired GUI stuff out to slim down the Docker-Image size. Here is an overview of the image sizes:

cMake Qt6 (default) Qt6 (slimed down)
Debug 150MB 750MB 640MB
Release 100MB 680MB 540MB

I know the Qt stuff is heavy, but 600MB? … i doubt it.

Question:

  • What else can I do to slim down the Image size?
  • Is there an other base than FROM --platform=linux/${IMAGE_ARCH} torizon/qt6-wayland${GPU}:${BASE_VERSION} which can be used?

In the Dockerfile we commented out the following:

  • ARG GPU
  • apt-get install of imx-gpu-viv-wayland-dev or libgl stuff
  • apt-get install of qt6-wayland and qml6 stuff

Here is my modified Dockerfile (same changes apply to Dockerfile.debug and Dockerfile.sdk):

# ARGUMENTS --------------------------------------------------------------------
##
# SDK container version
##
ARG SDK_BASE_VERSION=3
##
# Base container version
##
ARG BASE_VERSION=3

##
# Board architecture
##
ARG IMAGE_ARCH=

##
# Board GPU vendor prefix
##
ARG GPU=

##
# Directory of the application inside container
##
ARG APP_ROOT=

# BUILD ------------------------------------------------------------------------
# TODO: cross compile x86 to arm
# We will use emulation here
##
# Build Step
##
FROM --platform=linux/${IMAGE_ARCH} \
    torizon/qt6-wayland${GPU}:${SDK_BASE_VERSION} AS Build

ARG IMAGE_ARCH
#ARG GPU
ARG APP_ROOT

# for vivante GPU we need some "special" sauce
#RUN apt-get -q -y update && \
#        if [ "${GPU}" = "-vivante" ] || [ "${GPU}" = "-imx8" ]; then \
#            apt-get -q -y install \
#            imx-gpu-viv-wayland-dev \
#        ; else \
#            apt-get -q -y install \
#            libgl1 \
#            libgles-dev \
#        ; fi \
#    && \
#    apt-get clean && apt-get autoremove && \
#    rm -rf /var/lib/apt/lists/*

# __deps__
RUN apt-get -q -y update && \
    apt-get -q -y install \
    build-essential \
    cmake \
#    qt6-base-private-dev \
    qt6-base-dev \
#    qt6-wayland \
#    qt6-wayland-dev \
#    qt6-declarative-dev \
#    qt6-declarative-private-dev \
#    qml6-module-qtqml \
#    qml6-module-qtqml-workerscript \
#    qml6-module-qtcore \
#    qml6-module-qtquick \
#    qml6-module-qtquick-window \
#    qml6-module-qtquick-controls \
#    qml6-module-qtquick-layouts \
#    qml6-module-qtquick-templates \
#    libqt6opengl6-dev \
    # ADD YOUR PACKAGES HERE
# DO NOT REMOVE THIS LABEL: this is used for VS Code automation
    # __torizon_packages_dev_start__
	libssl-dev:arm64 \
	libasio-dev:arm64 \
    # __torizon_packages_dev_end__
# DO NOT REMOVE THIS LABEL: this is used for VS Code automation
    && \
    apt-get clean && apt-get autoremove && \
    rm -rf /var/lib/apt/lists/*
# __deps__

COPY . ${APP_ROOT}
WORKDIR ${APP_ROOT}

# Remove the code from the debug builds, inside this container, to build the
# release version from a clean build
RUN rm -rf ${APP_ROOT}/build-${IMAGE_ARCH}

RUN if [ "$IMAGE_ARCH" = "arm64" ] ; then \
        cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=aarch64-linux-gnu-g++ -DCMAKE_C_COMPILER=aarch64-linux-gnu-gcc -Bbuild-${IMAGE_ARCH} ; \
    elif [ "$IMAGE_ARCH" = "arm" ] ; then \
        cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=arm-linux-gnueabihf-g++ -DCMAKE_C_COMPILER=arm-linux-gnueabihf-gcc -Bbuild-${IMAGE_ARCH} ; \
    fi

RUN cmake --build build-${IMAGE_ARCH}

# BUILD ------------------------------------------------------------------------

# DEPLOY -----------------------------------------------------------------------
##
# Deploy Step
##
FROM --platform=linux/${IMAGE_ARCH} \
    torizon/qt6-wayland${GPU}:${BASE_VERSION} AS Deploy

ARG IMAGE_ARCH
#ARG GPU
ARG APP_ROOT

# SSH for remote debug
EXPOSE 2231
ARG SSHUSERNAME=torizon

# Make sure we don't get notifications we can't answer during building.
ENV DEBIAN_FRONTEND="noninteractive"

# for vivante GPU we need some "special" sauce
#RUN apt-get -q -y update && \
#        if [ "${GPU}" = "-vivante" ] || [ "${GPU}" = "-imx8" ]; then \
#            apt-get -q -y install \
#            imx-gpu-viv-wayland-dev \
#        ; else \
#            apt-get -q -y install \
#            libgl1 \
#            libgles-dev \
#        ; fi \
#    && \
#    apt-get clean && apt-get autoremove && \
#    rm -rf /var/lib/apt/lists/*

# your regular RUN statements here
# Install required packages
RUN apt-get -q -y update && \
    apt-get -q -y install \
    file \
    curl \
#    qt6-base-private-dev \
    qt6-base-dev \
#    qt6-wayland \
#    qt6-wayland-dev \
#    qt6-declarative-dev \
#    qt6-declarative-private-dev \
#    qml6-module-qtqml \
#    qml6-module-qtqml-workerscript \
#    qml6-module-qtcore \
#    qml6-module-qtquick \
#    qml6-module-qtquick-window \
#    qml6-module-qtquick-controls \
#    qml6-module-qtquick-layouts \
#    qml6-module-qtquick-templates \
# DO NOT REMOVE THIS LABEL: this is used for VS Code automation
    # __torizon_packages_prod_start__
    # __torizon_packages_prod_end__
# DO NOT REMOVE THIS LABEL: this is used for VS Code automation
    && \
    apt-get clean && apt-get autoremove && \
    rm -rf /var/lib/apt/lists/*

USER torizon

# Copy the application compiled in the build step to the $APP_ROOT directory
# path inside the container, where $APP_ROOT is the torizon_app_root
# configuration defined in settings.json.
COPY --from=Build ${APP_ROOT}/build-${IMAGE_ARCH}/bin ${APP_ROOT}

# "cd" (enter) into the APP_ROOT directory
WORKDIR ${APP_ROOT}

# Command executed in runtime when the container starts
CMD ["./UVsmartHVACserver"]

# DEPLOY -----------------------------------------------------------------------

Thank in advance

Uwe

Greetings @buw3,

If your goal is to have a headless Qt6 application. It might be easier to start from a standard headless C/C++ template then add the Qt packages you need, compared to removing. Though I guess it depends on your use-case/needs.

As for other base images. You can see all our Debian container image sources here: torizon-containers/debian-docker-images at stable · torizon/torizon-containers · GitHub

The default base torizon/qt6-wayland${GPU}:${BASE_VERSION} is coming from the qt6-wayland folder. This pulls in a bunch of graphical Qt packages you probably don’t need. This image is in turn based on wayland-base-vivante which pulls in a lot of graphical stuff you also probably don’t need. This is based on our actual base container image which is the starting point for all our subsequent Debian container images.

So if you really want to optimize the container image size, you could start with the base image then build it up from there: Docker

Do you have a target size you’re trying to aim for? Or are you just trying to reduce the container image size as much as possible? Is the flash space on your system tight and that’s why you’re trying to do this?

Also out of curiosity what kind of application are you trying to create that uses Qt6 but is headless?

Best Regards,
Jeremias

Hello @jeremias.tx,
thanks for the fast reply.

… sounds reasonable. I will try that.

I already checked that, but I didn’t found anything which suits the purpose and I hope I had missed something :smiley:

thank you for the explanation! That was helpful.

Just trying to minimize the container image size as much as possible. Flash space isn’t an issue.

Due to older projects we already got a Qt licence, so we want to use the convinience of Qt for our new project, which is a device to control heating/ventilation and air-conditioning… I’m not allowed to tell you more :wink:

If I have achieved my goal, I will reply to the topic. But this may take some Days.

Best Regard,
Uwe

Hello again,

unfortunately I have not managed to get it working. I studied the Dockerfiles (from qt6-wayland and wayland-base-vivante) and also the Dockerfiles from the Torizon-Qt-GUI-Template-Project.

At the end I’m always facing the same problem, which is:

CMake Error at CMakeLists.txt:10 (find_package):
  By not providing "FindQt6.cmake" in CMAKE_MODULE_PATH this project has
  asked CMake to find a package configuration file provided by "Qt6", but
  CMake did not find one.

  Could not find a package configuration file provided by "Qt6" with any of
  the following names:

    Qt6Config.cmake
    qt6-config.cmake

  Add the installation prefix of "Qt6" to CMAKE_PREFIX_PATH or set "Qt6_DIR"
  to a directory containing one of the above files.  If "Qt6" provides a
  separate development package or SDK, be sure it has been installed.

Like in this post in the Qt-Forum it is said that for find_package to be successful, CMake must find the Qt installation and this should be done with CMAKE_PREFIX_PATH. I’ve tried that, but didn’t get anything to work.

Where does the declaration of the Qt installation happen in the Torizon-Qt-GUI-Template-Project?

I’ve tried many things the whole day, but here are my cleaned minimal files (only with qt6core):

CMakeLists.txt

cmake_minimum_required(VERSION 3.16)

project(HVACserver VERSION 1.0 LANGUAGES CXX C)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(CMAKE_PREFIX_PATH "/usr/lib/aarch64-linux-gnu/")

find_package(Qt6 REQUIRED COMPONENTS Core)

add_executable(HVACserver src/main.cpp)

set(CMAKE_SYSTEM_NAME Linux)

target_link_libraries(HVACserver PRIVATE
    Qt::Core
)

set_target_properties(HVACserver PROPERTIES
    RUNTIME_OUTPUT_DIRECTORY "bin"
    ARCHIVE_OUTPUT_DIRECTORY "lib"
    LIBRARY_OUTPUT_DIRECTORY "lib"
)

Dockerfile.sdk

# ARGUMENTS --------------------------------------------------------------------
ARG CROSS_SDK_BASE_TAG=3.2.1-bookworm

##
# Board architecture
# arm or arm64
##
ARG IMAGE_ARCH=

##
# Application root directory inside the container
##
ARG APP_ROOT=


# BUILD ------------------------------------------------------------------------
FROM torizon/debian-cross-toolchain-${IMAGE_ARCH}:${CROSS_SDK_BASE_TAG}

ARG APP_ROOT

# __deps__
RUN apt-get -q -y update && \
    apt-get -q -y install \
    build-essential \
    cmake \
    # ADD YOUR PACKAGES HERE
    && \
    apt-get clean && apt-get autoremove && \
    rm -rf /var/lib/apt/lists/*
# __deps__

# automate for torizonPackages.json
RUN apt-get -q -y update && \
    apt-get -q -y install \
# DO NOT REMOVE THIS LABEL: this is used for VS Code automation
    # __torizon_packages_dev_start__
	libqt6core6:arm64 \
    # __torizon_packages_dev_end__
# DO NOT REMOVE THIS LABEL: this is used for VS Code automation
    && \
    apt-get clean && apt-get autoremove && \
    rm -rf /var/lib/apt/lists/*

WORKDIR ${APP_ROOT}

Dockerfile.debug

# ARGUMENTS --------------------------------------------------------------------
##
# Board architecture
##
ARG IMAGE_ARCH=



##
# Base container version
##
ARG BASE_VERSION=3.2.1-bookworm

##
# Debug port
##
ARG SSH_DEBUG_PORT=

##
# Run as
##
ARG SSHUSERNAME=

##
# Application root directory inside the container
##
ARG APP_ROOT=

# BUILD ------------------------------------------------------------------------
##
# Deploy Step
##
FROM --platform=linux/${IMAGE_ARCH} \
    torizon/debian:${BASE_VERSION} AS Debug

ARG IMAGE_ARCH
ARG SSH_DEBUG_PORT
ARG SSHUSERNAME
ARG APP_ROOT

# SSH for remote debug
EXPOSE ${SSH_DEBUG_PORT}

# Make sure we don't get notifications we can't answer during building.
ENV DEBIAN_FRONTEND="noninteractive"

# your regular RUN statements here
# Install required packages
RUN apt-get -q -y update && \
    apt-get -q -y install \
    openssl \
    openssh-server \
    rsync \
    file \
    curl \
    gdb && \
    apt-get clean && apt-get autoremove && \
    rm -rf /var/lib/apt/lists/*

# automate for torizonPackages.json
RUN apt-get -q -y update && \
    apt-get -q -y install \
# DO NOT REMOVE THIS LABEL: this is used for VS Code automation
    # __torizon_packages_dev_start__
	libqt6core6:arm64 \
    # __torizon_packages_dev_end__
# DO NOT REMOVE THIS LABEL: this is used for VS Code automation
    && \
    apt-get clean && apt-get autoremove && \
    rm -rf /var/lib/apt/lists/*

# ⚠️ DEBUG PURPOSES ONLY!!
# copies RSA key to enable SSH login for user
COPY .conf/id_rsa.pub /id_rsa.pub

# create folders needed for the different components
# configures SSH access to the container and sets environment by default
RUN mkdir /var/run/sshd && \
    sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' \
        -i /etc/pam.d/sshd && \
    if test $SSHUSERNAME != root ; \
        then mkdir -p /home/$SSHUSERNAME/.ssh ; \
        else mkdir -p /root/.ssh ; fi && \
    if test $SSHUSERNAME != root ; \
        then cp /id_rsa.pub /home/$SSHUSERNAME/.ssh/authorized_keys ; \
        else cp /id_rsa.pub /root/.ssh/authorized_keys ; fi && \
    echo "PermitUserEnvironment yes" >> /etc/ssh/sshd_config && \
    echo "Port ${SSH_DEBUG_PORT}" >> /etc/ssh/sshd_config && \
    su -c "env" $SSHUSERNAME > /etc/environment

RUN rm -r /etc/ssh/ssh*key && \
    dpkg-reconfigure openssh-server

# Copy the compiled application to the $APP_ROOT directory path inside the 
# container, where $APP_ROOT is the torizon_app_root configuration defined 
# in settings.json.
COPY --chown=$SSHUSERNAME:$SSHUSERNAME ./build-${IMAGE_ARCH}/bin ${APP_ROOT}

CMD [ "/usr/sbin/sshd", "-D" ]

main.cpp

#include <QCoreApplication>
#include <iostream>

int main(int argc, char *argv[]) {
    std::cout << "Hello Torizon!" << std::endl;
    QCoreApplication app(argc,argv);

    return app.exec();
}

Can someone give my any further advice what to do?

Thanks in advance
Uwe

Well looking at the error it looks like CMake tries to confirm the existence of Qt by looking for the either of the following files:

Qt6Config.cmake
qt6-config.cmake

The file Qt6Config.cmake gets installed via the Debian package qt6-base-dev as seen here: Debian -- Package Contents Search Results -- Qt6Config.cmake

Now the problem is that this package also pulls in a lot of graphical related dependencies that you probably don’t want or care about. Additionally to even install qt6-base-dev you need to install other graphical packages otherwise the package dependencies don’t work out.

I suppose you could get a copy of Qt6Config.cmake and just install that manually without installing the rest of qt6-base-dev. Though I don’t know if you’ll run into any other issues doing this as I’m just speculating now. I’m unsure if there’s another way to satisfy CMake here other than providing one of these file it’s looking for. Might be more of a question for the Qt guys.

Another idea is that maybe you just need to install all this stuff in the SDK container for compilation but for the actual runtime container you can get away with not having qt6-base-dev and all that. Again, just speculating though I don’t know much about your application and it’s runtime

Best Regards,
Jeremias

Thank you again @jeremias.tx for the help. I appreciate it!

What you said is more or less the approach which I’ve also already tried. I fiddled around to get it somehow working, but with not luck yet.

If I get it to work i will reply here.

Best Regards,
Uwe

I guess a good step would be to confirm the accuracy of the error message and whether the Qt6Config.cmake file really does not exist anywhere in the SDK container filesystem.

By the way if you manage to figure something out here. It would be appreciated as a contribution to the templates repository the extension uses here: GitHub - toradex/vscode-torizon-templates: VS Code Torizon Integrated Development Environment Templates

This repo accepts contributions for additional templates. A headless Qt template could be an interesting contribution that would benefit you and possibly others going forward.

Best Regards,
Jeremias