Qt 6.9 嵌入式 Linux 交叉编译全栈填坑指南(以树莓派5 AArch64 为例

在嵌入式开发中,将现代化的 UI 框架 Qt 6 移植到 ARM 板子上是一件极具成就感但也充满挑战的事情。尤其是面对基于 Debian/Ubuntu 系统的树莓派时,由于其独特的多架构(Multi-arch)库路径设计,往往会让编译器找不到头文件或链接库。

本文将基于最新的 Qt 6.9.2 源码,记录一次从零构建、解决各类诡异报错、最终成功运行 QML 程序的完整交叉编译全过程。

参考官方教程(QtAssistant)

Qt 6.9/Product information/Supported Platforms/Qt for Embedded Linux

Qt 6.9/Product information/Supported Platforms/Qt for Embedded Linux/Configure an Embedded Linux Device

Qt 6.9/Product information/ Supported Platforms/ Cross-compiling Qt

内容为自己写了一份, 真实的编译过程, 完整测试并且成功运行, 让AI整理了一下, 基本都是实际遇到的问题与步骤

核心术语与架构预览

在正式动手之前,我们需要理清几个核心概念:

  • 宿主机(Host): 负责编写代码并执行编译的机器,通常是一台高性能的 x86_64 Linux 电脑(此处使用的是 Ubuntu/WSL2)
  • 目标机(Target): 最终运行程序的嵌入式设备,例如树莓派 4(AArch64 架构)
  • 交叉编译(Cross-compiling): 在宿主机上编译出能在目标机上运行的二进制程序
  • Sysroot(目标系统根目录): 目标板上真实的 /lib/usr/include 等目录的一个本地镜像备份。编译器需要通过它来了解目标板上有哪些库和头文件

主机工具(Host Tools)的核心作用

在编译 Qt 源码时,需要运行很多 Qt 自身的工具(如 moc 元对象编译器、rcc 资源编译器、qsb 着色器编译器等)来生成代码。

关键点: 这些工具必须能在宿主机(x86_64)上直接运行。因此,在为目标机编译 Qt 之前,我们必须先在宿主机上编译出一套同版本的本地 Qt,专门用来提供这些"主机工具"。确保版本绝对一致的最佳方法,就是用同一套源码先后编译两次

QPA 平台插件该如何选择?

Qt 通过 QPA(Qt Platform Abstraction,Qt平台抽象) 插件来适配不同的显示环境。嵌入式平台常见的插件对比如下:

QPA 插件 适用场景 核心特性与性能
EGLFS 无桌面环境的纯嵌入式系统 推荐首选默认 基于 EGL 和 OpenGL ES 2.0,直接全屏单窗口输出,硬件加速性能最高。
Wayland 新版现代桌面系统 新一代轻量化窗口管理器,高刷新率优化极佳。
LinuxFB 无 GPU 加速的老旧设备 直接读写 framebuffer(/dev/fb0),不支持 OpenGL/Qt Quick 2,纯 CPU 渲染,性能较低。
XCB 传统 X11 桌面(如老 Ubuntu) 支持多窗口和弹窗,但对嵌入式硬件加速支持极差,不推荐。
VNC 远程无头(Headless)部署 将显示画面通过网络 VNC 协议输出,便于远程调试。

第一步:准备工作(Host 构建与目录规划)

我设工作目录中包含以下内容:

  1. 工具链(Toolchain): cross-pi-gcc-14.2.0-64(64位树莓派交叉编译器)abhiTronix/raspberry-pi-cross-compilers: Latest GCC Cross Compiler & Native (ARM & ARM64) CI generated precompiled standalone toolchains for all Raspberry Pis. 🍇
  2. Sysroot: 从树莓派上同步过来的根文件系统(只需要/lib /usr/lib /usr/include, 其中/lib/usr/lib的软链接),存放在 /home/nichijou_wsl/LinuxTools/raspegl_sysroot
  3. Qt 源码: qt-everywhere-src-6.9.2 Index of /archive/qt/6.9/6.9.2/single
    sysroot(系统根目录)是交叉编译工具链中的一个核心概念,它指定了目标系统(Target System)的根文件系统路径. 注意复制sysroot时,树莓派使用的是多架构模式, 其内容不在/usr/lib下, 而是在/usr/lib/aarch64-linu-gnu下, 可以考虑直接把这个文件复制到主机sysroot/usr/lib下, include也是同理. 也可以在cmake中手动指定搜索路径, 后文采用的是这个方式, 这样不用修改sysroot中的内容.

1. 编译宿主机 Host Qt

首先,进入一个独立的构建目录,仅编译出供后续交叉编译使用的主机工具(host_tools),这样可以大幅节省时间,且不需要真正执行 make install

bash 复制代码
mkdir ~/QtHostBuild && cd ~/QtHostBuild
/path/to/qt-everywhere-src-6.9.2/configure -developer-build -nomake tests
cmake --build . --target host_tools

这个命令需要调整, 命令来自官方教程, 但是编译主机的Qt基本不会遇到什么问题, 这样编译出来的主机不一定可用, 但是用来交叉编译目标机器的Qt需要的工具已经具备了. 也可编译好后安装到指定目录, 具体过程略, 总之最后我是编译并安装到了

/home/nichijou_wsl/LinuxTools/qt6-host-x86_64中.

第二步:编写 CMake Toolchain 配置文件

Qt 6 全面拥抱 CMake。为了能够优雅地管理交叉编译参数,强烈建议准备一个专属的 raspberrypi.cmake 工具链文件

针对树莓派的多架构(aarch64-linux-gnu)路径,我们需要显式通过 -B 指定运行时启动文件的位置,以及 -I-Wl,-rpath-link 来告诉链接器去哪里找隐式依赖。

在本地创建 /home/nichijou_wsl/LinuxTools/raspberrypi.cmake,内容如下:

cmake 复制代码
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)

# 1. 定义 sysroot 与平台架构
set(CMAKE_SYSROOT /home/nichijou_wsl/LinuxTools/raspegl_sysroot)
set(CMAKE_LIBRARY_ARCHITECTURE aarch64-linux-gnu)
set(TARGET_SYSROOT ${CMAKE_SYSROOT})
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})

# 2. 关联之前编译好的 Host Qt 路径与本地安装路径
set(QT_HOST_PATH /home/nichijou_wsl/LinuxTools/qt6-host-x86_64)
set(CMAKE_STAGING_PREFIX /home/nichijou_wsl/LinuxTools/qt_raspegl) # 宿主机上的临时安装点
set(CMAKE_INSTALL_PREFIX /usr/local/qt6)                         # 最终在树莓派上的部署路径

# 3. 指定编译器路径
set(CMAKE_C_COMPILER /home/nichijou_wsl/LinuxTools/cross-pi-gcc-14.2.0-64/bin/aarch64-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER /home/nichijou_wsl/LinuxTools/cross-pi-gcc-14.2.0-64/bin/aarch64-linux-gnu-g++)

# 4. 核心:强制注入多架构路径(解决 crt1.o 与头文件丢失问题)
# -B 告诉编译器去哪里找内部启动组件和库
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -B${CMAKE_SYSROOT}/usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I${CMAKE_SYSROOT}/usr/include/${CMAKE_LIBRARY_ARCHITECTURE}")
# -Wl,-rpath-link 引导链接器解决间接依赖的库寻找
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wl,-rpath-link,${CMAKE_SYSROOT}/lib/${CMAKE_LIBRARY_ARCHITECTURE}")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wl,-rpath-link,${CMAKE_SYSROOT}/usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}")

set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS}")

# 5. 修正 Pkg-Config 在交叉编译下的行为
set(ENV{PKG_CONFIG_SYSROOT_DIR} ${CMAKE_SYSROOT})
set(ENV{PKG_CONFIG_LIBDIR} ${CMAKE_SYSROOT}/usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}/pkgconfig)
set(ENV{PKG_CONFIG_PATH} "")

# 6. 查找模式防御:只在 sysroot 中查找库和头文件,绝不污染宿主机环境
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)

第三步:交叉编译经典踩坑录(血泪排查)

在执行 configure 配置和 cmake --build 编译期间,我们极大概率会遇到以下几个经典报错。这里给出原因和一剂特效药:

坑 1:Mesa 图形库导致的 SBOM 识别错误

  • 报错信息: > CMake Error at qtbase/cmake/QtPublicCMakeHelpers.cmake:548 (message): Unknown arguments: (-;kisak-mesa;PPA)
  • 翻译与原因: 意思是"未知的参数:(-;kisak-mesa;PPA)"。这是因为树莓派的 Sysroot 中安装了第三方的 kisak-mesa 图形驱动加速源,其特殊的版本字符串干扰了 Qt 6.9 的 SBOM(软件物料清单组件) 检查机制。
  • 解决办法:configure 参数中加上 -no-sbom 跳过物料清单生成。

坑 2:找不到 crt1.olibc-header-start.h

  • 报错信息: > cannot find crt1.o: No such file or directory
    fatal error: bits/libc-header-start.h: No such file or directory
  • 翻译与原因: 提示找不到 C 语言运行时的核心启动文件(crt1.o)或标准 C 库头文件。由于 Debian 系的多架构策略,这些文件被藏在了 /usr/lib/aarch64-linux-gnu 下。
  • 解决办法: 确保你的 toolchain.cmake 中正确添加了上文提到的 -B-I-Wl,-rpath-link 标志。

坑 3:缺失三方依赖库(Brotli / GTK3)

  • 报错信息: > fatal error: brotli/decode.h: No such file or directory
    cannot find -lgtk-3: No such file or directory
  • 翻译与原因: 分别代表"找不到网页压缩算法 Brotli 的解码头文件"以及"找不到 GTK3 图形主题库"。
  • 解决办法: 如果我们是做纯嵌入式全屏 UI,网络压缩和桌面主题不是必须的,可以直接裁剪掉。在 configure 阶段添加 -no-feature-brotli -no-feature-gtk3

坑 4:符号冲突------被 Sysroot 原有的旧版本 Qt6 污染

  • 报错信息: > sysroot/lib/aarch64-linux-gnu/libQt6OpenGL.so.6: undefined reference to 'lcOpenGLProgramDiskCache()@Qt_6'
  • 翻译与原因: "未定义的引用 lcOpenGLProgramDiskCache"。这意味着链接器在编译时,错误地链接到了树莓派 Sysroot 本身自带的旧版本 Qt6 动态库,而不是我们当前正在编译的全新 Qt 6.9.2 源码,从而导致符号对不上。
  • 解决办法: 釜底抽薪,断了链接器的后路。直接在 Sysroot 中把旧的 Qt 库隔离备份起来:
bash 复制代码
cd /home/nichijou_wsl/LinuxTools/raspegl_sysroot/lib/aarch64-linux-gnu/
mkdir -p qt6_old_backup
mv libQt6* qt6_old_backup/ # 强行移走,迫使链接器使用本次编译输出的新库

第四步:最终配置与编译安装

扫清所有障碍后,在源码同级创建 build-rasp-new 目录,并进入该目录, 执行最终的配置指令:

bash 复制代码
../configure -release -opengl es2 -egl -no-sbom \
  -skip qtwebengine -skip qtwebview \
  -no-feature-brotli -no-feature-gtk3 \
  -- -DCMAKE_TOOLCHAIN_FILE=/home/nichijou_wsl/LinuxTools/raspberrypi.cmake

注意: 配置完成后,请务必仔细检查终端输出目录的的 config.summary(配置摘要) !确保以下两项为 yes,这才意味着你的 GPU 硬件加速成功开启了:

复制代码
OpenGL ES 2.0 ........................ yes
EGLFS .................................. yes
EGLFS GBM ............................ yes

确认无误后,全核起飞开始编译并安装:

bash 复制代码
cmake --build .

编译好后安装到指定目录, 方便将安装目录的内容部署到树莓派上

bash 复制代码
cmake --install . --prefix ../../qt_egl_rasp

第五步:部署至树莓派并配置环境变量

编译完成后,本地的 qt_egl_rasp 目录中会生成 binlibpluginsqml 等完备的构建产物。我们只需将运行时必需的动态库和插件同步到树莓派即可。

1. 使用 rsync 进行文件同步

bash 复制代码
# 同步到临时目录(避免权限问题)
rsync -avz lib nichijou@192.168.137.18:/tmp/qt6/
rsync -avz plugins nichijou@192.168.137.18:/tmp/qt6/
rsync -avz qml nichijou@192.168.137.18:/tmp/qt6/

# 登录树莓派,将其移动到规范的安装目录
sudo cp -r /tmp/qt6 /usr/local/qt6

这样目标机器就拥有运行使用这一套工具编译的软件的能力了. 测试发现这样编译下来支持这些平台插件

wayland, eglfs, linuxfb, vnc, minimal, minimalegl, offscreen, wayland-egl

2. 关键:在树莓派上配置环境变量

为了让树莓派能正确识别到我们的新 Qt6 库、插件以及字体,必须在系统的 ~/.bashrc 或启动脚本中配置以下环境变量:

bash 复制代码
# 优先引导我们的新 Qt6 动态库路径
export LD_LIBRARY_PATH=/usr/local/qt6/lib:$LD_LIBRARY_PATH
# 明确指示 Qt 插件的寻址位置
export QT_QPA_PLATFORM_PLUGIN_PATH=/usr/local/qt6/plugins
# 如果你需要直接输出到显示器而没有桌面,指定 EGLFS 平台
export QT_QPA_PLATFORM=eglfs
# Qt 6 移除了默认内置字体,必须指定系统有效字体路径,否则界面文字会变成方块
export QT_QPA_FONTDIR=/usr/share/fonts/truetype/dejavu

第六步:应用开发实战:使用 qt-cmake 飞速编译

这一套流程下来,最爽利的部分就在这里了。Qt 编译完成后,会在其安装目录的 bin/ 下生成一个名叫 qt-cmake 的脚本。

这个脚本是一个完美的"高阶封装"。它已经将我们的交叉编译器、Sysroot 路径、以及编译好的目标板 Qt 库路径 全部打包内置成了通用的 qt.toolchain.cmake

后续我们在 CLion、VS Code 或终端编译自己的项目(比如 NQtQmlExample)时,不再需要往项目的 CMakeLists.txt 里塞满各种交叉编译参数-例如sysroot位置, Qt位置, 交叉编译工具链位置 。项目保持最纯净的桌面级编写逻辑,只需在配置项目时用 qt-cmake 替代标准 cmake 即可:

bash 复制代码
# 使用生成的快捷 qt-cmake 脚本进行工程配置
/usr/local/qt6/bin/qt-cmake \
  -DCMAKE_BUILD_TYPE=Release \
  -G Ninja \
  -S /path/to/your/NQtQmlExample \
  -B /path/to/your/NQtQmlExample/build-target

# 进入构建目录一键编译
cd /path/to/your/NQtQmlExample/build-target
cmake --build .

把编译出来的可执行文件直接拷贝至树莓派。运行,收工!

相关推荐
864记忆3 小时前
OD车牌号获取流程
qt
满天星83035775 小时前
【Qt】信号和槽 (一)(概述和基本使用)
开发语言·c++·qt
郝学胜_神的一滴7 小时前
Qt 高级开发 027: QTabWidget自定义样式表美化实战
c++·qt
Apibro8 小时前
【Linux】Qt Creator 中文输入法
linux·qt
Jun6268 小时前
QT(5)-第三方日志系统
开发语言·数据库·qt
sycmancia8 小时前
Qt——多页面切换组件
开发语言·qt
落羽的落羽9 小时前
【项目】JsonRpc框架——功能测试、项目总结
linux·服务器·开发语言·c++·qt·算法·机器学习
bush417 小时前
嵌入式linux学习记录七,中断
linux·嵌入式
Jun6261 天前
QT(3)-线程中使用控件
开发语言·qt