1. 为什么要在aarch64上折腾Qt静态编译如果你手上有Orange Pi CM5、树莓派这类aarch64开发板又打算用Qt做界面迟早会撞上这个问题板子上跑Qt程序要么依赖一大堆动态库要么版本对不上部署一次能折腾半天。动态链接的Qt程序拷到板子上ldd一看几十个.so找不到这种场景我遇到过太多次了。静态编译Qt的核心价值就一个把Qt运行时全部塞进可执行文件里拷过去就能跑不用在目标板上装Qt环境。这在嵌入式量产、现场部署、给客户演示的场景下省掉的是成倍的沟通成本和环境排查时间。但静态编译Qt5.14.2到aarch64坑比想象中多。Qt官方对静态编译的支持是能用但不推荐尤其是涉及serialport、svg、webengine这些模块时配置项写错一个字母就是几小时的编译白费。这篇手册就是把我从零搭建的完整过程拆开讲包括交叉编译工具链的选择、configure参数的取舍逻辑、编译过程中真实踩到的报错以及最后怎么验证产物真的能在板子上跑起来。适合谁看有Linux基础、用过Qt但没做过交叉编译的开发者正在用aarch64开发板做项目的嵌入式工程师以及被unknown module in qt: serialport这类报错折磨过的人。全文基于Qt5.14.2 aarch64 静态链接这条主线但配置思路对Qt5.15.x同样适用。提示静态编译Qt的构建时间在普通四核机器上大约2到4小时建议放在性能较好的x86_64主机上做不要在开发板上直接编译。2. 交叉编译工具链的选型与验证2.1 为什么不用板子自带的gcc很多人第一反应是在开发板上直接装Qt编译觉得原生编译最省事。我试过在Orange Pi CM5上直接编译Qt5.14.2结果是编译到一半内存爆了swap拉满进程被OOM Killer干掉。aarch64开发板通常内存2G到8GQt的编译峰值内存需求远超这个数尤其是qtdeclarative和qtwebengine模块。所以正确做法是在x86_64主机上用交叉编译工具链产出aarch64的二进制。主机负责编译板子只负责运行。工具链的选择上主流有三类工具链来源典型前缀适用场景注意事项Linaro官方aarch64-linux-gnu-通用aarch64 Linux版本较老glibc版本需匹配芯片厂商SDK如aarch64-none-linux-gnu-特定SoC优化与板子BSP绑定兼容性最好发行版仓库gcc-aarch64-linux-gnuUbuntu/Debian主机安装最方便推荐入门我个人的选择是Ubuntu主机上直接apt install gcc-aarch64-linux-gnu g-aarch64-linux-gnu原因是安装简单、版本可控、和主机glibc兼容性好。厂商SDK的工具链虽然针对性强但经常缺一些头文件配置起来反而麻烦。2.2 工具链安装与交叉编译能力验证安装命令很直接sudo apt update sudo apt install gcc-aarch64-linux-gnu g-aarch64-linux-gnu装完之后必须验证不能假设它能用。验证分两步第一步确认工具链存在且版本正常aarch64-linux-gnu-gcc --version aarch64-linux-gnu-g --version第二步写一个最小测试程序确认能编译出aarch64架构的可执行文件echo int main(){return 0;} test.c aarch64-linux-gnu-gcc test.c -o test_aarch64 file test_aarch64file命令的输出应该是ELF 64-bit LSB executable, ARM aarch64。如果显示的是x86-64说明你调用的还是主机gcc工具链没生效。注意交叉编译工具链的glibc版本必须小于等于目标板上的glibc版本。主机工具链用的glibc太新编出来的程序在板子上会报GLIBC_2.xx not found。用aarch64-linux-gnu-gcc -print-file-namelibc.so.6可以查看工具链链接的glibc版本和板子上ldd --version对比一下。2.3 sysroot的准备最容易被忽略的一步交叉编译Qt时configure需要知道目标系统的头文件和库在哪里这就是sysroot。很多人编译失败就是因为sysroot没配对。sysroot的来源有两种一是从开发板上直接拷贝/usr/include和/usr/lib二是用工具链自带的sysroot。我推荐从板子上拷贝因为这样能保证编译时链接的库版本和板子完全一致。操作方式# 在开发板上执行打包系统库 tar czf sysroot.tar.gz /usr/include /usr/lib /lib # 拷回主机后解压到指定目录 mkdir -p /opt/aarch64-sysroot tar xzf sysroot.tar.gz -C /opt/aarch64-sysroot如果板子空间紧张至少要把/usr/include下的基础头文件和/usr/lib下的libc、libpthread、libdl、libm拷过来。Qt的configure会检查这些库是否存在。3. Qt5.14.2源码获取与configure参数拆解3.1 源码包的选择为什么是5.14.2Qt5.14.2是Qt5系列里最后一个长期支持版本LTS官方维护到2025年。相比5.12.x它对aarch64的支持更完善相比5.15.x它的静态编译配置更稳定社区踩坑记录也更多。源码包从Qt官方归档下载qt-everywhere-src-5.14.2.tar.xz。注意不要下成qt-everywhere-opensource-src的旧版本5.14之后官方改了命名。下载后校验一下md5避免传输损坏导致解压后文件缺失。wget https://download.qt.io/archive/qt/5.14/5.14.2/single/qt-everywhere-src-5.14.2.tar.xz md5sum qt-everywhere-src-5.14.2.tar.xz tar xf qt-everywhere-src-5.14.2.tar.xz cd qt-everywhere-src-5.14.23.2 configure参数逐项解释configure是整个编译过程的核心参数写错就是几小时白费。我把关键参数分成四组来讲。第一组平台与工具链-xplatform linux-aarch64-gnu-g -device-option CROSS_COMPILEaarch64-linux-gnu- -sysroot /opt/aarch64-sysroot-xplatform指定的是qtbase/mkspecs/devices/或qtbase/mkspecs/下的mkspec目录名。linux-aarch64-gnu-g这个mkspec在5.14.2里是存在的如果找不到需要自己从linux-arm-gnueabi-g复制一份改。-device-option CROSS_COMPILE是给mkspec里的变量赋值告诉qmake用哪个前缀的工具链。第二组静态编译与安装路径-static -prefix /opt/qt5.14.2-aarch64-static -hostprefix /opt/qt5.14.2-host-static开启静态编译这是核心。-prefix是目标板上的安装路径-hostprefix是主机上qmake等工具的安装路径。这两个必须分开否则主机上的qmake会指向aarch64的库导致主机工具无法运行。第三组模块裁剪-skip qtwebengine -skip qtwebview -skip qtquickcontrols -skip qtquickcontrols2 -skip qtgamepad -skip qtserialbus-skip是跳过不需要的模块。qtwebengine基于Chromium静态编译几乎不可能成功直接跳过。qtquickcontrols和qtquickcontrols2如果不用QML可以跳过能省大量编译时间。但注意-skip qtserialport要慎重。如果你要用串口这个模块必须保留而且静态编译serialport有个已知问题后面会讲。第四组功能开关-no-opengl -no-openssl -no-cups -no-glib -no-iconv -no-feature-dbus -no-feature-icu这些是关闭不需要的功能。-no-opengl在无GPU的板子上必须加否则Qt会尝试链接OpenGL库导致失败。-no-glib关闭glib依赖减少运行时依赖。完整的configure命令./configure \ -prefix /opt/qt5.14.2-aarch64-static \ -hostprefix /opt/qt5.14.2-host \ -xplatform linux-aarch64-gnu-g \ -device-option CROSS_COMPILEaarch64-linux-gnu- \ -sysroot /opt/aarch64-sysroot \ -static \ -release \ -opensource \ -confirm-license \ -no-opengl \ -no-openssl \ -no-cups \ -no-glib \ -no-iconv \ -no-feature-dbus \ -no-feature-icu \ -skip qtwebengine \ -skip qtwebview \ -skip qtquickcontrols \ -skip qtquickcontrols2 \ -skip qtgamepad \ -skip qtserialbus \ -nomake examples \ -nomake tests-nomake examples -nomake tests能省掉大量编译时间examples和tests对最终产物没用。3.3 configure报错的常见原因configure阶段最常见的报错是Cannot find suitable mkspec。原因是-xplatform指定的目录不存在。解决方法是去qtbase/mkspecs/下确认目录名5.14.2里aarch64相关的有linux-aarch64-gnu-g和linux-arm-gnueabi-g前者是64位后者是32位。第二个常见报错是sysroot not found。检查-sysroot路径是否存在以及路径下是否有usr/include和usr/lib。第三个是CROSS_COMPILE not set。这个报错说明mkspec里的QMAKE_CXX等变量没被正确赋值检查-device-option的写法等号两边不能有空格。4. 编译过程中的真实踩坑记录4.1 serialport模块的静态编译陷阱如果你保留了qtserialport模块编译到qtserialport时大概率会遇到这个报错:-1: error: Unknown module(s) in QT: serialport这个报错的根因是静态编译时serialport模块的依赖没有正确传递。Qt的serialport模块依赖core和network但静态链接时qmake的模块依赖解析会出问题。解决方案是在configure时显式加上-qt-libudev让serialport使用libudev而不是内置的枚举方式-qt-libudev同时确保sysroot里有libudev的头文件和库。如果板子上没有udev可以改用-no-libudev但这样serialport的自动端口枚举功能会受限。另一个坑是编译完serialport后在项目里QT serialport仍然报unknown module。这是因为静态Qt的.prl文件没有正确生成。检查/opt/qt5.14.2-aarch64-static/lib/libQt5SerialPort.prl是否存在如果不存在说明serialport模块编译失败但被跳过了。需要单独进qtserialport目录重新qmake make。4.2 编译到qtdeclarative时的内存爆炸qtdeclarative是QML引擎编译时内存占用极高。在4G内存的主机上编译到qquicktextnode.cpp时经常被OOM Killer干掉。解决方法有三个一是加swap二是限制并行编译数三是单独编译这个模块。加swap最直接sudo fallocate -l 8G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile限制并行数用make -j2而不是make -j$(nproc)。虽然慢但不会爆内存。如果还是不行单独编译cd qtdeclarative /opt/qt5.14.2-host/bin/qmake make -j1 make install4.3 静态链接时的符号冲突编译到最后链接阶段可能会遇到multiple definition of的报错。这通常是Qt的静态库和系统库有同名符号。典型的是qtbase/src/corelib/global/qglobal.cpp里的qVersion()和某些系统库冲突。解决方法是加链接选项-Wl,--allow-multiple-definition但这是治标不治本。更稳妥的做法是在configure时加-no-feature-...关掉冲突的功能。比如-no-feature-getentropy可以避免和glibc的getentropy冲突。提示静态链接的符号冲突很难一次性定位建议在configure阶段就尽量精简功能减少和系统库的交集。4.4 编译时间与并行策略完整编译Qt5.14.2静态版在8核16G的主机上用make -j8大约需要1.5到2小时。如果内存只有8G建议make -j4。4G内存的话make -j2并且必须加swap。编译过程中可以用make -j8 21 | tee build.log把日志存下来方便出错时回溯。日志文件会很大几个G是正常的。5. 产物验证与目标板部署5.1 验证静态Qt的完整性编译安装完成后先检查/opt/qt5.14.2-aarch64-static/目录结构ls /opt/qt5.14.2-aarch64-static/lib/应该能看到libQt5Core.a、libQt5Gui.a、libQt5Widgets.a等静态库文件。如果只有.so没有.a说明-static没生效。再检查主机工具/opt/qt5.14.2-host/bin/qmake -v输出应该是QMake version 3.1并且Using Qt version 5.14.2。注意这个qmake是主机上运行的不是aarch64的。5.2 写一个最小测试程序验证静态Qt能不能用写一个最简单的Widgets程序#include QApplication #include QLabel int main(int argc, char *argv[]) { QApplication app(argc, argv); QLabel label(Hello aarch64 static Qt); label.show(); return app.exec(); }用静态Qt的qmake编译/opt/qt5.14.2-host/bin/qmake test.pro make file testfile输出应该是ELF 64-bit LSB executable, ARM aarch64, statically linked。注意statically linked这个关键词如果显示dynamically linked说明链接的还是动态库。再用ldd test检查静态链接的程序应该输出not a dynamic executable。5.3 部署到开发板并运行把编译好的test拷到开发板上scp test root192.168.1.100:/root/在板子上直接运行chmod x test ./test如果板子有显示环境X11或Wayland应该能看到窗口。如果没有显示环境可以用-platform offscreen测试./test -platform offscreen程序不报错就说明静态Qt的运行时是完整的。注意静态Qt程序体积会很大一个简单的Widgets程序可能就有20M到30M。如果板子存储紧张可以用strip去掉符号表能减小30%左右。但strip后调试信息会丢失量产版本可以strip调试版本保留。5.4 常见运行时问题静态Qt程序在板子上跑不起来最常见的原因是缺少字体。Qt的Widgets需要字体来渲染文字如果板子上没有/usr/share/fonts程序会报QFontDatabase: Cannot find font directory。解决方法是在板子上装一个最小字体包或者把字体文件打包进程序资源里。我通常会在板子上放一个DejaVuSans.ttf然后在程序里用QFontDatabase::addApplicationFont()加载。第二个常见问题是平台插件缺失。静态Qt默认只编译了libqlinuxfb.a和libqoffscreen.a如果板子用X11需要确保configure时没有-no-xcb并且sysroot里有X11的库。第三个是时区数据缺失。Qt的QDateTime依赖时区数据静态编译时如果没打包/usr/share/zoneinfo时间转换会出错。可以在程序里用QTimeZone::setDefaultTimeZone()手动设置。6. 静态编译Qt的取舍与适用边界6.1 静态编译不是万能药静态编译Qt解决了部署依赖问题但代价也很明显。首先是体积一个动态链接的Qt程序可能只有几百K静态链接后变成几十M。其次是更新困难Qt出了安全补丁静态程序必须重新编译整个Qt再重新编译程序不能只替换.so。所以静态编译适合的场景是程序功能固定、部署环境不可控、更新频率低。比如工业控制面板、医疗设备界面、车载终端。如果你的程序需要频繁更新或者板子存储空间紧张动态编译加打包依赖可能是更好的选择。6.2 和动态编译的对比维度静态编译动态编译部署复杂度低单文件高需打包依赖程序体积大20M起小几百K起更新成本高需重编Qt低替换so即可内存占用略高无共享低多进程共享编译时间长2小时起短30分钟适用场景嵌入式量产开发调试、频繁更新6.3 混合方案静态Qt 动态业务库实际项目中我经常用混合方案Qt静态编译业务逻辑做成动态库。这样Qt的部署问题解决了业务逻辑还能单独更新。具体做法是Qt用-static编译业务库用-shared编译。主程序静态链接Qt动态加载业务库。这样主程序体积大但稳定业务库体积小可更新。# 业务库编译 aarch64-linux-gnu-g -shared -fPIC business.cpp -o libbusiness.so # 主程序链接静态Qt动态加载业务库 /opt/qt5.14.2-host/bin/qmake main.pro make主程序的.pro里用LIBS -L. -lbusiness运行时用LD_LIBRARY_PATH指定业务库路径。6.4 后续扩展方向这套静态Qt编译环境搭好后可以复用到其他aarch64项目。比如把boost库也交叉编译成静态库和Qt一起链接。boost的交叉编译比Qt简单用b2工具指定toolsetgcc和address-model64就行。另一个方向是裁剪Qt模块。如果项目只用Widgets不用QML可以在configure时-skip qtdeclarative能省掉一半编译时间。如果不用网络-skip qtnetwork也能省不少。最后分享一个我踩过的坑静态Qt的qmake生成的Makefile里链接顺序很重要。Qt的静态库之间有依赖关系libQt5Widgets.a依赖libQt5Gui.alibQt5Gui.a依赖libQt5Core.a。如果链接顺序错了会报undefined reference。qmake通常能处理对但如果手动改Makefile一定要保证依赖库在后面。提示静态Qt编译完成后建议把整个/opt/qt5.14.2-aarch64-static目录打包备份。下次换机器或者重装系统直接解压就能用不用重新编译。这个目录大概2G到3G压缩后500M左右。