Qt5.14.2 的 aarch64 静态交叉编译,是我这两年折腾嵌入式 Linux 项目时做过最“磨人”但也最“上头”的一件事。先说结论:整个过程从下载源码到最终产出一个静态链接好的 Qt 可执行文件,正常情况下需要大半天;如果对工具链或 sysroot 不熟,踩坑时间可以轻松翻倍。这篇手册就是把我踩过的坑、筛查过的参数、验证过的命令全部整理出来,给准备在 aarch64 板子上跑 Qt 应用的朋友一条能直接走通的路。适合遇到以下场景的人参考:手里有一块 ARM64 开发板(树莓派、瑞芯微 RK 系列、全志、飞腾等),需要在宿主机(x86 的 Ubuntu)上交叉编译出 Qt 程序,再拷到板子里跑;而且希望最终程序不依赖板子上的 Qt 动态库,做到“一个二进制文件丢过去就能运行”。静态交叉编译的核心价值就在这里:部署简单、环境隔离、不污染目标系统。
1. 方案选型思考:为什么选 Qt 5.14.2、静态编译和独立 sysroot
动手之前先聊清楚方案怎么来的,这样后面配参数时才不会一脸懵。
1.1 Qt 5.14.2 版本定位
Qt 5.14.2 是 The Qt Company 在 2020 年发布的一个开源补丁版本。它属于 Qt 5.14 系列,这个系列有几个很实际的优势:
- 对嵌入式 Linux 的 platform 插件(linuxfb、eglfs、minimal)支持成熟稳定,嵌入式场景大量使用。
- 5.15 之后尽管也有开源版,但部分模块对商业用户和开源用户划分得更清晰,很多团队为了“省事”继续钉在 5.14 LTS 语义上。
- 5.14.2 对 C++11/C++14 支持良好,第三方库如 OpenSSL 1.1.1、zlib 1.2.11 等版本都兼容良好。
- 它可以在相对老旧的 Ubuntu 20.04 宿主机上直接编译,工具链要求不高,GCC 9 就能满足。
从我个人的项目经历来看,只要是跑在 aarch64 嵌入式设备上的 Qt Widgets 或 Qt Quick(非 WebEngine)应用,5.14.2 基本是“低风险、高成功率”的选择。如果你要用 Qt WebEngine,那我建议直接换 Qt 5.15 或 6.x,因为 WebEngine 在 5.14 里已经不太好编译了,架构上也不是为静态编译准备的。
1.2 静态编译的收益与代价
静态编译意味着把所有 Qt 库直接链进你的可执行文件,目标板子上不需要安装任何 Qt 运行库。代价也很明显:
- 可执行文件体积大。一个简单的 Widgets 程序,release 静态编译后通常 15MB 到 25MB 左右,如果引入了 Qt Widgets、Qt Network、Qt Sql 等模块,30MB 也有可能。
- 编译时间更长。Qt 自身需要完整编译成静态库(.a),工程代码每次改动重链接时间也高一些。
- 部分与平台相关模块(如 xcb、DBus、ICU)在静态编译时特别容易出兼容问题,所以需要合理裁剪。
但在无人值守的嵌入式设备、工业控制板卡、或者“不想折腾目标系统环境”的场景里,静态编译带来的部署便利是动态编译没法比的。
1.3 交叉编译关键思路:宿主机 + 目标 sysroot + Qt 源码
交叉编译说白了就是“在 A 机器上编译出 B 机器能运行的程序”。那问题来了:编译器(aarch64-linux-gnu-gcc)知道目标 CPU 是 ARM64,但它怎么找到目标系统的头文件和库文件?答案是 sysroot。
sysroot 就是一个“目标系统的根目录”,里面放着 /usr/include、/usr/lib 等目录。交叉编译时,编译器不去看宿主机的 /usr/include,而是去 sysroot 里找。所以:
- 宿主机:x86_64 Ubuntu,负责跑编译工具链和 Qt 编译脚本。
- 目标 sysroot:一个通过 debootstrap 创建的 ARM64 Ubuntu 根文件系统,集体积约 400MB 到 1GB,里面包含 aarch64 版本的 glibc、zlib、libpng 等基础库。
- Qt 源码:解压在宿主机上,通过 configure 指定“架构平台为 linux-aarch64-gnu-g++”,all 输出目录安装到宿主机某个前缀路径(如 /opt/qt5.14.2-aarch64-static),生成的都是 aarch64 的静态库。
这三者的关系理顺了,后面每一步都只是充实细节。
注意:很多人一上来就执行 ./configure 然后 make,结果各种“cannot find -lGL”“mkspecs 找不到”报错扑面而来。核心原因就是没提前把 sysroot 和工具链的关系想清楚。
2. 环境准备:宿主机、工具链与目标根文件系统
2.1 宿主机系统与基础工具
我用的宿主机是 Ubuntu 20.04.6 LTS x86_64。其他发行版也可以,但命令和包名会不同,建议新手直接照搬 Ubuntu 20.04 或 22.04。需要的基础包:
sudo apt update sudo apt install -y build-essential cmake git wget tar xz-utils \ python3 python3-pip \ libssl-dev \ flex bison \ pkg-config \ qemu-user-static \ debootstrapqemu-user-static 后面会用来跑 ARM64 的 debootstrap 二级引导,这个千万别漏。
查看宿主机的架构和编译器:
uname -m gcc --version只要输出是 x86_64、gcc 9.x 或 11.x,就没问题。如果你宿主机是 ARM64(比如某些 ARM 服务器),那就不叫“交叉”编译了,直接本机编译更省事。
2.2 安装 aarch64 交叉编译工具链
Ubuntu 20.04 官方源里就有现成的 aarch64 工具链,版本 9.3:
sudo apt install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu装完后验证:
aarch64-linux-gnu-gcc --version # 输出类似: # aarch64-linux-gnu-gcc (Ubuntu 9.4.0-1ubuntu1~20.04) 9.4.0这套工具链基于 glibc 2.31,对应 Ubuntu 20.04 的 ARM64 根文件系统,版本是匹配的。如果你用目标板厂商提供的私有工具链,比如 Linaro GCC 7.5 或 Buildroot 生成的工具链,思路一样,但 sysroot 路径和 glibc 版本对应关系要重新核对。
交叉工具链安装后,/usr/aarch64-linux-gnu 目录里已经自带了一个基础 sysroot:
ls /usr/aarch64-linux-gnu # 里面包含 include、lib 等目录这就是最简单的 sysroot,但里面只有基础 C/C++ 运行库,其他第三方库(zlib、libpng、openssl 等)需要我们自己补进去。
2.3 用 debootstrap 制作一个完整的 ARM64 sysroot
直接用 /usr/aarch64-linux-gnu 做 sysroot 不完整,装 Qt 依赖时经常缺头文件、缺 .so 软链。最佳实践是用 debootstrap 创建一个干净的 ARM64 Ubuntu 根文件系统。
创建目录:
sudo mkdir -p /opt/aarch64-sysroot sudo chown $USER /opt/aarch64-sysroot执行 debootstrap(这里用到 qemu-user-static,Ubuntu 的 debootstrap 会自动结合 binfmt 去模拟执行 ARM64 的维护脚本):
sudo debootstrap --arch=arm64 --variant=minbase focal /opt/aarch64-sysroot http://ports.ubuntu.com/ubuntu-ports/参数说明:
- --arch=arm64 指定目标架构。
- --variant=minbase 只安装最小系统,够编译库就行。
- focal 对应 Ubuntu 20.04。
- 源使用 Ubuntu Ports,因为 ARM64 的软件包不在普通 ubuntu.com 源里。
这个过程会下载约 100MB 到 200MB 的网络包,取决于网络情况。完成后检查:
ls /opt/aarch64-sysroot/etc/ # 应该能看到 os-release 等文件接着往这个基础 sysroot 里装 Qt 编译需要的 ARM64 开发包。方式有两种:
- 直接 chroot 进去,用 apt 安装。
- 用 apt-get download 下载 .deb,手工解包进去。
方法 1 更省事。先准备 chroot 环境:
sudo cp /usr/bin/qemu-aarch64-static /opt/aarch64-sysroot/usr/bin/ sudo chroot /opt/aarch64-sysroot /bin/bash进入之后:
export DEBIAN_FRONTEND=noninteractive echo "deb http://ports.ubuntu.com/ubuntu-ports/ focal main universe" > /etc/apt/sources.list apt update然后安装需要的基础开发包:
apt install -y \ libc6-dev \ zlib1g-dev \ libssl-dev \ libpng-dev \ libjpeg-dev \ libfreetype6-dev \ libfontconfig1-dev \ libsqlite3-dev \ libicu-dev \ libdbus-1-dev \ libglib2.0-dev \ libegl1-mesa-dev \ libgles2-mesa-dev \ libgbm-dev \ libinput-dev \ libxkbcommon-dev \ libxcb1-dev这一套装下来,sysroot 里的依赖基本齐了。退出 chroot:
exit注意:在 chroot 安装时,如果板子最终运行环境不需要某些显示服务,比如纯 framebuffer 场景,xcb、xkbcommon 等可以不装。但为了后续扩展性,我建议先把 libxkbcommon-dev 装上;xcb 相关的库对静态编译不友好,后面配置 Qt 时我会直接禁掉。
2.4 目录规划与版本对照
建议所有路径固定下来,避免后期配置混乱:
| 项目 | 路径 |
|---|---|
| Qt 源码目录 | /opt/src/qt-everywhere-opensource-src-5.14.2 |
| Qt 安装前缀 | /opt/Qt5.14.2-aarch64-static |
| ARM64 sysroot | /opt/aarch64-sysroot |
| 交叉编译工具链 | /usr/bin/aarch64-linux-gnu-* |
Qt 源码可以从 Qt 官方 archive 页下载 qt-everywhere-opensource-src-5.14.2.tar.xz,约 500MB 左右,解压后约 2GB 以上,注意磁盘空间至少留 20GB(编译 Qt 静态库很占空间)。
3. sysroot 依赖补全与 Qt configure 前的准备
3.1 检查 sysroot 中的软链是否完整
很多人在编译依赖库时碰到 “cannot find -lGL” 这类错误,往往不是真的缺 libGL.so,而是 sysroot 里只有 libGL.so.1,没有开发用的软链 libGL.so。
在 chroot 里安装的 dev 包会自动生成软链,所以一般问题不大。但我遇到过 debootstrap + chroot 后,某些库的 .so 软链没更新到最新版本,这时可以手动修复。
最典型的例子是 libGL:
ls -l /opt/aarch64-sysroot/usr/lib/aarch64-linux-gnu/libGL.so如果没有,就检查对应版本文件后手动创建:
sudo ln -s libGL.so.1 /opt/aarch64-sysroot/usr/lib/aarch64-linux-gnu/libGL.so同样的手法适用于 libEGL、libGLESv2、libgbm 等。
3.2 交叉编译第三方依赖库(zlib、openssl、libpng)
虽然 sysroot 里已经装了大部分库的 aarch64 版本,但为了减少不确定因素,我建议把下面这几个关键库自己源码编译一遍,以静态库的形式放进 sysroot。理由很简单:用系统 .deb 装的多半是动态库,Qt 静态编译时依赖外部库,如果外部库是动态的,那“全静态”的目标就泡汤了。
先编 zlib,它是 Qt 的基础依赖:
wget https://zlib.net/zlib-1.2.11.tar.gz tar xzf zlib-1.2.11.tar.gz cd zlib-1.2.11 CC=aarch64-linux-gnu-gcc \ AR=aarch64-linux-gnu-ar \ RANLIB=aarch64-linux-gnu-ranlib \ ./configure --prefix=/opt/aarch64-sysroot/usr --static make -j$(nproc) sudo make install这样生成的 libz.a 会直接放进 sysroot 的 /usr/lib/aarch64-linux-gnu 和 /usr/include 中。注意 zlib 的 configure 脚本比较特殊,不能用标准的“--host”参数,直接给它 CC 环境变量就行。
接下来是 OpenSSL 1.1.1,主要给 Qt Network 的 SSL 支持用:
wget https://www.openssl.org/source/openssl-1.1.1w.tar.gz tar xzf openssl-1.1.1w.tar.gz cd openssl-1.1.1w ./Configure linux-aarch64 \ --prefix=/opt/aarch64-sysroot/usr \ --openssldir=/opt/aarch64-sysroot/usr/ssl \ shared \ no-asm make -j$(nproc) sudo make install_sw注意这里 OpenSSL 用了 shared 方式。其实 Qt 支持 OpenSSL 动态加载,编译 Qt 时设了 -openssl-linked 才会硬链。为了减少静态链的复杂度,我建议编译 Qt 时使用 -openssl-runtime,构建阶段不直接链 OpenSSL,运行阶段也尽量让 Qt 加载板子自己的 libssl.so。
libpng 和 libjpeg、freetype、fontconfig 则直接用 sysroot 里的 dev 包就行,不用全手工编。Qt configure 时会通过 pkg-config 或直接检查头文件/库文件来确认这些依赖是否存在,只要 sysroot 里装好了 arm64 版本,Qt 的交叉编译脚本能自动找到。
3.3 设置交叉编译环境变量
建议写一个环境变量脚本,每次编译前 source 它:
vim /opt/setenv-aarch64.sh内容如下:
export PATH=/usr/bin:$PATH export CROSS_COMPILE=aarch64-linux-gnu- export CC=${CROSS_COMPILE}gcc export CXX=${CROSS_COMPILE}g++ export AR=${CROSS_COMPILE}ar export AS=${CROSS_COMPILE}as export LD=${CROSS_COMPILE}ld export RANLIB=${CROSS_COMPILE}ranlib export STRIP=${CROSS_COMPILE}strip export SYSROOT=/opt/aarch64-sysroot export PKG_CONFIG_PATH=/opt/aarch64-sysroot/usr/lib/aarch64-linux-gnu/pkgconfig export PKG_CONFIG_SYSROOT_DIR=/opt/aarch64-sysroot export PKG_CONFIG_ALLOW_SYSTEM_CFLAGS=1 export PKG_CONFIG_ALLOW_SYSTEM_LIBS=1 export QT_PREFIX=/opt/Qt5.14.2-aarch64-static export PATH=$QT_PREFIX/bin:$PATHPKG_CONFIG_SYSROOT_DIR 这个变量非常关键,它告诉 pkg-config 所有依赖路径都要自动加上 sysroot 前缀,防止交叉编译时误抓到宿主机的 x86 库。
4. Qt 5.14.2 源码编译详细步骤
4.1 修改 mkspecs 架构文件
Qt 源码的 mkspecs 目录下没有现成的 linux-aarch64-gnu-g++,但有通用的 linux-g++ 和 linux-arm-gnueabi-g++ 可以参考。我们需要手动创建一个:
cd /opt/src/qt-everywhere-opensource-src-5.14.2 cp -r mkspecs/linux-arm-gnueabi-g++ mkspecs/linux-aarch64-gnu-g++然后编辑 mkspecs/linux-aarch64-gnu-g++/qmake.conf:
vim mkspecs/linux-aarch64-gnu-g++/qmake.conf关键内容修改为:
MAKEFILE_GENERATOR = UNIX CONFIG += incremental QMAKE_INCREMENTAL_STYLE = sublib include(../common/linux.conf) include(../common/gcc-base-unix.conf) include(../common/g++-unix.conf) QMAKE_CC = aarch64-linux-gnu-gcc QMAKE_CXX = aarch64-linux-gnu-g++ QMAKE_LINK = aarch64-linux-gnu-g++ QMAKE_LINK_SHLIB = aarch64-linux-gnu-g++ QMAKE_AR = aarch64-linux-gnu-ar cqs QMAKE_OBJCOPY = aarch64-linux-gnu-objcopy QMAKE_NM = aarch64-linux-gnu-nm QMAKE_STRIP = aarch64-linux-gnu-strip QMAKE_INCDIR = /opt/aarch64-sysroot/usr/include QMAKE_INCDIR_QT = /opt/Qt5.14.2-aarch64-static/include QMAKE_LIBDIR = /opt/aarch64-sysroot/usr/lib/aarch64-linux-gnu QMAKE_LIBDIR_QT = /opt/Qt5.14.2-aarch64-static/lib QMAKE_LIBS = -lz -lpng -ljpeg -lfontconfig -lfreetype load(qt_config)这里 QMAKE_LIBS 行不是必须的,但我在实际编译时发现 Qt 的某些配置检查(比如 configure 里的 freetype 检查)会漏掉 sysroot 里的库路径,手动加上之后问题少很多。
同时在 mkspecs/linux-aarch64-gnu-g++/qplatformdefs.h 中,保持引用通用头文件即可:
cat mkspecs/linux-aarch64-gnu-g++/qplatformdefs.h #include "../../linux-g++/qplatformdefs.h"4.2 configure 关键参数详解
回到 Qt 源码根目录,执行配置前先确认环境变量已经 source:
source /opt/setenv-aarch64.sh ./configure --help > /tmp/qt_configure_help.txt然后使用下述参数:
./configure \ -static \ -release \ -opensource \ -confirm-license \ -xplatform linux-aarch64-gnu-g++ \ -prefix /opt/Qt5.14.2-aarch64-static \ -nomake examples \ -nomake tests \ -no-compile-examples \ -skip qtwebengine \ -skip qtwebview \ -skip qt3d \ -skip qtdoc \ -skip qtlocation \ -skip qtwayland \ -skip qtsensors \ -skip qtserialbus \ -skip qtsvg \ -skip qttools \ -skip qttranslations \ -skip qtxmlpatterns \ -no-opengl \ -no-xcb \ -no-icu \ -no-dbus \ -no-ssl \ -no-feature-xcb \ -qt-zlib \ -qt-pcre \ -qt-libpng \ -qt-libjpeg \ -qt-freetype \ -no-fontconfig \ -no-glib \ -no-cups \ -no-tslib \ -no-pch \ -no-avx \ -no-avx2 \ -no-mips_dsp \ -no-mips_dspr2 \ -optimize-size \ -silent这些参数逐个说下选择理由:
- -static:编译静态 Qt 库。这是整个项目的核心目标。
- -release:只编译 release 版本,不编 debug,大幅缩短时间。
- -opensource -confirm-license:接受开源协议,避免交互卡住。
- -xplatform linux-aarch64-gnu-g++:指定刚才创建的交叉架构文件。
- -prefix:安装目录。
- -nomake examples -nomake tests:不编示例和测试,省时间省空间。
- -skip 一系列模块:把用不到的 Qt 模块全部跳过。特别是 qtwebengine,它体积巨大且对 aarch64 静态编译很不友好。
- -no-opengl:如果你的界面是纯 QWidget 或 QML 2D 场景,不需要 OpenGL。如果板子支持 EGLFS 且需要 GPU 加速,则留 -opengl es2,但依赖更复杂,新手不建议。
- -no-xcb:静态编译 xcb 插件容易出各种幺蛾子(xcb 依赖 xcb libs、xkbcommon 等),嵌入式场景默认用 linuxfb、eglfs 或 minimal 就够了。
- -no-icu:ICU 库体积庞大,中文处理若无特殊要求,Qt 自带的 QString 在处理 UTF-8 时足够用。如果用了 Qt Quick Controls 2 的部分复杂排版可能有点影响,但大部分场景没事。
- -no-icu 之后,QRegularExpression 不支持 Unicode 属性类,但基础正则完全够用。
- -no-ssl:先完全关闭 SSL 支持。如果后面应用需要 https,再回过来调 -openssl-runtime,避免一开始就被 OpenSSL 交叉编译问题卡住。
- -qt-zlib -qt-pcre -qt-libpng -qt-libjpeg -qt-freetype:让 Qt 使用自带的第三方源码库。这样可以保证所有图像、字体、压缩相关的代码都是静态编进 Qt 库中的,避免依赖 sysroot 的动态库。
- -no-fontconfig:嵌入式环境字体配置简单,用 Qt 自带 freetype 即可。不过要注意,没有 fontconfig 时 Qt 的字体匹配策略会退化为直接加载指定字体文件,需要在代码里设置 QFontDatabase 或通过环境变量 QT_QPA_FONTDIR 指定字体目录。
- -no-feature-xcb:再次确保 xcb 相关功能被彻底移除。
- -optimize-size:静态编译时体积优化很重要,能让最终二进制小一些。
configure 执行完,看到类似 “Qt is now configured for building” 的提示,就说明交叉配置通过了。此时还会生成一个 config.summary,我强烈建议你打开看看,确认里面各种 “OpenSSL: no”“xkbcommon: no” 都和预期一致。
4.3 make 编译与安装
编译 Qt 静态库比较耗时,建议开启多核:
make -j$(nproc) 2>&1 | tee /tmp/qt_build.log这个步骤通常需要 30 到 90 分钟,取决于机器性能。如果中途报错,不要整段重跑,先查 log 尾部定位。
我遇到过的几个编译期问题:
- “cannot find -lGL”:说明 configure 阶段没有正确处理 OpenGL 库。解决办法是去掉 -no-opengl 改为 -no-opengl 后仍然报错?实际上如果明确不使用 OpenGL,应确保 configure 参数里同时有 -no-opengl 且 sysroot 不要安装 libegl1-mesa-dev 等包。版本不一致会造成 configure 自动检测到 EGL 试图启用。最干净的方案是 sysroot 中不装任何 GL 相关 dev 包,只用 -no-opengl。
- “collect2: error: ld returned 1 exit status” 伴随 undefined reference to `qt_version_tag':这个一般出现在最终应用链接而不是 Qt 编译中。解决方式是在编译应用时加 -fuse-ld=bfd 或直接使用 aarch64-linux-gnu-g++ 作为链接器,同时确保 Qt 的 lib 路径放在最前。
- 编译卡在 qtwebengine:检查你是否真的传了 -skip qtwebengine。如果没跳过,它的下载和编译是噩梦,所以务必检查配置参数。
编译完成后安装:
sudo make install安装完成后检查:
ls /opt/Qt5.14.2-aarch64-static/lib/libQt5Core.a # 应该能看到 libQt5Core.a 等静态库到这里,Qt aarch64 静态交叉编译环境就算建好了。
5. 第一个 aarch64 静态 Qt 程序:从 qmake 到部署
5.1 编写测试程序并交叉编译
先写一个最简单的 QWidget 程序验证环境:
// main.cpp #include <QApplication> #include <QLabel> int main(int argc, char *argv[]) { QApplication app(argc, argv); QLabel label("Hello aarch64 Qt Static"); label.resize(400, 200); label.show(); return app.exec(); }创建工程文件:
# test.pro QT += widgets SOURCES += main.cpp TARGET = hello_qt然后交叉编译:
source /opt/setenv-aarch64.sh cd /path/to/test /opt/Qt5.14.2-aarch64-static/bin/qmake test.pro make这里有个细节:qmake 从 Qt 安装前缀里的二进制会生成一个 Makefile,它内部引用的编译器路径来自我们在 qmake.conf 里写的 QMAKE_CC、QMAKE_CXX 变量,所以编译时不用手动指定编译器。
最终生成 hello_qt,检查它的架构:
file hello_qt输出里应该包含 “ELF 64-bit LSB executable, ARM aarch64”。再检查动态依赖:
aarch64-linux-gnu-readelf -d hello_qt | grep NEEDED正常情况下只有基础的 libc、libm、libgcc_s 等系统库,没有 libQt5Core.so、libQt5Widgets.so。
5.2 部署到目标板与运行验证
把 hello_qt 传到板子上,可以直接用 scp、U 盘或者网络共享。运行前做准备:
export QT_QPA_PLATFORM=linuxfb export QT_QPA_FONTDIR=/usr/share/fonts/truetype/dejavu ./hello_qt如果板子有 HDMI 或 LVDS 屏且不依赖 GPU,linuxfb 平台插件是最稳妥的选择。如果板子启用了 DRM/KMS,也可以尝试:
export QT_QPA_PLATFORM=eglfs ./hello_qt静态编译的 linuxfb 插件已经编码进 libQt5PlatformSupport.a 里,运行时不需要额外的 .so 插件文件,这也是静态编译带来的部署便利之一。
5.3 体积优化与 strip
编译出来的 hello_qt 可能很大,比如 20MB 甚至更多。可以用 aarch64-linux-gnu-strip 去掉符号表:
aarch64-linux-gnu-strip --strip-unneeded hello_qtstrip 之后通常能减小 30% 到 50% 体积。再配合 upx 之类的压缩工具,体积还能进一步缩减。注意 UPX 对 aarch64 架构的支持不如 x86 完善,我实际试过有时压缩后运行不稳定,所以项目要求稳定性时建议只 strip,不上 UPX。
6. 常见问题与坑:我踩过的 7 个关键点
6.1 问题排查速查表
| 现象 | 常见原因 | 解决办法 |
|---|---|---|
| configure 时报 “Compiler cannot create executables” | 工具链与 sysroot 不匹配,或 CC 环境变量未设置 | 检查交叉编译环境变量,确认 source 正确 |
| cannot find -lz / -lpng | sysroot 中缺少对应静态库或软链 | 在 sysroot chroot 中安装 dev 包或手动编译静态库 |
| cannot find -lGL | sysroot 中安装 GL 开发包,但 Qt 配置为 -no-opengl 或反之 | 统一配置:不用则 sysroot 不装 GL dev 包并加 -no-opengl |
| xcb 相关报错 | 静态编译 xcb 插件依赖过多库 | 使用 -no-xcb,用 linuxfb 或 eglfs |
| qmake 生成的 Makefile 里编译器路径是 x86 的 gcc | qmake.conf 中 QMAKE_CC/QMAKE_CXX 设置错误 | 改回 aarch64-linux-gnu-gcc/g++ |
| 运行时找不到字体 | 没有 fontconfig,Qt 无法自动找字体文件 | 通过 QT_QPA_FONTDIR 指定字体目录 |
| 程序运行时报 “could not find or load the Qt platform plugin linuxfb” | 动态编译时的经典错误,但静态编译若裁剪过度也可能出现 | 静态编译时确认 platform 插件已编入,可用 QT_DEBUG_PLUGINS=1 查看 |
6.2 独家避坑心得
第一,Qt 源码 configure 阶段千万别嫌参数多就不写。做嵌入式静态编译,config 参数就是“保命符”。少一个 -skip 模块,编译时间就可能多半小时;少一个 -no-xcb,后面可能牵出一串无法链接的 xcb 符号。
第二,sysroot 里不要频繁增删 dev 包。我一开始反复 install/remove 各种交叉编译库,结果软链混乱,之后 make 时不时冒出怪异错误。后来我用了一台干净的 Ubuntu 20.04 虚拟机,单独跑构建,才彻底稳定下来。建议你也专门准备一个“构建专用环境”,别在常用开发环境里搞。
第三,如果 configure 时出现了自动检测到本机库的情况,可以用 -qt-* 系列参数强制 Qt 使用内置代码,而不要让 configure 去 sysroot 外部搜索。另外 configure 里的 -I 和 -L 参数不会自动加 sysroot 前缀,所以尽量用 -qt-* 来限制依赖搜索范围。
第四,关于 qmake 的版本信息,编译应用时容易出现 Qt 库版本不匹配问题。确保环境变量里 PATH 的 /opt/Qt5.14.2-aarch64-static/bin 在最前面,否则系统可能有另一个 x86 版本的 qmake 被优先找到,生成错误 Makefile。
6.3 一个容易忽略的 hidden 坑:sysroot 里 /lib 与 /usr/lib 的软链
Ubuntu 的 ARM64 根文件系统里,/lib 通常是 /usr/lib 的软链,/lib/aarch64-linux-gnu 或 /usr/lib/aarch64-linux-gnu 都有可能被编译器搜索。Qt 的 qmake.conf 里我写了 QMAKE_LIBDIR 指向 /usr/lib/aarch64-linux-gnu,但链接器默认搜索顺序还包括 /lib/aarch64-linux-gnu。如果某些库只存在于其中一个目录,就会导致找不到。
建议把两个目录都配置进去:
QMAKE_LIBDIR = /opt/aarch64-sysroot/usr/lib/aarch64-linux-gnu /opt/aarch64-sysroot/lib/aarch64-linux-gnu对应的 include 目录也类似:
QMAKE_INCDIR = /opt/aarch64-sysroot/usr/include这个细节很多教程不提,但实际踩到过好几次。如果编译某个依赖库时出现 “/usr/include/xxx.h: No such file or directory”,多半就是 include 路径只配置了系统默认路径,没把 sysroot 的路径传进去。
7. 扩展:静态链接其他第三方库
实际项目中很少只靠 Qt 几个模块就能搞定,多少会用到第三方库,比如 curl、protobuf、sqlite 等。静态交叉编译思路是一样的:把第三方库编成 .a,放进 sysroot,然后在工程的 .pro 文件里用 LIBS += -lxxx 链接进去。
以前段时间我在 RK3588 板子上做一个数据采集工具为例,除了 Qt Widgets,还需要 MQTT 通信。选择了 mosquitto 库,交叉编译步骤:
wget https://mosquitto.org/files/source/mosquitto-1.6.15.tar.gz tar xzf mosquitto-1.6.15.tar.gz cd mosquitto-1.6.15 make CC=aarch64-linux-gnu-gcc \ CXX=aarch64-linux-gnu-g++ \ AR=aarch64-linux-gnu-ar \ STRIP=aarch64-linux-gnu-strip \ LDFLAGS="--sysroot=/opt/aarch64-sysroot" \ CFLAGS="-I/opt/aarch64-sysroot/usr/include" \ WITH_TLS=no \ WITH_SRV=no sudo make install DESTDIR=/opt/aarch64-sysroot然后工程的 .pro 文件里加:
LIBS += -lmosquitto INCLUDEPATH += /opt/aarch64-sysroot/usr/include这种“先编第三方库,再编 Qt 应用”的顺序,也是我在实际项目中验证过的最稳妥方式。
8. 最后再分享一点实际经验
这套 Qt 5.14.2 aarch64 静态交叉编译环境,我在多个项目上反复用过:RK3288、RK3399、树莓派 3B+、树莓派 4B 上都跑通了。最让我意外的是,同样的静态编译产物在很多板子上能直接运行,基本不用像动态链接方案那样为不同的板子准备不同的库集合。这也是我为什么坚持“哪怕编译时间长,也要上静态”。
如果你打算长期做 aarch64 嵌入式 Qt 开发,我建议把这篇手册里的所有路径、参数、脚本固化成一个 Dockerfile 或脚本仓库。这样以后不管是换机器还是移交同事,都能最多半小时重建整个环境。
另外,有个小技巧:编译 Qt 静态库时建议关闭系统的 “prelink” 服务和自适应文件系统索引服务,它们会在编译过程中频繁访问文件,导致 I/O 瓶颈。我第一编译 Qt 时没关,速度慢了将近一倍。
这篇文章没有写到的细节,比如特定板子 EGLFS 的显示适配、Qt Quick 场景的静态编译资源处理、QSS 资源裁剪等,网上资料也比较零散。如果你在某个具体环节卡住了,把 configure 的输出和 config.summary 发给我看,我们可以一起排查。毕竟交叉编译这东西,最怕的就是“看起来编译过了,跑起来全是问题”。