Qt 5.14.2 aarch64静态交叉编译从零搭建实践手册

发布时间:2026/9/14 21:25:17
Qt 5.14.2 aarch64静态交叉编译从零搭建实践手册 1. 这个组合有什么讲究为什么偏偏是5.14.2和静态嵌入式Linux圈子里的老人应该都有同感给目标板交叉编译一套Qt表面上是跑一条configure加make实际上是在跟版本、架构、依赖链玩排列组合。我最早在ARM32上折腾Qt 5.9后来换到aarch64又追过5.15、6.2绕了一大圈最后却把工程基线锁在了Qt 5.14.2上而且是静态编译。不是怀旧是被现实教育出来的。先说说版本。Qt 5.14.2属于Qt 5系列的LTS分支官方对它的维护周期拉得很长商业支持到2023年之后社区补丁也持续了相当久。对嵌入式产品来说没人追着你升版本就是最大的幸福感。6.x虽然新但License和模块划分的变动让很多老工程要重做集成qml模块的底层渲染架构也换了旧代码迁移成本不是一般的高。更何况aarch64的交叉编译链对5.14.2的支持早就被无数人验证过踩坑资料一搜一大把。对做产品的人来说稳定可复现比版本号最新重要得多。再说架构。aarch64如今几乎成了新硬件平台的默认选项瑞芯微RK3568、RK3588全志T507树莓派CM4飞腾、鲲鹏服务器清一色的AArch64。开发机上x86_64编译目标板aarch64运行交叉编译就是绕不开的基本功。而静态编译又是嵌入式部署里的硬需求为什么要静态我列个对比你感受一下对比维度动态编译静态编译板端运行环境需要拷贝所有Qt运行库到rootfs单个可执行文件直接运行依赖管理库版本不一致直接崩无外部依赖冲突启动性能动态装载有额外开销启动略快内存占用略高调试便利性可用gdb attach动态库报错可追溯需完整符号表文件体积大部署交付环境差异容易翻车拷贝即用适合产线刷机我之前在某个基于Linux的仪表项目上体验过动态编译的痛研发好的程序移植到产线设备上前一秒还跑得好好的换一批硬件批次之后QtQuick渲染直接花屏查了半天是rootfs里某个so版本被系统更新覆盖了。从那以后凡是交到现场的嵌入式程序我默认上静态编译宁可编译机多吃半小时也不愿去追一个现场问题。这套手册我就基于Qt 5.14.2 aarch64 静态编译三个关键点把从零搭建到产出可运行程序的完整过程掰开揉碎讲完不绕弯子。2. 折腾开始前的环境盘整主机、工具链和sysroot标题里写着从零搭建那咱们就真的从零开始。我先把我这次搭建用的环境完整交代清楚你照着做至少不会在第一步就卡住。2.1 主机环境与注意事项我用了Ubuntu 20.04 LTS x86_64的干净系统来做编译主机。说实话Ubuntu 18.04和22.04我也试过——18.04的gcc版本偏老编译新内核头文件时容易语法报错22.04的gcc 11在编译旧代码时告警多得眼花。20.04的gcc 9算是兼容性比较好的平衡点。如果你手里已经是其他版本也没关系后面我会提到用容器或Sysroot规避差异的办法。主机上要装的基础依赖老生常态但必须列全sudo apt update sudo apt install -y build-essential flex bison gperf libicu-dev \ libxslt-dev libxkbcommon-dev libgl1-mesa-dev libegl1-mesa-dev \ libfontconfig1-dev libfreetype6-dev libssl-dev python3 \ ninja-build cmake g-aarch64-linux-gnu注意最后这个g-aarch64-linux-gnu——Ubuntu对自己的交叉编译链做了Debian系patch直接用它是省事的但后面要小心系统的交叉编译器如果带了额外的安全加固选项有概率影响Qt某些模块的编译。我们先以这个工具链为基准后面如果遇到汇编报错再换Linaro或其他版本。2.2 交叉编译工具链的取舍我为什么不用完整自编译很多教程第一步就是让你下载crosstool-ng自己搓一套编译器说实话这有点仪式感过剩了。对于Qt这种应用层框架的交叉编译你需要的不是一个从零编译的GCC而是一套和目标板rootfs匹配的Sysroot。Sysroot这个概念打个比方交叉编译就像在图纸上给另一座城市装修房子你没法去现场量尺寸就靠一套户型图集来做事。Sysroot就是这套图纸里面存着目标板的头文件、标准库、各种底层依赖库的.so文件让编译器的链接器知道目标板子上到底有什么。我推荐两条稳定的工具链途径途径一Ubuntu自带交叉工具链 手动补齐Sysroot最简单适合快速验证。安装g-aarch64-linux-gnu后工具链自带一份基础的aarch64 Sysroot路径在/usr/aarch64-linux-gnu里面有libc、libstdc等基础库但缺很多图形和系统相关依赖需要手动从目标板的rootfs里补齐比如libfontconfig、libdbus、libxkbcommon等等。如果你直接用它编Qt大概率会在configure环节报missing dependencies。途径二使用配套的交叉编译SDK比如用Linaro提供的aarch64-linux-gnu工具链解压出来的文件夹里自带完整的Sysroot省去手动补齐的麻烦。下载地址这里就不贴了搜索引擎关键词Linaro aarch64-linux-gnu toolchain download就能找到官方release包。我实测下来Linaro 7.5版本的编译器编译Qt 5.14.2非常顺手几乎没有坑。我这次手册以途径一为例展开因为Ubuntu包管理器安装工具链最方便同时我会在2.3节把Sysroot补齐的操作讲清楚这条路线一旦打通你以后面对任何目标板都心里有数。2.3 手动补齐Sysroot依赖不管你是用哪种工具链核心思路一致把目标板运行时要调用的库的头文件、.so链接文件都放到编译器能找到的Sysroot目录里。这里有一个关键操作别把目标板的.so直接整个拷进去-dev包里的.so通常是指向实际版本号文件的软链而目标板为了省空间往往删掉了这些软链。手动补齐流程大概是这样的在目标板上执行dpkg -l | grep lib列出板子装过的库清单挨个去对应源里下载-dev版本统一解包到一个目录。用rsync把解包出来的usr/include和usr/lib/aarch64-linux-gnu合并到交叉工具链的Sysroot里# 将补齐的devel包拷贝进sysroot sudo rsync -av /path/to/collected-rootfs/usr/ /usr/aarch64-linux-gnu/usr/注意符号链接在Sysroot里检查一下libstdc.so是否存在且指向了正确的.so.6ls -l /usr/aarch64-linux-gnu/usr/lib/aarch64-linux-gnu/libstdc.so如果缺失ln -sf libstdc.so.6 libstdc.so补上。这一步做得好不好直接决定后面configure能不能过。很多人的Qt交叉编译死在libGLESv2.so not found上多半就是Sysroot里的链接文件不完整。3. configure这一步值半篇手册逐项参数说明Qt的configure脚本是整个编译流程里最容易被低估的一环。它的成败不取决于你敲了多少参数而取决于你理解不理解每个参数在干什么。交叉编译配置错了后面make的时候报错会非常诡异指向的完全不是真正的问题。所以这一节我打算把核心配置项一个个拆开讲清楚。3.1 静态编译的核心configure片段先给一个能直接在Ubuntu 20.04 aarch64-gnu环境里复现的完整配置命令cd /path/to/qt-everywhere-src-5.14.2 ./configure \ -prefix /opt/Qt5.14.2-aarch64-static \ -opensource -confirm-license \ -xplatform linux-aarch64-gnu \ -release \ -static \ -no-feature-glib \ -no-feature-cups \ -no-feature-dbus \ -no-feature-openssl \ -nomake examples -nomake tests -nomake tools \ -skip qtwebengine -skip qtwayland -skip qtscript \ -qt-xcb \ -qt-pcre -qt-zlib -qt-libpng -qt-libjpeg -qt-freetype \ -no-feature-assistant -no-feature-designer \ -fontconfig \ -no-eglfs -no-linuxfb -no-kms -no-opengl \ -I/usr/aarch64-linux-gnu/usr/include \ -L/usr/aarch64-linux-gnu/usr/lib/aarch64-linux-gnu3.2 几个坑点逐一分析先看-xplatform linux-aarch64-gnu。这里我踩过一个坑很多人误以为Qt每个架构都有一套独立的platform文件其实Qt的qmake/mkspecs目录下只给了少量参考specs。linux-aarch64-gnu在5.14.2里正好有它内部其实是引用了linux-generic-g再叠加大端小端、位数等参数所以不要自作主张去创建自定义specs除非你确实遇到了非改不可的情况比如指定musl libc。然后是-static。这一个参数会让Qt内部所有模块都以静态库的方式编译。这里有个隐藏逻辑-static打开后默认的-qt-xcb变成了硬依赖因为Qt不能再用动态方式加载xcb的插件。所以上面命令里我专门加了-qt-xcb并且补了-qt-pcre -qt-zlib -qt-libpng -qt-libjpeg -qt-freetype全部用Qt源码树里自带的三方库做静态链接——千万不要省略这组-qt-*参数否则configure会在检测系统版本库时因为Sysroot里的库版本不匹配而报错或者编译出行为诡异的程序。关于模块裁剪-skip qtwebengine -skip qtwayland -skip qtscript这三个我建议必加。WebEngine是出了名的重本身依赖一堆系统库而且静态编译时它对GPU和沙箱的要求会直接导致链接失败普通人没必要碰。Wayland在纯X11环境下完全用不上剪掉能省下十几分钟的编译时间。Script模块我遇到过一次编译错误后来直接跳过省心。接着看-no-eglfs -no-linuxfb -no-kms -no-opengl这几个是很多人纠结的地方。如果你的目标板子上没有GPU、没有OpenGL支持那就老老实实全关。如果板子上有Mali核心或者Vivante那才需要针对性打开eglfs和opengl。我这次手册针对的是那些只跑业务逻辑、界面上不需要3D加速的设备场景。静态编译加OpenGL是另一个深坑这里先绕开它后面有空我会单独写一篇OpenGL ES的交叉编译笔记。-no-feature-glib -no-feature-cups -no-feature-dbus分别关掉glib主循环集成、打印机支持、D-Bus总线。关D-Bus这条要说明一下不是所有Qt程序都需要D-Bus只有用systemd或者桌面组件深度集成才需要嵌入式板卡上多数是纯业务程序关掉后编译和运行都更稳。最后单说-fontconfig。我保留了fontconfig因为它对文本渲染太重要了特别是中文环境。日常项目里至少要用到中文字体如果关掉fontconfig你就只能依靠Qt自己的freetypeDirectFontDatabase字体匹配和回退规则会痛苦很多。保留fontconfig的同时要确保Sysroot里有libfontconfig1-dev。上面的configure命令里我把-I和-L都指向Sysroot路径就是为了让configure检测fontconfig时能找到头文件。3.3 configure的输出检查清单configure跑完后不要急着往下走先检查打印日志。我总结了几个值得关注的标记Qt is now configured for building确认出现这一行说明基本通过。检查生成的config.summary主要看以下几行Build .................. staticPlatform ................ linux-aarch64-gnuXcb .................... yesFontConfig ............. yes检查有没有出现WARNING: ... is not available之类的警告尤其xkbcommon和xcb-xlib。如果这两个出现no最后的程序可能连界面窗口都弹不出来。出现WARNING级别的问题倒不用慌日志里会告诉你缺的是运行时依赖还是编译依赖照着补齐Sysroot再重新configure即可。4. 编译期间的坎实际报错与完整定位链路configure过了make的时候才算真正进入从零搭建的战场。我把自己在这条路上真实踩过的三个典型报错连同完整排查思路写出来这段内容是网上教程最缺乏的部分。每个报错我不会直接给答案先把排查链路摆出来你再对照自己的情况判断。4.1 报错一/usr/lib/gcc-cross/aarch64-linux-gnu/7/../../../../aarch64-linux-gnu/bin/ld: cannot find -lGL这个报错几乎是交叉编译Qt的打卡题。字面意思是链接器在Sysroot里找不到libGL库。但要注意这个GL不是OpenGL的GL很多时候是Qt的xcb插件代码默认要链接OpenGL库即使你关闭了OpenGL功能某些共享代码块还是写死了GL头文件和库。排查链路是这样走的第一步确认Sysroot里到底有没有libGL相关的库find /usr/aarch64-linux-gnu -name *libGL*我实测的时候发现Ubuntu的交叉Sysroot里确实没有libGL.so因为桌面主机的libGL是Mesa的交叉Sysroot没有对应包。这就是根源。解决的办法有两种一个是干脆把Qt源码里xcb的OpenGL挂钩去掉打开qtbase/src/plugins/platforms/xcb/xcb.pro搜索GL相关行把xcb-glx相关的SOURCES注释掉。二是直接造一个空的libGL软链sudo ln -s /usr/aarch64-linux-gnu/usr/lib/aarch64-linux-gnu/libGL.so /usr/aarch64-linux-gnu/usr/lib/aarch64-linux-gnu/libGL.so.1第一个方案更干净因为应用本身用不到GL但是折腾源码不够优雅第二个方案在Sysroot里放一个安慰剂10秒解决问题非常实用。我实际用了第二个方案后续没有再出现GL报错。4.2 报错二error: unknown type name char16_t或者u8_string has not been declared这个报错出现的位置通常在Qt的src/corelib/text/qstring.h附近大概长这样qstring.h:113: error: unknown type name char16_t刚看到时我愣住了因为char16_t是C11标准里就有的东西。仔细一想问题在于交叉编译链的默认语言标准。Qt 5.14.2的某些头文件没有显式用std::u16string直接裸用了char16_t如果编译命令里没有带上-stdc11或者更高标准古老的GCC版本就会报这个错。排查时我先验证了一下交叉编译器的标准支持aarch64-linux-gnu-g -stdc11 test_char16.cpp -o /dev/null结果通过。说明编译器没问题是configure阶段没把-std传进去。然后我去看了Qt生成的qmake.conf发现QMAKE_CXXFLAGS里没有-std。我直接在环境变量里给编译选项加料export CXXFLAGS-stdc11 export CFLAGS-stdc11加完重新configure、make这个报错消失。事后复盘很可能是Ubuntu自带的g-aarch64-linux-gnu patch版本把默认标准改了导致Qt的检测逻辑误判。如果你用的是Linaro工具链大概率不会碰到这条。4.3 报错三Qt虚拟键盘模块编译时Assimp相关错误严格说这不是必须装的模块但如果你在configure里用了-skip没完全跳干净某些子模块还是会带出隐性依赖。我遇到的是qtvirtualkeyboard在静态编译下尝试链接assimp报了一堆类型推导错误。这个问题的完整排查链路是先去qtvirtualkeyboard/src/virtualkeyboard/virtualkeyboard.pro里面看样式发现它contains(QT_CONFIG, opengl)的地方才启用assimp。因为我在configure里已经-no-opengl理论上不该走到这里。后来检查了一下我命令行里的-skip发现只跳了webengine、wayland、script确实没跳virtualkeyboard。根本原因是configure检测opengl虽然关了但是Qt的主题模块里virtualkeyboard的pro文件有bug在无opengl时检测条件没有正确短路。解决方式是加一条环境变量export QT_BUILD_PARTS或者更直接把这个模块跳过去把-skip qtvirtualkeyboard加进configure参数列表重新跑一次configure。把这三条报错排完make基本就能一路半通畅了。我自己完整make的时间大约需要40到60分钟取决于机器核心数。建议用make -j$(nproc)但如果你内存小于16G建议-j4别把编译机跑死。4.4 编译产物的结构解读编译结束后在/opt/Qt5.14.2-aarch64-static下会看到一个典型的Qt目录结构。特别提醒一下静态编译模式下lib/目录下的.a文件才是主角比如libQt5Core.a、libQt5Widgets.a、libQt5Gui.a它们的大小加起来可能超过800MB。bin目录下有一些工具如moc、rcc、uic这些是主机架构的不是目标板的交叉编译应用时需要显式把/opt/Qt5.14.2-aarch64-static/bin加入PATH。另外qmake文件路径在/opt/Qt5.14.2-aarch64-static/bin/qmake这个文件本身是主机可执行的用来生成目标板的Makefile别把它拷到板子上这也是一个天然陷阱我见过新人把qmake拷到板子上运行的。5. 产物的部署验证ldd、qt.conf和板端运行静态编译最终目标就一句话产出一个file显示为ELF 64-bit LSB executable, ARM aarch64的可执行文件丢到板子上就能跑。但拿到可执行文件不代表结束了部署验证的过程里依然有暗坑。5.1 用file和ldd做第一轮体检编译完一个简单的Qt Widgets应用后先在本机做体检file ./hello_world # 应该输出 ELF 64-bit LSB executable, ARM aarch64 aarch64-linux-gnu-readelf -d ./hello_world | grep NEEDEDreadelf打印出来的NEEDED列表里如果干干净净只剩libc.so.6、libm.so.6、libstdc.so.6这类系统基础库说明静态链接成功。如果还看到libQt5Core.so.5之类的影子那说明链接用的qmake配置没生效回头检查是不是PATH里的qmake还是主机版本。这一步经常被忽略但它能提前拦下一大批部署到板子上崩了的问题。在Linux终端上静态链接Qt应用其实还有一个隐藏风险部分系统库可能被gcc隐式链接成动态版本。排查时我用readelf认真确认了NEEDED列表结果发现竟然还有一个libdl.so.2这通常是正常的因为C运行时库的内部实现依赖dlopen即使静态链接也会隐式带上。5.2 qt.conf是静态应用的救星很多人在板子上跑Qt程序遇到的经典报错是qt.qpa.plugin: Could not find the Qt platform plugin xcb in 动态编译时换个路径就得改环境变量但静态编译更尴尬——如果你configure时指定了-prefix /opt/Qt5.14.2-aarch64-static那么qmake会在编译期把这个绝对路径编译到Qt的QLibraryInfo内部机制里运行时它仍会去/opt/Qt5.14.2-aarch64-static/plugins找平台插件。可你的板子上根本不存在这个目录。解决方案就是在可执行文件同级目录下放一个qt.conf文件[Paths] Prefix . Libraries lib Plugins plugins Imports imports Qml2Imports qml这个qt.conf的作用是告诉Qt运行时所有资源都从当前目录开始搜索。静态编译模式下其实所有Qt库都已经链接进可执行文件了plugin目录也基本用不到除非你用了需要编译进二进制的插件机制但写上它能让Qt内部那些基于QLibraryInfo的路径判断全部短路省掉90%的运行时路径问题。5.3 板端验证跑起来只是第一步把编译好的二进制和qt.conf一并拷到板子运行前先用chmod x保证权限然后执行./hello_world -platform xcb如果板子是纯headless环境无屏幕、无X11这里会报连接失败。我之前做的就是带屏设备所以没问题。跑完窗口弹出来之后再执行一个简单的自检while true; do ./hello_world -platform xcb sleep 5; kill $!; done连续启停20次观察内存有没有泄漏、有没有段错误。静态链接的应用虽然在启动速度上有优势但若代码里使用了Qt的全局静态对象在exit时偶尔会遇到析构顺序问题提前这样压力测一下能筛掉大部分部署隐患。6. 再往深走一步模块裁剪与体积优化静态编译最大的槽点就是体积爆炸动不动一个hello world都要十几MB带着QtWidgets的完整业务程序随随便便50MB起步。如果你的板子flash只有128MB就要考虑做体积优化了。这块内容在基础手册里常常被放在最后但我个人认为它反而是产品化之前最关键的一步。6.1 用feature机制去掉用不到的模块Qt 5.14.2的configure脚本支持大量-no-feature-*开关可以精准禁用Qt特性。有些特性在嵌入式场景里毫无用处禁掉可以让代码规模和运行时内存同时下降。我在实际项目中已经验证过的安全裁剪项包括-no-feature-texthtmlparser \ -no-feature-textodfwriter \ -no-feature-printdialog \ -no-feature-printpreviewwidget \ -no-feature-imageformat_bmp \ -no-feature-imageformat_ppm \ -no-feature-imageformat_xbm \ -no-feature-imageformat_xpm \ -no-feature-dns-lookup \ -no-feature-ftp \ -no-feature-http每裁剪一个特性静态链接时那个模块的代码就从二进制里消失体积能明显下降几MB。但要注意这些开关必须以Qt官方特性名称为准需要先跑./configure -list-features查看完整列表再逐个确认不要凭感觉瞎猜。6.2 编译器和链接器的优化参数除了Qt层面的裁剪GCC侧的优化也不能放过。我在qmake工程文件里加入了这几个flagQMAKE_CXXFLAGS_RELEASE -Os -fno-keep-inline-dllexport -fvisibilityhidden -fvisibility-inlines-hidden QMAKE_LFLAGS_RELEASE -s-Os针对体积优化不是-O2-fvisibilityhidden把默认导出符号全部隐藏链出来的二进制里只保留必要符号-s更是直接strip掉符号表。这几个参数加完我的一个短线通讯小程序体积从25MB缩小到13MB效果立竿见影。不过别把链接器剥得太狠-s会把调试符号全删掉板子上如果崩溃了addr2line都解析不出来。真实项目的做法是保留一份带符号的原始二进制部署时用strip过的版本。6.3 字体和资源的后期瘦身静态链接之后字体的体积往往超过二进制本身。Qt的fontconfig会搜索系统字体目录如果你的板子上没有对应字体程序界面的中文全部变成豆腐块。解决方案是把需要的字体子集化我用过pyftsubset裁字体保留常用3500个汉字和标点一个原本18MB的思源黑体裁完之后只有2.3MB画质肉眼看不出区别。具体命令pyftsubset SourceHanSansCN-Regular.otf \ --text-filechinese_common.txt \ --output-fileSourceHanSansCN-Subset.otf \ --layout-features* \ --glyph-names \ --symbol-cmap \ --legacy-cmap然后在程序里显式指定字体文件路径或者放到板子的/usr/share/fonts下让fontconfig自动识别。7. 换个思路把这套流程沉淀成可复用的编译脚本最后一节我想聊点手册之外的东西。整套流程跑通之后如果每次重编都手动敲configure那2000个字的命令迟早会出错。我在项目里把它沉淀成了一个bash脚本如今从零到出二进制只需要一个参数指定源码路径即可。脚本主体逻辑很直白#!/bin/bash TARGET_ROOT/opt/Qt5.14.2-aarch64-static SYSROOT/usr/aarch64-linux-gnu/usr QT_SRC/path/to/qt-everywhere-src-5.14.2 export PATH/opt/Qt5.14.2-aarch64-static/bin:$PATH export CROSS_COMPILEaarch64-linux-gnu- export CXXFLAGS-stdc11 export CFLAGS-stdc11 cd $QT_SRC ./configure \ -prefix $TARGET_ROOT \ ...一系列参数... make -j$(nproc) make install这个脚本我放在公司的内部代码仓库里持续维护每次换一台新编译机跑一遍脚本就能复现完全一致的Qt环境。对于做嵌入式产品的人来说环境可复现比技术选型时髦重要得多因为产线人员不需要理解configure的每个参数他们要的只是一个可靠的流水线。我个人在实际操作中还养成了一个习惯每次configure之前都会先跑一次git diff确认Qt源码有没有被本地patch污染过。有一次我发现同事为了修一个字体渲染问题直接改了qfontengine_ft.cpp源头文件后来升级Qt版本时把这个改动丢了字体问题神秘复发排查了很久才发现是源码被本地改过。Qt框架这种量级的代码库有任何本地patch都要归入版本管理留好记录不然从零搭建最后会变成从玄学搭建。再分享一个扩展方向qt-everywhere里其实还带qtquickcontrols2等模块如果你需要做富媒体界面也可以交叉编译QML相关的部分静态链接下QML的plugin机制是打包进QRC资源文件里的部署方式会更统一这个后续值得专门开一篇写。这篇手册到这里该记录的坑都记录了该给的建议也都给了。希望你能在第一次configure就顺利通过。