从拿到一块 aarch64 的目标板,到把 Qt5.14.2 通过静态交叉编译的方式跑起来,这个过程我前前后后折腾了快两个星期。中间踩过的坑,从工具链不匹配、sysroot 缺失,到 openssl 静态库没编进去、xcb 插件加载失败,每一个都能让新手卡上好几天。所以我把这套从零搭建 Qt5.14.2 aarch64 静态交叉编译环境的完整过程整理成手册,包括每一步的命令、参数、路径规划,以及我实际遇到的各种报错和处理方式。这篇文章适合正在做嵌入式 Linux 开发、需要把 Qt 应用部署到 ARM 平台、或者想把应用编译成单个独立可执行文件的朋友参考,照着做基本能少走一半弯路。
1. 为什么非要静态交叉编译
1.1 动态库方案为什么不适合我们的场景
先说说我为什么坚持用静态编译。大多数时候,我们拿到一块 ARM 开发板,第一反应是直接用板子自带的交叉编译工具链,把 Qt 动态编译出来,然后应用链接动态库,部署的时候把一整套.so拷到板子上。这个方案在开发阶段确实没什么问题,编译快、Qt 更新也不用重新编应用。但是到了产品化阶段,问题就来了。
首先是依赖链太乱。Qt 动态库本身就依赖一堆系统库,像 libfontconfig、libdbus、libxkbcommon,这些库在板子上不一定都有,或者版本跟编译环境不一致。你辛辛苦苦拷过去,运行的时候ldd一看,还差一大串。其次是版本冲突。如果板子上还有别人编译的 Qt 程序,用的 Qt 版本跟你不一致,那动态库一覆盖,对方立刻崩给你看。
静态编译就完全绕开了这些。应用把 Qt 核心库、平台插件、第三方依赖统统打进了同一个二进制里,部署的时候就是一个文件搞定。缺点是编译时间变长、可执行文件体积变大,但对于生产环境来说,稳定性和可维护性远比那几百 MB 的磁盘空间重要。我这次的场景是给一台 ARM 工控机上跑一个独立的 Qt 业务程序,目标板上不想装任何 Qt 运行库,所以静态交叉编译是唯一合理的选择。
1.2 静态编译的真正成本和边界
静态编译不是万能的,动手之前你得先搞清楚它的代价。第一是 Qt 本身的体积会变大。一个带 GUI 的静态 Qt 程序,release 编译出来一般在 10MB 到 40MB 之间,具体取决于你启用了多少模块。这还不算 Qt 自带的字体、qml 模块之类的东西。第二是插件必须静态注册。动态编译的时候,Qt 的 platform 插件(比如 linuxfb、eglfs、xcb)是单独的.so文件,运行时按需加载。静态编译时这些插件会被编译成.a或者直接编进可执行文件,需要你在代码里手动调用Q_IMPORT_PLUGIN,或者在.pro文件里用QTPLUGIN指定,这一块不处理好,程序会报could not load the Qt platform plugin的错误。
第三是第三方库的许可证问题。Qt 本身是 LGPL,静态链接意味着你不能简单地用动态替换的方式来遵守许可证,如果你的应用不开源,你就得考虑购买商业许可,或者确保你把对象文件提供给用户让他们能重新链接。这个法律问题虽然不属于技术范畴,但做产品之前一定要跟团队确认清楚。
另一个边界是工具链兼容性。静态编译只是让 Qt 应用不依赖 Qt 的.so,但你的可执行文件仍然依赖目标系统的 glibc、libstdc++ 和内核接口。如果你用高版本 glibc 的工具链去编译,放到低版本 glibc 的板子上跑,一样会报version 'GLIBC_2.xx' not found。所以交叉编译之前,第一件事就是把工具链和 sysroot 跟目标系统对齐。
2. 搭建前的基础设施
2.1 宿主机、工具链与目标系统的版本匹配
我这次用的宿主机是 Ubuntu 20.04 x86_64,目标板是 aarch64 架构的 Linux 系统,glibc 版本是 2.31,跟你自己的目标环境越接近越好。工具链我直接用的 Ubuntu 官方源里的gcc-aarch64-linux-gnu,版本是 9.4.0,配合 glibc 2.31 的 sysroot 使用。如果你要编译的目标板 glibc 版本更高,比如 Ubuntu 22.04 的 glibc 2.35,那我建议你在 22.04 的宿主机上装gcc-aarch64-linux-gnu,或者直接使用 ARM 官方提供的aarch64-none-linux-gnu工具链。
版本匹配是静态编译里最重要的一环,它决定你最终程序的兼容性边界。先看一下目标板上系统的信息,可以在板子上执行:
cat /etc/os-release ldd --version uname -a然后在宿主机上确认工具链版本:
aarch64-linux-gnu-gcc --version如果你的目标板还能联网,最简单的办法是用debootstrap生成一个跟目标板相同发行版版本的 rootfs 作为 sysroot。例如要生成 Ubuntu 20.04 的 arm64 rootfs:
sudo debootstrap --arch=arm64 --variant=minbase focal /opt/sysroot/aarch64 http://ports.ubuntu.com/ubuntu-ports/这步会下载一份完整的 arm64 根文件系统,里面包含了 arm64 版本的 glibc、libstdc++ 和一堆基础库。以后交叉编译第三方库的时候,装依赖就会方便很多。
2.2 sysroot 的制作与目录规划
sysroot 是什么?简单理解就是目标板的根文件系统在宿主机上的一个副本。交叉编译时,GCC 和链接器会到这个目录下去找头文件和库文件,而不是到宿主机的/usr/include里找,这样编出来的程序在目标板上才能找到正确的运行时依赖。
我的目录规划是这样的:
/opt/ ├── sysroot/ │ └── aarch64/ # arm64 rootfs,也就是 sysroot │ ├── usr/ │ ├── lib/ │ └── etc/ ├── qt-src/ │ └── qt-everywhere-src-5.14.2/ # Qt 源码 ├── qt5.14.2-aarch64/ # 目标板运行时的 Qt 安装目录(extprefix) └── qt5.14.2-aarch64-tools/ # 宿主机上的 Qt 工具目录(hostprefix)注意 Qt 交叉编译有个概念:-prefix是目标板上 Qt 安装的路径,-extprefix是 sysroot 之外的备选安装路径,-hostprefix是宿主机上放的qmake、moc、uic等工具所在的位置。交叉编译时,在宿主机上运行的 Qt 工具,比如 moc、uic、rcc,必须是用宿主机架构编译出来的,否则 x86 的机器跑不了 aarch64 的可执行文件。这点非常容易遗漏,后面我会细讲。
sysroot 准备好之后,一般板子上的/lib和/usr/lib都是符号链接,debootstrap 生成时已经处理好,不用动。你只需要确认编译器能用它:
echo 'int main(){return 0;}' > test.c aarch64-linux-gnu-gcc --sysroot=/opt/sysroot/aarch64 -o test test.c file test如果输出是ELF 64-bit LSB executable, ARM aarch64,说明工具链和 sysroot 基本没问题。
2.3 环境变量与常用工具
交叉编译 Qt 之前,我建议先把环境变量整理好。虽然 configure 可以通过参数指定,但环境变量能避免很多麻烦:
export SYSROOT=/opt/sysroot/aarch64 export CROSS=aarch64-linux-gnu export CC=$CROSS-gcc export CXX=$CROSS-g++ export AR=$CROSS-ar export RANLIB=$CROSS-ranlib export LD=$CROSS-ld export PKG_CONFIG_PATH=$SYSROOT/usr/lib/aarch64-linux-gnu/pkgconfig:$SYSROOT/usr/share/pkgconfig export PKG_CONFIG_SYSROOT_DIR=$SYSROOT export PKG_CONFIG_LIBDIR=$SYSROOT/usr/lib/aarch64-linux-gnu/pkgconfigPKG_CONFIG_SYSROOT_DIR和PKG_CONFIG_LIBDIR这两个变量在编译第三方库时非常关键,如果不设置,pkg-config 会去找宿主机的.pc文件,到头来链接的本机库不是你 sysroot 里的 arm64 库,会出各种诡异的链接错误。
还需要装一些基础工具:build-essential、pkg-config、flex、bison、libtool、cmake。Qt 的 configure 脚本在检测依赖时会用这些命令,少了哪个它可能就直接关闭某个特性,甚至编译到一半报错。
3. 依赖库的交叉编译
3.1 OpenSSL 静态库编译
Qt 的 QtNetwork 模块默认会尝试链接 OpenSSL。如果不启用 openssl 链接,Qt5Network 依然能编译,但 HTTP 的 HTTPS 请求就会运行时失败。对于要出产品的程序,HTTPS 基本上是必须的,所以 OpenSSL 还是老老实实编上。
我用的版本是 OpenSSL 1.1.1k,跟 Qt 5.14.2 完全兼容。下载源码后,在源码目录执行:
./Configure linux-aarch64 \ --prefix=$SYSROOT/usr \ --libdir=lib/aarch64-linux-gnu \ --cross-compile-prefix=aarch64-linux-gnu- \ no-shared \ no-testslinux-aarch64是 OpenSSL 对 aarch64 Linux 的配置目标,no-shared表示只生成静态库,因为我们要静态链接。配置完成后:
make -j$(nproc) make install编译完检查一下$SYSROOT/usr/lib/aarch64-linux-gnu/下是否有libcrypto.a和libssl.a,这一步就完成了。
注意:
--libdir=lib/aarch64-linux-gnu这个参数很容易漏。Ubuntu 的 arm64 系统默认把 64 位库放在这个目录,不指定的话 OpenSSL 会装到/usr/lib,虽然不影响链接,但 pkg-config 搜索路径经常会对不上,还是提前统一好。
3.2 zlib、libpng、libjpeg 等基础库
Qt 的图片插件需要 libpng 和 libjpeg,压缩相关的需要 zlib。理论上 Qt configure 可以启用-qt-zlib -qt-libpng -qt-libjpeg,让 Qt 自己编译内置版本,这是最省事的方式,不需要单独交叉编译这些库。但你也搞不清项目后面会不会直接用到这些库的 API,我最后还是把 zlib 和 libpng 单独编了一遍,放进 sysroot 里。
zlib 的交叉编译很简单:
CC=aarch64-linux-gnu-gcc \ AR=aarch64-linux-gnu-ar \ RANLIB=aarch64-linux-gnu-ranlib \ ./configure --static --prefix=$SYSROOT/usr make -j$(nproc) make installlibpng 的交叉编译稍微麻烦点,它依赖 zlib,需要让它的 Makefile 能发现 zlib 的头文件和库:
./configure \ --host=aarch64-linux-gnu \ --prefix=$SYSROOT/usr \ CC=aarch64-linux-gnu-gcc \ CPPFLAGS="-I$SYSROOT/usr/include" \ LDFLAGS="-L$SYSROOT/usr/lib/aarch64-linux-gnu" \ --enable-static --disable-shared make -j$(nproc) make install这些库属于比较基础的依赖,编译时一般不会出太大问题。真正让我折腾了很久的是显示后端的依赖。
3.3 显示后端相关依赖:xcb 与 linuxfb/eglfs 的取舍
Qt 在 Linux 上显示输出有几种平台插件:xcb(X11)、linuxfb(framebuffer)、eglfs(OpenGL ES + DRM/KMS)、wayland 等。对于 aarch64 的嵌入式设备,你需要根据目标硬件来决定用哪个。
如果你的目标板跑的是完整的桌面环境,有 X11 服务,那就需要 xcb。但 xcb 这套依赖非常重,它需要libxcb、xcb-util-*、libxkbcommon、libxkbcommon-x11一堆库,而且每库都得到 sysroot 里编一份静态版本。我这次的目标板是纯生产环境,没有 X11,只有 HDMI 直连的 LCD 屏,所以我直接放弃了 xcb,用linuxfb平台插件。
这样一下省掉了大量工作。Qt configure 的时候加-no-xcb就不会去检测 xcb 依赖,加了-linuxfb就会启用 framebuffer 平台插件。如果你的硬件支持 GPU 并且你有对应的 EGL 驱动,可以加-eglfs,显示效果更好还能实现 Qt Quick 的 OpenGL 加速。没有 GPU 或者驱动不全,老老实实linuxfb。
注意:linuxfb 确实是嵌入式里最稳的方案,但它没有硬件加速,绘图稍微复杂一点性能就上不去。如果你的应用有频繁的动画或复杂界面,建议先确认板子的 GPU 型号和驱动情况,再决定要不要上 eglfs。我这次做的是一个偏数据展示的界面,linuxfb 足够。
如果你的 Qt 版本要开启 eglfs,你还需要在 sysroot 里准备libgbm、libudev、libdrm这些库。一般用debootstrap生成的 rootfs 里没有这些开发包,可以用qemu-user配合 chroot 进 sysroot 用 apt 装,或者直接从目标板上的/usr/lib拷贝到 sysroot 里。这个方案比较灵活,但容易混入目标板上没用到的库,我还是偏向手动编译。
4. Qt5.14.2 的 configure 到 make
4.1 configure 参数逐项解析
拿到 Qt 源码之后,先用qt-everywhere-src-5.14.2.tar.xz解压,然后建立一个独立的 build 目录。Qt 官方说支持 out-of-source 构建,但 5.14 版本有些模块在 out-of-source 时还是会出问题,所以我直接采用了源码目录内编译,省事。
cd /opt/qt-src/qt-everywhere-src-5.14.2 ./configure \ -prefix /opt/qt5.14.2-aarch64 \ -extprefix /opt/qt5.14.2-aarch64 \ -hostprefix /opt/qt5.14.2-aarch64-tools \ -xplatform linux-aarch64-gnu-g++ \ -static \ -release \ -opensource \ -confirm-license \ -skip qtwebengine \ -skip qtwebview \ -skip qtwayland \ -nomake examples \ -nomake tests \ -no-xcb \ -linuxfb \ -no-opengl \ -openssl-linked \ -I$SYSROOT/usr/include \ -L$SYSROOT/usr/lib/aarch64-linux-gnu \ -qt-zlib -qt-libpng -qt-libjpeg逐个解释一下这些参数:
-xplatform linux-aarch64-gnu-g++:这个最关键。它告诉 Qt 使用qtbase/mkspecs/devices/linux-aarch64-gnu-g++这个配置,里面已经定义好了 aarch64 编译器、qmake.conf 等,不需要自己写 mkspec。-static:生成静态库,而不是.so。不加这个参数,后面全白搭。-release:编译 release 版本,裁剪调试信息,控制体积。-hostprefix:宿主机上的 Qt 工具安装目录,这个千万不能省。交叉编译的时候,bootstrapped 的 qmake、moc、uic 会被安装到这个目录,后续编应用要用。-skip qtwebengine:Qt WebEngine 模块在 aarch64 交叉编译时配置复杂度极高,静态编译更是重灾区,不用就跳过。-no-xcb:配合我们上面讲的,目标环境没有 X11。-linuxfb:启用 framebuffer 平台插件。-no-opengl:如果你确定不用 OpenGL,可以直接关掉,省去libGL的依赖。如果应用要用 QPainter 之类,不影响。-openssl-linked:跟 OpenSSL 静态链接,需要你在-I和-L里指定好头文件和库路径。
配置好之后,configure 会跑几十个测试项目。如果输出末尾没有明显的 error,就可以进入编译。
4.2 编译过程与时间、资源参考
make -j$(nproc)。我当时的机器是 8 核 16 线程,整个编译过程大约用了 45 分钟到 1 小时。如果你内存小于 8GB,建议把-j调低到 4,否则内存不够会 OOM,现象是编译到一半进程直接被杀死。编译的日志建议重定向到文件:
make -j$(nproc) 2>&1 | tee /tmp/qt_build.log编译过程中最常见的错误是某模块找不到某个头文件,或者链接时报 undefined reference。遇到这种问题不要急着重新配置,先看日志里的路径和名字。这一步我也踩了不少坑,下面第 6 章会集中说。
编译完成后:
make install安装结束之后,/opt/qt5.14.2-aarch64/下面应该是 Qt 的库、头文件、平台插件;/opt/qt5.14.2-aarch64-tools/下面应该是可以运行在宿主机上的bin/qmake、moc、uic等工具。
4.3 验证 Qt 交叉环境是否可用
安装完成之后最好马上做一个冒烟测试,确认你的 Qt 环境不是半残的。我一般会写个最简单的窗口程序测:
#include <QApplication> #include <QLabel> int main(int argc, char *argv[]) { QApplication app(argc, argv); QLabel label("Hello Qt on aarch64"); label.show(); return app.exec(); }编辑.pro文件:
QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = hello TEMPLATE = app SOURCES += main.cpp然后用 host 端的 qmake 编译:
/opt/qt5.14.2-aarch64-tools/bin/qmake hello.pro make file hello如果输出是ELF 64-bit LSB executable, ARM aarch64,说明基本环境是通的。然后把这个hello拷贝到目标板上执行。如果目标板有 framebuffer,就直接./hello -platform linuxfb,看到界面上出现文字,说明整套链路跑通了。
这里要特别注意:qmake一定要用-hostprefix里面的那个,不要用-prefix里的 qmake。-prefix里的 qmake 是给目标板用的,宿主机直接运行会报Exec format error。很多教程写的时候混着用,导致编译应用时链接到了错误架构的 Qt 库,这一点我后面还会强调。
5. 应用工程迁移到静态 Qt 环境
5.1 工程文件怎么写
你的项目要能在这个静态 Qt 环境下编译,.pro文件建议加上这些:
QT += core gui widgets network TARGET = myapp TEMPLATE = app CONFIG += static SOURCES += main.cpp mainwindow.cpp # 让 Qt 在 mingw 的 mkspec 下能自动找到平台插件 QTPLUGIN += qlinuxfb # 静态链接时把所有需要的 Qt 模块都链进来 LIBS += -lssl -lcrypto -ldl -lpthread -lzCONFIG += static不是必须的,因为你的 Qt 本身就是静态库,qmake 会自己处理。但显式加上没坏处,能让链接器参数更明确。QTPLUGIN += qlinuxfb是新版 Qt 推荐的静态插件注册方式,它会在编译时自动把 linuxfb 平台插件链进可执行文件。
如果你的代码里用了Q_IMPORT_PLUGIN,比如某些自定义插件,你需要把它放在main.cpp里:
#include <QtPlugin> Q_IMPORT_PLUGIN(QLinuxFbIntegrationPlugin)但用了QTPLUGIN就不用手动写。两种方式二选一,别重复。
5.2 静态插件注册与平台插件
这一节是很多人踩坑的重灾区。Qt 平台插件是动态架构的典型代表。动态编译时,platforms/libqlinuxfb.so放在可执行文件旁的platforms/目录下,Qt 运行时会去加载。静态编译时,插件被直接编进程序里,但 Qt 的插件注册机制还是按“按名字查找”的逻辑来跑,所以你得告诉链接器“我要用 linuxfb 插件”。
如果漏了这一项,运行程序时会报:
qt.qpa.plugin: Could not load the Qt platform plugin "linuxfb" in even though it was found.注意这句话很骗人,看起来像“找到了但加载不了”,其实根本原因是插件没有静态注册。加QTPLUGIN += qlinuxfb即可。
还有一种情况是你跑的程序用了多个平台插件,比如支持-platform linuxfb和-platform eglfs,那.pro里就写:
QTPLUGIN += qlinuxfb qeglfsQt 会把这些插件对应的目标文件全部链接进来。缺点是可执行文件会变大,但换来的是灵活性。
5.3 部署目标板的完整文件组织
静态编译出来的程序虽然不需要 Qt 库,但是有的资源还是得带。拿我这次的程序举例,部署到目标板上的文件结构大概是:
/opt/myapp/ ├── myapp # 静态可执行文件 ├── zh_CN.qm # 翻译文件 ├── config.ini # 程序配置 ├── fonts/ │ └── wqy-microhei.ttc # 中文字体 └── icons/ └── app.pngQt 应用在嵌入式 Linux 上经常遇到中文显示方框的问题。这个跟 Qt 本身无关,是系统里没有中文字体,或者 Qt 的 fontconfig 依赖没启用。静态编译时如果需要较好的字体匹配,Qt 会依赖于 fontconfig 库,这意味着你还得在 sysroot 里准备libfontconfig的静态库,并且在 configure 时不要加-no-fontconfig。我没有启用 fontconfig,而是在代码里直接指定字体文件:
QFont font("/opt/myapp/fonts/wqy-microhei.ttc"); font.setPointSize(12); QApplication::setFont(font);这种方式在 linuxfb 平台下非常可靠,也不依赖 fontconfig,强烈推荐嵌入式场景直接指定字体路径。
翻译文件这块,Qt 的QTranslator机制正常用。编译生成.qm文件,部署时放到固定路径,程序启动时加载:
QTranslator translator; if (translator.load("/opt/myapp/zh_CN.qm")) { app.installTranslator(&translator); }6. 常见问题与排查技巧实录
6.1 configure 阶段报错
configure 报错有两类,一类是依赖缺失,一类是交叉编译测试程序无法运行。
“The test for linking against libxcb ... failed”:这个要么是 xcb 依赖没编,要么是你没加-no-xcb。如果你目标平台不用 X11,直接加-no-xcb关闭;如果要支持 xcb,就得把 xcb 全套依赖(xcb-util、xcb-image、xcb-keysyms、xkbcommon 等)的静态库逐一编进 sysroot。
“The test for linking against libGL ... failed”:嵌入式板子一般没有完整的 OpenGL 开发库。要么-no-opengl,要么明确使用-opengl es2,并确保 sysroot 里有libgles2和libegl1的开发包。
还有一个比较隐蔽的问题:configure 会自动生成一堆小的测试程序,并尝试在宿主机上运行。交叉编译时这些测试程序是 aarch64 架构的,宿主机不能直接运行,所以 Qt 用了一个技巧:通过-device-option CROSS_COMPILE=...来让测试程序在模拟器或者目标板上运行。但我们用的linux-aarch64-gnu-g++mkspec 已经处理了这些,正常情况下不会卡死。如果你遇到 configure 卡在某个测试上超过十几分钟,去看config.log,里面有非常详细的错误原因,几乎所有 configure 问题都能在里面找到答案。
6.2 编译阶段报错
“undefined reference toSSL_new”,或者 HTTPS 相关代码编译报错,基本都是 OpenSSL 没链接进来。在.pro文件里加:
LIBS += -lssl -lcrypto如果已经加了还是报错,检查 OpenSSL 静态库是否真的编译出了libssl.a和libcrypto.a,以及 Qt configure 时-I和-L是否指向了 sysroot 的正确路径。
“cannot find -lfontconfig”:如果 QApplication 或者 QFontDatabase 相关的代码报这类错,说明你在 configure 时开了 fontconfig,但 sysroot 里没有。要么去编 fontconfig 的静态库,要么 configure 时加-no-fontconfig,然后在代码里手动指定字体文件。
“qmake: could not exec '/usr/lib/aarch64-linux-gnu/qt5/bin/qmake'”:这个错误很典型,是 PATH 里混入了系统自带的 Qt 工具,导致 make 的时候调用了错误的 qmake。解决办法是重新确认你的 PATH,并把/opt/qt5.14.2-aarch64-tools/bin放在最前面:
export PATH=/opt/qt5.14.2-aarch64-tools/bin:$PATH6.3 运行阶段问题
程序能启动,但界面上全是方块/乱码:字体缺失。参照第 5.3 节,用QFont加载指定字体文件。
程序启动后黑屏或者刷新缓慢:linuxfb 平台下,性能瓶颈通常在 framebuffer 的绘制方式。Qt 的 linuxfb 插件默认会用 memcpy 把窗口内容拷贝到/dev/fb0,如果屏幕分辨率高,比如 1920x1080,每次局部刷新都会很吃力。可以尝试用-platform linuxfb:fb=/dev/fb0来指定 framebuffer 设备,同时尽量缩小窗口尺寸或减少不透明窗口部件数量。
程序运行时提示This plugin does not support propagateSizeHints():这个是 linuxfb 插件很常见的提示,不影响功能,只是警告。忽略即可。
运行时报libgcc_s.so.1 must be installed for pthread_cancel to work:这个错误比较坑,它不一定表示真的缺库,而是静态编译时 gcc 的 pthread 取消逻辑没有正确处理。解决办法是在.pro里加QMAKE_LFLAGS += -Wl,--no-as-needed,或者在链接时显式带上-lpthread。这个报错在 aarch64 静态编译里出现的概率很高,我查了很多资料才定位到。
可执行文件在宿主机上能跑(x86),交叉编译后丢到板子上报 Exec format error:这种情况一般不是文件本身的问题,而是你没有用交叉编译的 Qt。检查file 你的程序,如果显示x86-64,说明你的 qmake 用错了,用了宿主机上系统自带的 Qt 的 qmake,而不是/opt/qt5.14.2-aarch64-tools/bin/qmake。
6.4 做完整套环境之后,我沉淀下来的几个习惯
第一个习惯是每次重编 Qt 之前,把 configure 参数原封不动存到一个 shell 脚本里,命名带日期,注释写明用途。这套环境一旦编译好,短期内不太会动,但是过了几个月再回来看,可能已经完全忘了当时为什么加某些参数。有脚本在,重装环境就是一条命令的事,不用费劲回忆。
第二个习惯是始终保留一份编译日志。Qt 这种大工程的编译问题,很多是在一百多屏日志之后才出现细微的错误。没有日志,出错之后只能重新编译一遍,浪费时间。有日志之后,直接grep -i error /tmp/qt_build.log几秒钟就能定位。
第三个习惯是我在实际操作中体会最深的一点:不要追求“一次配置永久使用”。不同项目对 Qt 模块的需求不一样,有的要 WebEngine,有的要 OpenGL,有的要 xcb,有的不要。我后来根据项目需求维护了两套 Qt aarch64 环境,一套精简版(linuxfb,无 OpenGL,无网络模块的依赖),一套完整版(带 network、sql、qml 等常用模块)。这样不同项目直接用对应的环境编译,不用每次重新 configure。
最后,静态交叉编译这个事,第一次做确实会觉得很繁琐,但当你看到目标板上只有一个孤零零的可执行文件,双击就能跑起来,再也不用担心缺库、版本冲突、部署环节出错的时候,之前的折腾都值了。希望这份手册能帮你少踩几个坑。