搞嵌入式Qt有几年了,最让我记忆犹新的不是某个复杂业务界面,而是给一块aarch64板卡做Qt 5.14.2静态交叉编译的那段经历。板子存储不到1GB,还得跑一个带图表的控制软件,动态库方案光是部署依赖就得小两百兆,根本塞不下。没办法,只能走“静态编译+交叉编译”这条路。这篇文章把流程完整捋一遍:从交叉工具链怎么装、依赖怎么解决、Qt源码怎么配置,到应用怎么静态链接、最后上板怎么调,每一步都给出我实测过的命令和参数,顺带把踩过的坑也一并写清楚。适合打算在ARM64设备上部署Qt应用、或者正在被交叉编译折磨的开发者。
1. 需求拆解与方案选型:为什么非要静态交叉编译不可
1.1 静态编译和动态编译,差的不是一个后缀名
很多刚接触嵌入式Linux的同事对“静态”的理解就是“编译出一个很大的可执行文件”,这个方向是对的,但背后的取舍远不止体积这么简单。
动态编译的可执行文件里只保留对共享库的引用,运行时由系统动态加载器去找libQt5Widgets.so.5、libQt5Core.so.5这些文件。优点是多个进程可以共享同一份库代码,节省物理内存;缺点是目标板上必须精确具备对应版本、对应路径的共享库,版本稍微一偏,程序起来就是雪花屏或者直接error while loading shared libraries。
静态编译则把所有Qt库和第三方库的代码直接链接进ELF文件,运行时不依赖任何Qt相关so。为此要付出的代价是体积大、内存里没有共享的可能、链接时对库的依赖顺序非常敏感。但对于一个独立运行、单进程、不叠加其他Qt程序的嵌入式设备来说,静态链接的部署优势是压倒性的——一个文件拷过去,直接跑,不用折腾根文件系统。
两种方案的实际区别我列个表:
| 对比项 | 静态编译 | 动态编译 |
|---|---|---|
| 可执行文件体积 | 大,常见30~50MB起步 | 小,几MB |
| 目标板依赖 | 仅Linux内核和基础C库 | 需要完整Qt运行库及依赖树 |
| 部署复杂度 | 拷贝单个文件即可 | 需配置LD_LIBRARY_PATH或/etc/ld.so.conf |
| 多进程内存共享 | 不支持 | 支持 |
| 升级维护 | 整体重新编译 | 替换单个so文件 |
| 静态链接顺序要求 | 高,库顺序错就undefined reference | 低 |
我这次选静态,原因很直接:设备出厂后没人去维护库依赖,固件更新就是替换一个主程序。
1.2 为什么偏偏选Qt 5.14.2
Qt版本那么多,为什么锁定5.14.2?我当时的考量有三点:
一是生态成熟度。5.14.2是2019年底发布的补丁版本,修复了一堆QPA平台插件相关的问题,同时对嵌入式Linux的设备无关平台抽象层支持得很完整。网上搜“Qt5.14.2”能找到大量论坛帖子、厂商SDK都是基于这一版,遇到问题基本都能查到解法。
二是授权和下载问题。5.15以后虽然也是不错的选择,但开源包下载流程变复杂了,很多团队并不想掺和授权判断的事情。5.14.2走开源路线,configure时加-opensource -confirm-license一次性通过,没有卡顿。
三是实际硬件适配。我手里这块板卡的SoC厂商BSP里就给Qt5.14做了输入设备和framebuffer适配,自己手动编Qt往往要处理芯片特有的输入事件校准,选5.14可以顺带复用厂商的补丁思路。
当然,5.12 LTS也是一个稳妥的选择,但我需要一些5.13/5.14才有的QPA改进,所以没有进5.12的序列。
1.3 交叉编译的整体思路:三条线同时推进
交叉编译,说白了就是“在A机器上编译出B机器能运行的代码”。这里A泛指x86_64宿主机,B是aarch64目标板。要做到这件事,三个条件缺一不可:
- 工具链是aarch64的。编译器虽然跑在x86上,但生成的汇编指令是指向ARM64的。
- 依赖库是aarch64的。Qt静态库本身要用aarch64编出来,Qt依赖的zlib、libpng、freetype等系统库也必须是aarch64版本。
- 最终的应用程序二进制是aarch64的,并且没有对x86库的引用。
这里面最容易翻车的就是第二点。很多人以为给g++加上-march=armv8-a就万事大吉,结果configure过了、make到一半报cannot find -lXtst,或者链接时一堆未定义引用,基本都是“库的架构不对”或“压根没有交叉版静态库”。
所以整体流程我是这样安排的:先准备好宿主和工具链,再解决aarch64依赖库,然后用Qt源码编出aarch64静态库,最后用这套库去编应用。后面的章节就是沿着这条线展开的。
2. 环境准备:宿主机装好双架构依赖,这是半个重点
2.1 宿主机系统与基础软件
我使用的宿主机是Ubuntu 20.04 x86_64,一块常规开发机。用22.04也可以,但要注意个别依赖包名可能有调整。先更新系统并安装编译Qt所需的基础工具:
sudo apt update sudo apt install build-essential git perl python3 g++ make \ libglib2.0-dev libfontconfig1-dev libfreetype6-dev \ libxkbcommon-dev libx11-dev \ ninja-buildninja-build现在不是硬性需求,但后面如果要配合CMake使用会很方便。Python是Qt构建系统工具链的一部分,5.14.2的configure脚本和构建脚本会调用,所以提前装好。
这里有个小经验:如果宿主机上已经装了大量Python 2相关的工具链,务必在编译之前确认默认python命令指向哪个版本。Ubuntu 20.04默认只有python3,但Qt 5.14的构建脚本在某些场景下仍会查找python或python2。为了避免后期卡壳,建议显式指定PYTHON=python3环境变量,后面配置那一步我会写出来。
2.2 安装aarch64交叉编译工具链
Ubuntu官方仓库里可以直接装到aarch64的交叉工具链,虽然工具链版本不是最新,但对Qt 5.14来说完全够用:
sudo apt install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu装完以后,编译器的前缀是aarch64-linux-gnu-,也就是说普通gcc的位置上现在有aarch64-linux-gnu-gcc、aarch64-linux-gnu-g++、aarch64-linux-gnu-ld等一整套工具。验证一下:
aarch64-linux-gnu-gcc -v能看到类似这样的输出就说明工具链可用了:
Target: aarch64-linux-gnu gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.2)Ubuntu的交叉工具链默认使用/usr/aarch64-linux-gnu作为sysroot根目录,里面会通过符号链接挂载multiarch的include和lib目录。这个机制等下配合arm64库安装非常方便,等于我们不用手工搭建一整套目标系统根文件系统。
2.3 启用arm64 multiarch并安装Qt依赖库
这里就是整个环境准备阶段最核心的一步:让宿主机能直接apt安装arm64版本的库。
Ubuntu的multiarch机制允许系统同时存在不同架构的软件包,但需要先显式声明:
sudo dpkg --add-architecture arm64 sudo apt update这之后就能用:arm64后缀安装aarch64版本的库了。以Qt静态编译常用的依赖为例,我装的是这组:
sudo apt install libz-dev:arm64 \ libpng-dev:arm64 \ libjpeg-dev:arm64 \ libfreetype-dev:arm64 \ libfontconfig1-dev:arm64 \ libglib2.0-dev:arm64 \ libx11-dev:arm64 \ libxkbcommon-dev:arm64 \ libgl1-mesa-dev:arm64 \ libharfbuzz-dev:arm64这里面有几个库Qt静态编译时不是非必需,但装上以后可以减少很多意外。比如libgl1-mesa-dev:arm64,即使你在configure里禁用OpenGL,某些版本的系统头文件检查仍然可能触发对GL库的探测,有头文件和库文件在,configure就少一个自找麻烦的路径。
注意,这里装的很多库只有动态版本。Ubuntu的multiarch仓库普遍不提供.a静态库,所以如果你非要Qt去静态链接系统的libxcb等库,光靠这一步不够。这也影响了我后面的configure参数选择——直接弃用xcb平台插件,改用Linux framebuffer平台,避免陷入“找静态xcb库”的泥潭。
2.4 pkg-config必须指向arm64,否则configure会捡到x86的配置
交叉编译Qt时最容易忽略的是pkg-config。默认情况下,pkg-config只搜索宿主机的/usr/lib/x86_64-linux-gnu/pkgconfig,如果Qt的configure探测某个库时调用到pkg-config,它可能拿到x86版本的头文件路径,最后编排出来的命令不伦不类。
我采用的做法是强制pkg-config只认arm64的.pc文件。在编译的整个过程中保持如下环境变量:
export PKG_CONFIG_LIBDIR=/usr/lib/aarch64-linux-gnu/pkgconfig export PKG_CONFIG_SYSROOT_DIR=/这里PKG_CONFIG_LIBDIR告诉pkg-config去哪个目录找.pc文件,PKG_CONFIG_SYSROOT_DIR=/表示.pc文件里的prefix路径就是当前根目录下的路径,不需要再加前缀。这个组合在Ubuntu交叉编译中比较顺。
为了让两个环境变量在终端重新打开后依然生效,建议把它们写进一个env_aarch64_qt.sh,每次编译前source一下。后面每次打开新终端我都执行一次,以免配置漂移。
# env_aarch64_qt.sh export PATH=/usr/bin:/bin:/usr/local/bin:$PATH export PKG_CONFIG_LIBDIR=/usr/lib/aarch64-linux-gnu/pkgconfig export PKG_CONFIG_SYSROOT_DIR=/ export PYTHON=python3还有一个细节:在宿主机同时装有x86和arm64库的情况下,configure有时候会直接调用系统编译器去编译测试程序,导致探测结果完全错误。所以PATH里一定要确保aarch64-linux-gnu-工具链在前面,并且configure要显式指定-platform和-xplatform,后面实战那一步会看到。
3. Qt 5.14.2源码获取与configure配置:成败都在这一步
3.1 下载源码与初始化构建目录
Qt 5.14.2的完整源码是一个qt-everywhere-src-5.14.2.tar.xz包,官方archive站点能下到,大概500MB左右。有些镜像站也提供,比如中科大、清华的镜像里会有qtproject目录。下载后用tar解压:
wget https://download.qt.io/archive/qt/5.14/5.14.2/single/qt-everywhere-src-5.14.2.tar.xz tar -xf qt-everywhere-src-5.14.2.tar.xz cd qt-everywhere-src-5.14.2这里要解释一下Qt源码包的结构:它包含qtbase、qtdeclarative、qttools、qtsvg等几十个模块,而我们实际经常用的核心模块全在qtbase里。configure脚本、qmake、QtCore、QtGui、QtWidgets都在qtbase,所以主流程编译时间主要集中在qtbase。
建议把编译目录单独放在一个磁盘剩余空间大于30GB的分区上,因为完整编译一次Qt 5.14.2占用的磁盘空间非常大。我自己碰到过/目录满了导致make崩溃的尴尬,后来直接把源码放到/work/build,再软链接到home目录方便管理。
3.2 configure参数逐项拆解
这是全篇最重要的一个命令。我最终跑通的configure命令如下:
./configure -release -static -opensource -confirm-license \ -platform linux-g++ \ -xplatform linux-aarch64-gnu-g++ \ -prefix /opt/qt5142-aarch64-static \ -no-opengl -no-glib -no-icu \ -qt-zlib -qt-libpng -qt-libjpeg -qt-freetype \ -linuxfb -no-xcb -no-eglfs -no-kms \ -no-feature-cups -no-feature-printdialog \ -skip qtwebengine -skip qt3d -skip qtcanvas3d \ -nomake examples -nomake tests -nomake benchmarks一个参数一个参数来看。
-release:编译发布版本,裁剪掉调试符号和一些调试辅助逻辑,生成的可执行文件小很多,链接也快。
-static:这是核心中的核心。它通知Qt的构建系统,所有库以静态库形式编译,生成.a文件而不是.so文件。这个参数会影响qmake生成Makefile时的链接配置,后面应用编译时也会自动带上-static相关选项。
-opensource -confirm-license:选择开源协议并跳过交互式确认。不写这个,configure会停下来问你接受不接受开源协议。
-platform linux-g++:宿主机使用的Qt编译平台。因为我们是在x86_64 Ubuntu上跑configure,这个平台用来编译构建工具,所以用linux-g++即可。
-xplatform linux-aarch64-gnu-g++:目标平台,即交叉编译目标。Qt5.14的源码里自带了一份qtbase/mkspecs/linux-aarch64-gnu-g++的配置,里面默认的编译器就是aarch64-linux-gnu-g++,跟Ubuntu工具链前缀完全吻合,所以不需要自己写mkspec。
-prefix /opt/qt5142-aarch64-static:安装目录。编译完成后所有头文件、静态库、qmake工具都会装到这里。选用/opt下的路径,主要是为了和应用项目的路径隔离,也方便多版本切换。
-no-opengl -no-glib -no-icu:这三个都是降低依赖的开关。-no-opengl对很多嵌入式系统适用,因为我们用的是linuxfb,不走GPU加速。-no-glib去掉GLib事件循环集成,-no-icu去掉Unicode ICU库依赖。ICU在静态编译场景中是个大头,有时能省掉好几MB的体积,而且在纯文本界面或简单英文界面下完全用不到。如果你的程序需要复杂的Unicode排序规则或某些本地化特性,再考虑保留ICU,不过代价是交叉编译ICU本身又是一层麻烦。
-qt-zlib -qt-libpng -qt-libjpeg -qt-freetype:这四个参数让Qt使用源码里自带的第三方库,而不是去链接系统的arm64版本。为什么要这样?因为Qt bundled的第三库会确保和Qt本身同时编译、同时静态链接,从源头规避“系统库没有静态版本”的问题。尤其对于libpng和libjpeg,很多平台的交叉环境中只有动态库,而Qt又需要它们,所以干脆用自带的。
-linuxfb -no-xcb:平台插件的选择。-linuxfb启用Linux framebuffer QPA插件,这是嵌入式Linux最常用的无窗口系统平台方案,直接写/dev/fb0。-no-xcb则禁止编译X11的xcb平台插件,因为xcb的依赖树非常深,想全静态链接几乎要自己把xcb相关的一整套库都交叉编译成.a,工作量太大,而且Ubuntu multiarch也没提供静态xcb库。对硬件没有X11服务器的板卡来说,xcb插件本来也用不上。
-no-eglfs -no-kms:这两个通常和GPU绑定。如果没有对应的Mali、Adreno等GPU EGL库,编译eglfs插件只会引入一堆GL相关的链接错误,所以直接禁用。
-no-feature-cups -no-feature-printdialog:禁用打印支持。嵌入式设备几乎不接打印机,关掉能砍掉QtPrintSupport模块里一部分依赖。
-skip qtwebengine -skip qt3d -skip qtcanvas3d:跳过体积巨大且编译难度高的模块。qtwebengine要动Chromium,交叉编译难度指数级上升,静态编译更是噩梦;qt3d和qtcanvas3d依赖OpenGL,我们关了OpenGL,它们也用不了。
-nomake examples -nomake tests -nomake benchmarks:只编译库本身,不编译示例和测试,省下大量时间。
这些参数不是拍脑袋定的,而是基于两个原则:第一,目标板上没有X11、没有GPU、只有一个framebuffer设备;第二,把所有能砍的依赖都砍掉,减少静态链接过程中“缺库、找库、库顺序错”的概率。
3.3 编译安装:耐心等,顺便准备ccache
configure通过后,会提示你接下来运行make。这里我直接用多线程编译,但不要一次性打满所有线程,避免内存耗尽。我当时是8核机器,用:
make -j8整个编译过程视机器性能大约需要40分钟到两个小时。期间如果你用top观察,会发现编译器确实是aarch64-linux-gnu-g++在处理源码,这就说明xplatform解析成功。
编译完成后,执行安装:
make install这一步会把aarch64静态Qt库、头文件、mkspec和qmake全部复制到/opt/qt5142-aarch64-static目录下。
这里有一个实用建议:如果你平时要来回编多遍,或者还要尝试不同配置,给ccache留个位置。Ubuntu上装一个:
sudo apt install ccache然后在/usr/local/bin/下建几个软链让ccache包住交叉编译器:
sudo ln -sf /usr/bin/ccache /usr/local/bin/aarch64-linux-gnu-gcc sudo ln -sf /usr/bin/ccache /usr/local/bin/aarch64-linux-gnu-g++因为/usr/local/bin通常在/usr/bin之前,Qt在检测交叉编译器时会优先找到ccache包装后的命令,后续重编不同功能组合时能省一半时间。
4. 应用侧静态编译实战:从qmake到单个可执行文件
4.1 一个最小的Qt Widgets示例工程
库已经编好,现在来验证能不能用它编出一个aarch64可执行文件。我建了一个非常简单的窗口程序,包含一个按钮和一个文本标签,用来验证QtWidgets、qtbase的静态链接是否通畅。
先写C++源文件main.cpp:
#include <QApplication> #include <QPushButton> #include <QVBoxLayout> #include <QWidget> int main(int argc, char *argv[]) { QApplication app(argc, argv); QWidget window; window.setWindowTitle("AArch64 Static Qt Test"); QVBoxLayout *layout = new QVBoxLayout(&window); QPushButton *btn = new QPushButton("Click Me"); QObject::connect(btn, &QPushButton::clicked, []() { qDebug() << "button clicked on aarch64"; }); layout->addWidget(btn); window.resize(480, 320); window.show(); return app.exec(); }然后写test.pro:
QT += core gui widgets CONFIG += release static TARGET = qt-test TEMPLATE = app SOURCES += main.cpp这里CONFIG += static是qmake层面的静态链接表态,它会通知Qt提供的链接脚本去寻找静态版本的Qt库。同时,因为我们要用linuxfb平台,我在pro里补充了一个关键项:
QTPLUGIN += qlinuxfb为什么需要QTPLUGIN?因为静态编译时,Qt的平台插件和各类插件不再由动态加载器在运行时按需加载,而是直接作为库的一部分被链接进主程序。QTPLUGIN += qlinuxfb告诉qmake需要链接libqlinuxfb.a这个静态插件库。
4.2 使用交叉qmake编译
编译应用要用我们自己刚刚构建出的qmake,而不是系统里可能安装的其他版本。直接用绝对路径调用:
source /work/env_aarch64_qt.sh export PATH=/opt/qt5142-aarch64-static/bin:$PATH cd /work/application /opt/qt5142-aarch64-static/bin/qmake test.pro -o Makefile make -j4这里PATH中最重要的是Qt的bin目录,这样make调用的qmake和编译过程中一些工具才会正确。编译完成后,马上用file命令验证:
file qt-test看到类似输出:
qt-test: ELF 64-bit LSB executable, ARM aarch64, dynamically linked, ...这里注意,目标文件虽然是ARM aarch64,但仍可能显示dynamically linked,因为Linux基础C库(libc、libm)默认仍然是动态链接的。完全不动态链接需要用到-static-libgcc -static-libstdc++以及全静态的libc,但嵌入式环境里我们通常保留动态链接的libc,因为静态libc会有DNS解析、NSS等不少怪问题。我们的目标只是“不依赖Qt的额外so”,不要求100%动静态。
验证一下它没有引用任何Qt动态库:
aarch64-linux-gnu-readelf -d qt-test | grep NEEDED正常输出里应当只有libc.so.6、libstdc++.so.6、libm.so.6、libgcc_s.so.1这些基础运行时库,而不会有libQt5Widgets.so.5等。这说明Qt相关代码全部被静态吞进去了。
4.3 关于strip和体积控制
静态编译的可执行文件体积感人,我编完的测试程序有37MB。没strip之前可能超过50MB,因为里面包含了完整的符号表。对嵌入式部署来说,体积直接影响Flash占用和加载速度,所以发布前执行:
aarch64-linux-gnu-strip qt-teststrip完体积能降到28MB左右。如果要进一步压缩,可以在pro里加上编译选项微调:
QMAKE_CXXFLAGS_RELEASE += -Os -s-Os让编译器以优化体积为目标,-s在链接时直接去掉所有符号表。这两者结合起来能再挤出几MB。但是别指望压到10MB以内,Qt Widgets静态链接的体量摆在那里,除非你改用Qt Quick/QML再外加裁剪模块,否则这个数量级很正常。
4.4 拷贝到目标板运行
把qt-test拷贝到目标板任意目录,确保权限可执行:
chmod +x qt-test ./qt-test板子上如果有/dev/fb0且当前用户有权限访问framebuffer,就能看到窗口。按下按钮,终端会打印出qDebug内容。这说明Qt应用在无窗口系统的Linux framebuffer上已经跑通了。
如果板子上没有framebuffer设备,也可以加QT_QPA_PLATFORM=offscreen环境变量让它跑在离屏模式,用于验证程序逻辑,但不能看到真实UI。还有一种做法是配一个linuxfb:fb=/dev/fb0参数指定设备路径。
5. 交叉编译排雷手记:我实际踩过的坑
5.1 configure卡在“Unknown option”或“Missing Python”
这类问题通常不是选项本身不存在,而是configure脚本对系统环境过于敏感。Qt 5.14的configure是perl脚本和shell脚本混合实现,它需要python命令存在。Ubuntu 20.04默认只有python3,且许多版本里没有提供python软链。于是configure在探测python时返回空,直接报错。
解决方法很简单,在环境变量里显式声明PYTHON=python3,或者建一个软链sudo ln -s /usr/bin/python3 /usr/bin/python。我第一次就是被这个卡了十分钟,后来加了环境变量瞬间通过。
5.2 make时报“cannot find -lGL”
出现这个错误说明Qt里某些模块仍然在尝试链接OpenGL库,通常是Qt SVG或Qt 3D等模块的问题,也可能是configure的-no-opengl没有生效,又或者是某个依赖模块依然引用了GL库。
我的处理分两步:第一,确认命令行里-no-opengl真的传进去了——有时候configure的缓存会干扰,建议每次修改configure参数前删除源码目录下的.qmake.super等缓存文件。第二,用grep搜索构建失败时对应的Makefile里是否有-lGL字样,找到以后手工把那个LIBS变量里的-lGL删掉。更彻底的做法是确保系统里有libgl1-mesa-dev:arm64,这样即使误引用也能链接上。
5.3 静态链接应用时“undefined reference toqt_plugin_query_metadata”
这个错误很有代表性,说明程序虽然链接了静态平台插件,但qmake的插件元数据注册机制没有生效。根本原因是QTPLUGIN += qlinuxfb只在qmake的某些配置环境下会自动生成plugin import语句,但如果你用了CMake或者手写了复杂的构建脚本,这个自动机制就失灵了。
解决办法是在源码里强制声明导入插件:
#include <QtPlugin> Q_IMPORT_PLUGIN(QLinuxFbIntegrationPlugin)Q_IMPORT_PLUGIN宏会把插件的初始化代码和元数据静态注册到当前编译单元。加了这行以后再编译,链接错误就会消失。如果你的程序需要其他平台插件,比如qoffscreen,还要同时加Q_IMPORT_PLUGIN(QOffscreenIntegrationPlugin)。
5.4 目标板运行时报“Failed to open /dev/fb0”或“Permission denied”
这是linuxfb最常见的运行问题,本质取决于目标板的framebuffer设备是否存在、当前用户是否有权限访问。
可以用下面几个步骤排查:
- 检查是否有设备节点:
ls -l /dev/fb*,有些板卡在启动时没有创建fb0,需要检查内核配置和设备树。 - 检查权限:
chmod 666 /dev/fb0或者把运行用户加入video组。开发阶段用chmod 666最快,上线时建议走udev规则。 - 如果板子支持HDMI或LCD屏,必须确保内核驱动已经把显示输出从终端切到了fb设备,否则可能出现黑屏但进程不报错的情况。
5.5 链接时出现一堆“undefined reference”且都与libpng/libjpeg有关
静态链接时,库之间的依赖是单向的。比如QtGui.a里有png解码代码引用png_read_image之类的符号,而libpng.a在命令行的顺序如果排在QtGui之前,链接器扫到QtGui的时候还不知道后面会有一个libpng.a能补上它的符号,于是直接报undefined reference。
常规解法是调整LIBS顺序,让被依赖的库排在依赖它的库后面。Qt的qmake在静态模式下已经自动做了大量排序,但遇到自定义的第三方静态库时还是会乱。这时候最简单的兜底方案是给链接器加-Wl,--start-group和-Wl,--end-group,让链接器反复扫描这组库:
QMAKE_LFLAGS += -Wl,--start-group -lpng -ljpeg -lz -Wl,--end-group--start-group会告诉链接器这组库之间可以来回引用,解决循环依赖和顺序敏感问题。注意,这只是一个手段,不能无脑堆所有库进去,否则链接时间会变长。
5.6 configure明明设置了-prefix,但安装到别的目录
这类问题通常是环境变量INSTALL_PREFIX或者旧配置缓存干扰。如果之前在同一目录跑过不同配置的configure,config.cache和.qmake.cache会残留,导致新参数不生效。
所以每次调整configure参数前,最稳的做法是重新解压源码包,或者至少执行:
make distcleandistclean会清理所有生成的Makefile和缓存文件。我后来养成了一个习惯:一个源码目录只跑一种配置,换参数就解压到新目录,从根上杜绝缓存串扰。
5.7 静态编译的Qt库太大,能不能裁剪
能。Qt支持功能裁剪,通过-no-feature-xxx可以把不需要的特性从编译中排除。5.14.2里特性名可以查qtbase/src/corelib/global/qfeatures.h,或者直接翻configure -feature-* -list-features。比如不需要老式的QTextCodec、不需要某些网络模块,都能减少一些体积。
但功能裁剪要谨慎,减多了会让你的代码被迫兼容“残缺版Qt”。我一般只裁剪掉打印对话框、CUPS、少数不常用模块,主要靠-no-opengl -no-icu和-skip qtwebengine来控体积,这样既省空间又不至于在写业务功能时被Qt限制。
5.8 编译速度慢,内存不足
Qt源码编译时大量模板实例化,内存和CPU都是瓶颈。如果make -j8导致内存耗尽,可以降为make -j4或-j2。还可以利用ccache大幅加速后续重编,前提是前面已经用ccache包装了交叉编译器。
另一个影响因素是磁盘I/O,尤其是NTFS挂载盘或网络文件系统上编译会慢到怀疑人生。把源码解压到本地固态盘的分区上,编译时间能缩短一半。
6. 这套方案可以扩展到哪些场景
搞定Qt 5.14.2的aarch64静态交叉编译后,我顺手把同样的思想复用了几个地方。
一个是nginx的aarch64移植。nginx本身依赖pcre和zlib,把工具链换成aarch64-linux-gnu、源码里通过./configure --with-cc=aarch64-linux-gnu-gcc就能编出ARM64版可执行文件。核心逻辑和Qt交叉编译完全一样:工具链先行、依赖库用arm64版本、注意configure检测。有了Qt这块硬骨头的经验,移植nginx只花了不到半小时。
另一个是给第三方C/C++库做交叉编译。我在板卡上用到了sqlite和openssl,都是源码configure后指定CC=aarch64-linux-gnu-gcc和--host=aarch64-linux-gnu,编译出静态库后塞进Qt应用里。整体思路都可以复用,总结起来就是四个步骤:选对工具链、选对sysroot、理清依赖关系、控制configure选项。
最后说一点个人体会:静态交叉编译本质上是个“把复杂留给自己,把简单留给部署”的过程。编译期确实辛苦了点,configure参数要反复试、库顺序要一遍遍调,但换来的是目标板上近乎零依赖的部署体验。后来这块板子量产,升级固件就是替换一个二进制文件,从来没有因为缺库、版本冲突在客户现场翻过车。如果你也正在折腾Qt的交叉编译,照着这份手册踩一遍,大概率能省下好几个通宵。