
1. 为什么我需要一份静态交叉编译的 Qt背景与决策先讲清楚我为什么要折腾这件事。年初接了一个边缘采集设备的项目目标平台是 RK3588 那一类 aarch64 架构的 ARM 板子系统是定制过的精简 Linux存储空间和运行环境都受限制。Qt 界面要跑在上面但目标板上不可能给你装编译器也不能像 x86 桌面一样随便 apt install 各种 so 库。更麻烦的是现场设备可能存在多台不同系统版本、不同目录结构的板子动态链接的部署方式太脆弱——今天缺这个 lib明天 dynamic linker 版本不对够你喝一壶。这时候静态编译的价值就体现出来了一个可执行文件包含全部 Qt 模块和依赖库拷到板子上就能跑不依赖目标板上的库文件部署和升级都变成覆盖一个文件这么简单。我最初调研的时候也对比过静态和动态两条路线这里把关键差异列出来供后来人参考对比项静态链接动态链接部署复杂度单文件拷贝需要同步 lib 树维护依赖关系目标板环境要求只需内核与 libc 兼容需要匹配的 so 库和符号版本可执行文件体积大约 20~80MB几个 MB编译时间长全量编一次 3~8 小时较短增量编译友好插件体系必须静态注册或手动实例化按需加载灵活升级维护整个文件覆盖按需替换某个库1.1 版本为什么锁定在 Qt 5.14.2很多刚入坑的朋友会问为什么非要用 5.14.2新的 5.15 LTS 或者 Qt 6 不香吗这里有个非常现实的原因从 Qt 5.13 开始官方在 configure 脚本里对-static参数做了限制虽然通过修改源码还能绕过但 5.14.2 是我试下来最省事、编译链路上文档也最完整的一个版本。而 Qt 6 全面转向 CMake模块拆分更细交叉编译的依赖管理系统变化很大对于只想要一个稳定 GUI 运行时的嵌入式场景来说5.14.2 社区成熟度高资料多踩坑也有人替你踩过了。另外 5.14.2 是最后一个对老式 qmake 项目和源码改一改就能编比较友好的版本如果你的项目里有大量基于.pro文件的历史代码迁移成本最低。后面我们所有配置和脚本都是围绕这个版本展开的建议新入坑的同学不要轻易换版本否则参数和目录结构对不上你会被活活折腾疯。2. 环境准备从交叉编译工具链到系统依赖2.1 宿主机系统选择与工具链安装我用的宿主机是 Ubuntu 20.0464 位 x86。这并不是随意选的——静态交叉编译出的二进制最终要在 aarch64 的板子上跑宿主机的 glibc 版本和目标板系统越接近越好。理论上讲如果你用 22.04 甚至更新的发行版新版本编译出来的二进制往往链接了更新版本的 glibc 符号拷到旧系统板子上很可能直接报version GLIBC_2.34 not found。我手上这个项目的板子 base 是 Ubuntu 18.04 精简系统glibc 是 2.27所以宿主机用 20.04glibc 2.31都要小心核验。最稳妥的验证方式是编译完后在板子上跑一下ldd --version对照这个后面说。工具链我选择了标准 ARM GNU 工具链也就是通常说的 aarch64-linux-gnu-gcc 系列。安装方式三条路# 方式一Ubuntu 自带交叉工具链最简单 sudo apt install gcc-aarch64-linux-gnu g-aarch64-linux-gnu crossbuild-essential-arm64 # 方式二ARM 官方 GNU-A 工具链更纯正版本新 # 从 ARM 官网下载 aarch64-none-linux-gnu 的 .tar.xz解压后加入 PATH 即可 # 方式三如果需要更贴近目标板 vendor 的编译环境用板上 SDK 自带的工具链我个人推荐方式二因为 Ubuntu 自带的工具链版本不一定和 Qt 5.14.2 的 mkspec 完全合拍偶尔会有老旧源码配新编译器导致的告警。ARM 官方工具链带一个完整的 sysroot但那个 sysroot 是通用的 glibc不是目标板定制系统的所以实际交叉编译时我更倾向于自己组装 sysroot下面会细说。2.2 sysroot 的来龙去脉没有它交叉编译就是空谈交叉编译的难点在于你在 x86 的机器上却要让链接器用 aarch64 的头文件和目标板系统的库文件。这个目标板的根文件系统镜像就是 sysroot。比较常见的做法有两种直接从板子上把/lib、/usr/lib、/usr/include等目录整个拷贝出来拼成一个 sysroot。用目标板厂商提供的 SDK rootfs里面已经包含了完整的开发头和库。我用的目标是定制 Linux 系统所以自己拼接了 sysroot把板上的/lib、/usr/lib、/usr/include全部拷贝到宿主机的一个目录比如/opt/aarch64-sysroot。注意这里有个重要细节不能把 x86 主机上的libgcc_s.so、libstdc.so等混进去必须全部来自目标板系统的 aarch64 版本。我之前贪图省事把 x86 的某些头文件也复制进去结果编译出的程序在板子上段错误排查了整整两天所以血泪教训写在这里。2.3 编译 Qt 本身需要的宿主机依赖在正式编译 Qt 之前宿主机还需要装几个基础依赖否则 configure 阶段会报东少西少sudo apt install build-essential python2 perl vim git \ libglib2.0-dev libfontconfig1-dev libfreetype6-dev \ libxcb1-dev libxkbcommon-dev libxkbcommon-x11-dev \ ninja-build gperf bison flex这里面python2比较特殊Qt 5.14 的 configure 脚本还需要 python2 环境在 Ubuntu 20.04 上需要从 old-releases 源装或者用符号链接把python指到 python3 也能骗过大部分检查。我当时是直接下载了 python2 的 static 版本放到 PATH 里避免污染系统环境。另外这些依赖大多是给 Qt 的host端编译用的交叉编译的 target 端依赖是另一套下面专门说。3. Qt 5.14.2 源码准备与 configure 参数的背后逻辑3.1 源码下载与目录规划从 Qt 官方的离线包或者清华大学镜像站下载qt-everywhere-src-5.14.2.tar.xz大约 1.5GB 左右。解压到工作目录后我习惯建立一个独立的构建目录来存放中间产物不要把编译产物和源码混在一起这样后期重新配置、清理都方便mkdir -p ~/qt-build cd ~/qt-build tar xf qt-everywhere-src-5.14.2.tar.xz mkdir build-qt5.14.2-aarch64目录规划这件事是我吃了好几次亏才养成的习惯。Qt 这么大的工程一旦 configure 参数不对重新配置时如果不清理干净旧产物经常出现改了参数但还走旧逻辑的诡异问题。所以我在每次新配置前都是直接rm -rf build-qt5.14.2-aarch64 mkdir build-qt5.14.2-aarch64重建一个干净目录一劳永逸。3.2 让 configure 接受 -static 参数的修改Qt 5.13 之后官方 configure 脚本里有一段检查如果在非 Windows 平台上检测到-static会直接提示The static build is not supported on this platform并退出。这个检查点在qtbase/configure和qtbase/mkspecs/底下都有逻辑最省事的改法是编辑qtbase/configure文件搜索关键字static build把那一段抛出错误的分支注释掉即可或者在命令行加一个-secure-transport之类的已知参数顺序来骗过检查。我用的是前者注释掉检查分支然后强制加-static。这里要提醒的是改完之后第一次运行 configure如果还在报同样的错误多半是你没清理干净旧配置缓存或者修改的是源码根目录而不是qtbase目录下的 configure。Qt 的 configure 入口在源码根目录但真正执行的是qtbase/configure脚本两处都要确保版本一致。3.3 configure 参数逐个拆解以下是我最终跑通的完整 configure 命令每个参数单独说清楚为什么这样设置./configure \ -prefix /usr/local/Qt-5.14.2-aarch64-static \ -xplatform linux-aarch64-gnu-g \ -device linux-rk3399x-g \ -sysroot /opt/aarch64-sysroot \ -opensource -confirm-license \ -release \ -static \ -ltcg -optimize-size \ -no-opengl \ -skip qtwebengine -skip qt3d -skip qtdoc \ -skip qtgamepad -skip qtscript -skip qtquick3d \ -nomake examples -nomake tests \ -no-compile-examples \ -no-pch \ -qt-zlib -qt-libpng -qt-libjpeg \ -qt-sqlite -qt-pcre -qt-xcb \ -no-icu -no-dbus -no-glib -no-cups \ -qt-freetype -no-fontconfig \ -openssl-linked -I /opt/aarch64-sysroot/usr/include -L /opt/aarch64-sysroot/usr/lib \ -v逐个解释几个关键参数-prefix /usr/local/Qt-5.14.2-aarch64-static这是编译完成后的安装路径。静态编译的 Qt 不像动态版那样需要拷一堆库文件到板上prefix 只是给宿主机上组织安装文件用的。即使如此我还是强烈建议用一个独立的、带 arch 标识的路径方便日后多个版本共存。-xplatform linux-aarch64-gnu-g指定目标平台 mkspec。Qt 自带的 mkspec 里已经有这个目录指向aarch64-linux-gnu-g。如果你的工具链前缀不同比如用 ARM 官方工具链的aarch64-none-linux-gnu-g就要自己新建一个 mkspec 或者在 qmake.conf 里改QMAKE_CROSS_PREFIX。-device linux-rk3399x-g这一步比较关键Qt 5.12 之后交叉编译已经全面迁移到 device 体系。如果你只指定-xplatform而不指定-device可能会出现 qmake 生成的 mkspec 不完整的问题。linux-rk3399x-g是一个我自己在qtbase/mkspecs/devices/下复制修改的设备描述文件核心是往 qmake.conf 里写入 sysroot 路径和交叉编译器前缀。-sysroot /opt/aarch64-sysroot告诉 qmake编译时使用的头文件和库文件都从这个目录找它会自动加上-I sysroot/usr/include和-L sysroot/usr/lib。-no-opengl目标板是纯 2D 应用没打算跑 GPU 相关的 QML 渲染。而且 openssl es2 的移植又是一整套麻烦事第一次搭环境建议先把 OpenGL 关掉后续需要再打开。-skip qtwebengine等静态编译时尺寸特别敏感Qt WebEngine 编出来体积巨大而且 Chromium 的交叉编译是个无底洞绝对要跳过。-skip qt3d、-skip qtdoc、-skip qtquick3d同理按需裁剪。-no-compile-examples静态编译全量 Qt 的时间已经很长了再编译 examples 纯属浪费。-no-icu -no-dbus -no-glib -no-cups嵌入式精简系统上一般没有这些系统服务禁用后能减少不少编译负担。需要注意如果你要跑较复杂的 QML 文本处理ICU 还是建议保留否则部分字符处理功能会退化。我的场景只需要 QWidget 基础控件所以全部关掉。-openssl-linked这里是个坑。Qt 的 configure 默认检测到 sysroot 里有 openssl 头文件就会启用 OpenSSL 支持但静态编译时默认是动态链接方式容易在最后链接阶段报libssl.so相关的 undefined reference。显式加-openssl-linked后Qt 会把自己的 SSL 代码静态链入 OpenSSL 库前提是 sysroot 里必须有 openssl 的.a静态库和头文件。我用的 openssl 是自己源码编译的 aarch64 静态版后面专门讲。3.4 configure 阶段的常见异常configure 跑的时候输出末尾有一段Qt is now configured for building的总结建议一行行看特别是每个-前缀的配置项后面有没有no或者not found。最常见的问题是python2: not found—— 解决方式前面说了装 python2 或者符号链接把 python 指向 python3。xkbcommon: no—— 如果你要用 xcb 平台插件xkbcommon 是必须的。sysroot 里没有的话需要先交叉编译 xkbcommon。GLES2: no或者OpenGL: no—— 如果 configure 检测不到 OpenGL ES但它没有主动报错一般会退化为软件渲染。没问题。-no-fontconfig之后中文显示会依赖 freetype 和字体文件。静态编译的 Qt 默认在/usr/local/Qt-5.14.2-aarch64-static/lib/fonts找字体部署时要把字体文件也拷过去或者运行时通过QT_QPA_FONTDIR环境变量指定这个后面验证阶段还会再提。4. 静态编译的前置依赖库交叉编译一个都不能少静态编译的静态意味着所有依赖库最终都会以.a的形式链入你的可执行文件。所以 sysroot 里不仅仅是头文件还要有每个依赖库的静态版本。Qt 自带了一些基础的第三方库源码如 zlib、libpng、libjpeg通过-qt-zlib -qt-libpng -qt-libjpeg直接使用 Qt 内置版本即可这一类不需要自己额外编译。真正麻烦的是 Qt 不自带但 xcb 平台插件强依赖的那一串。4.1 xcb 依赖链顺序很重要xcb 平台的静态链接依赖链大致是xcb-proto → libxcb → xcb-xkb → xkbcommon → xkbcommon-x11 → 相关 X11 库如果 sysroot 里只有动态库Qt 在 configure 阶段也能通过检测但最终静态链接时会报一堆undefined reference to xcb_*之类的错误。所以正确做法是手动编译这些库的 aarch64 静态版按依赖顺序逐个交叉编译。以 xcb-proto 为例cd /opt/src wget https://xcb.freedesktop.org/dist/xcb-proto-1.14.tar.gz tar xf xcb-proto-1.14.tar.gz cd xcb-proto-1.14 ./configure --hostaarch64-linux-gnu --prefix/opt/aarch64-sysroot/usr make sudo make install注意--host一定传aarch64-linux-gnu--prefix指向 sysroot 而不是宿主机根目录这样安装的头文件会直接落到 sysroot 里Qt 编译时自动找得到。xkbcommon 编译时要依赖wayland、X11和xcb相关头文件如果只做 xcb 平台--with-xkb-config-root那个配置项也不用管直接./configure --hostaarch64-linux-gnu --prefix/opt/aarch64-sysroot/usr --without-wayland即可。xkbcommon 0.8.4 是我验证过能和 Qt 5.14.2 合拍的版本太新的版本偶尔会因为 xcb 协议版本差异导致 configure 报错。4.2 openssl 静态库的特殊处理OpenSSL 的静态交叉编译命令大致是cd /opt/src wget https://www.openssl.org/source/openssl-1.1.1v.tar.gz tar xf openssl-1.1.1v.tar.gz cd openssl-1.1.1v ./Configure linux-aarch64 \ --prefix/opt/aarch64-sysroot/usr \ --cross-compile-prefixaarch64-linux-gnu- \ no-shared enable-ec_nistp_64_gcc_128 make -j$(nproc) sudo make install这里的no-shared是关键它生成.a静态库而不是.so。enable-ec_nistp_64_gcc_128是给 64 位 ARM 平台的一个数学优化选项加不加都有加了性能好一点。有一点务必注意如果你 sysroot 里同时存在动态库和静态库OpenSSL 的编译结果libssl.a和libcrypto.a会装到 sysroot 的/usr/lib目录Qt 静态链接时优先选择.a。但如果之前通过apt之类的为宿主机装过别的架构的 OpenSSL 库混进 sysroot链接器会莫名其妙地选错库。所以 sysroot 里尽量只保留 aarch64 的内容。4.3 裁剪依赖宁可少一点不要多一件事在静态编译里每多一个特性就多一个依赖多一个依赖就多一组坑。我最终的裁剪思路是只保留 xcb 平台必须的库连同鼠标、键盘输入、窗口系统协议等基础功能其余一律关掉。实际上很多嵌入式的纯 QWidget 应用连 xcb 都可以不要使用-xcb -no-xcb-xlib配置后用offscreen平台跑。如果完全无显示需求甚至 configure 的时候直接用-qt-xcb -no-feature-sessionmanager -no-feature-xkbcommon这类参数进一步精简。但我这个项目最终要接 7 寸 HDMI 屏所以 xcb 是刚需整个依赖链必须编齐。下表是我在 sysroot 里最终保留的关键静态库清单供大家对照检查库名称编译命令关键项备注openssl 1.1.1vno-sharedlinux-aarch64提供 TLS/SSL 能力xcb-proto 1.14--hostaarch64-linux-gnu协议描述纯文本文件libxcb 1.14--hostaarch64-linux-gnu基础 xcb 库xkbcommon 0.8.4--hostaarch64-linux-gnu --without-wayland键盘处理libxkbcommon-x11 0.8.4同上桥接 X 与 xkbcommonzlib 1.2.11--hostaarch64-linux-gnu直接用 Qt 内置的也可5. 我踩过的坑编译失败到运行崩溃的完整排查链路5.1 陷阱一sysroot 里动态库残留导致链接器选错库第一次完成所有依赖编译后我跑make -j$(nproc)编译到 Qt XCB plugin 的时候链接器报出大量的undefined reference to Qt5::XcbQpa。一开始我以为 xcb 依赖链没编对反复重编了几遍还是不行。后来用aarch64-linux-gnu-objdump -p libqxcb.a | grep NEEDED检查 sysroot 里的 xcb 库发现 libxcb 的静态库和动态库是混着的而 qmake 在链接时优先选择了.so但这个.so是编译系统里宿主机环境的残留链接到了一个 x86 的 xcb 库上符号当然对不上。解决方式很简单在编译 xcb 系列库时每次都先确认输出目录里只有.a文件或者对应.so不会干扰最简单的是把 sysroot 里所有.so临时移走只留.a编完 Qt 之后再恢复。这个方法很粗暴但确实有效。5.2 陷阱二-static 与 -no-pch 的组合导致编译内存不足Qt 5.14.2 的 QtWidgets 模块在打开 PCH 后某些源文件交叉编译时几乎每个编译单元都要加载大量头文件有时编译一个.cpp能吃掉 4GB 内存。如果你的宿主机构建机内存只有 8GBmake -j$(nproc)同时开 8 个编译任务瞬间 OOM表现为c: internal compiler error: Killed (program cc1plus)。解决方式是换-no-pch参数减少 PCH 缓存占用并且把并行度降下来。我最终采取的方案是分模块编译先把qtbase里除了 QtWidgets 之外的模块编完再单独编 QtWidgetsmake -j2保证不 OOM。这一步很影响体验但静态编译全工程时值得多等十几分钟避免一次 OOM 导致前功尽弃。5.3 陷阱三bison/flex 版本太新导致 qmake 生成失败在 Ubuntu 22.04 上bison 3.8 默认生成的代码需要 glibc 2.34 以上支持如果你在老系统上编 qmake会报bison: error: stray ‘’ in code之类的语法错误。我建议在 Ubuntu 20.04 上直接使用默认的 bison 3.5.1不要升级到 3.8或者在宿主机上固定安装旧版本 bison。当时我为了这个折腾了一下午最后用apt install bison3.5.1固定版本解决。5.4 陷阱四平台插件无法加载编译完成、交叉编译出一个简单的 QWidget 窗口程序后我在开发板上运行./MyApp -platform xcb结果程序直接退出终端打印could not load the Qt platform plugin xcb。这个问题的根源是静态编译时QXcbIntegrationPlugin没有以静态插件的形式被链入程序。Qt 的插件系统在静态编译下不会自动把所有插件都链进来必须在工程.pro文件里手动注册QTPLUGIN qxcb同时为了让插件静态可用还需要在源码里写一段插件实例化代码#include QtPlugin Q_IMPORT_PLUGIN(QXcbIntegrationPlugin)如果你用了多个插件比如platforms里的linuxfb、minimal、offscreen每个都要Q_IMPORT_PLUGIN。这个细节是最容易漏的因为在动态编译下你根本不需要关心插件是谁加载的。5.5 陷阱五failed to load platform plugin之后的中文乱码xcb 插件加载成功之后界面能弹出来了但中文全部显示成方框。静态编译时字体目录和系统字体路径无关Qt 默认会从编译时的 prefix 目录找lib/fonts。我们的 prefix 是/usr/local/Qt-5.14.2-aarch64-static但这个路径只存在于宿主机目标板上当然没有。解决方法是把适合的 TrueType 中文字体放进程序运行目录的fonts/子目录然后设置环境变量export QT_QPA_FONTDIR/app/fonts或者在代码里用QFontDatabase::addApplicationFont(/app/fonts/msyh.ttc)加载。对于嵌入式项目我的经验是优先打包四五个常用字体文件中文黑体/宋体各一英文 sans 一个直接拷到/app/fonts然后统一设置QT_QPA_FONTDIR这样不管目标板系统里装了什么字体都不受影响。6. 完整全流程手册从零到可运行 Demo6.1 阶段一准备宿主与 sysroot# 1. 安装宿主机依赖 sudo apt update sudo apt install build-essential gcc-aarch64-linux-gnu g-aarch64-linux-gnu \ python2 perl vim git libglib2.0-dev libfontconfig1-dev libfreetype6-dev \ libxcb1-dev libxkbcommon-dev libxkbcommon-x11-dev \ ninja-build gperf bison flex # 2. 制作 sysroot从目标板上拷贝 mkdir -p /opt/aarch64-sysroot scp -r usertarget:/lib /opt/aarch64-sysroot/ scp -r usertarget:/usr/lib /opt/aarch64-sysroot/usr/ scp -r usertarget:/usr/include /opt/aarch64-sysroot/usr/ # 注意确保这些目录里都是 aarch64 格式的二进制和头文件6.2 阶段二交叉编译 openssl命令在 4.2 节已经给出这里补充一点如果不需要 TLS 功能完全可以把-openssl-linked去掉换成-no-openssl能省掉 OpenSSL 静态库编译和链接的一堆麻烦。我这边因为业务里要上 HTTPS 访问云端接口所以留了。6.3 阶段三交叉编译 xcb 依赖链按 xcb-proto → libxcb → xkbcommon → xkbcommon-x11 的顺序逐一编译每个库都指定--hostaarch64-linux-gnu --prefix/opt/aarch64-sysroot/usr。这里特别提醒libxcb 的编译依赖 xcb-proto 生成的xcb-proto目录里的 python 脚本而它在生成xcbgen相关文件时会调用宿主机 python。如果你在 Ubuntu 20.04 上用 python3 运行不会有大问题但如果系统同时存在 python2 和 python3 的切换混乱会导致生成的头文件路径不对进而让后面的 xkbcommon 编译失败。建议在 shell 里先执行export PYTHON/usr/bin/python3统一版本。6.4 阶段四修改 Qt 源码并运行 configurecd ~/qt-build/qt-everywhere-src-5.14.2 # 注释 configure 里的 static build 检查 sed -i s/echo The static build is not supported.*/echo static build patched/ qtbase/configure mkdir -p ~/qt-build/build-qt5.14.2-aarch64 cd ~/qt-build/build-qt5.14.2-aarch64 ~/qt-build/qt-everywhere-src-5.14.2/configure \ -prefix /usr/local/Qt-5.14.2-aarch64-static \ -xplatform linux-aarch64-gnu-g \ -device linux-rk3399x-g \ -sysroot /opt/aarch64-sysroot \ -opensource -confirm-license \ -release -static -ltcg -optimize-size \ -no-opengl \ -skip qtwebengine -skip qt3d -skip qtdoc \ -skip qtgamepad -skip qtscript -skip qtquick3d \ -nomake examples -nomake tests -no-compile-examples \ -no-pch \ -qt-zlib -qt-libpng -qt-libjpeg -qt-sqlite -qt-pcre -qt-xcb \ -no-icu -no-dbus -no-glib -no-cups \ -qt-freetype -no-fontconfig \ -openssl-linked \ -v 21 | tee configure.log跑完 configure 之后强烈建议先检查configure.log的末尾有没有错误。如果它显示ERROR: ...后面接一大段依赖缺失说明按提示补齐再重试。有些模块缺失是 warning 不影响主流程比如-skip qtquick3d后它显示qtquick3d: skipped这不是错误。6.5 阶段五编译与安装make -j2 sudo make installmake -j2的时间取决于机器性能我当时的 16 核服务器全开大概 3.5 小时个人电脑上保守估计 5 小时以上要有心理准备。编译过程中如果某个模块单独编译失败比如qtbase编译过了而qtmultimedia失败可以进入对应模块目录单独make重试不用全量重来。编译并安装完成后检查一下安装目录里是否包含我们需要的关键文件ls /usr/local/Qt-5.14.2-aarch64-static/lib/ # 期望看到 libQt5Core.a libQt5Gui.a libQt5Widgets.a 等静态库 ls /usr/local/Qt-5.14.2-aarch64-static/plugins/platforms/ # 期望看到 libqxcb.a静态库存在是第一步真正的考验在链接应用的时候。6.6 阶段六编译一个 Demo 程序并部署验证新建一个简单的 QWidget 窗口项目目录结构如下MyApp/ ├── MyApp.pro ├── main.cppMyApp.pro内容QT core gui widgets TARGET MyApp TEMPLATE app CONFIG static QTPLUGIN qxcb DEFINES QT_STATIC SOURCES main.cppmain.cpp内容#include QApplication #include QLabel #include QtPlugin Q_IMPORT_PLUGIN(QXcbIntegrationPlugin) int main(int argc, char *argv[]) { QApplication app(argc, argv); QLabel label(QStringLiteral(Hello aarch64 Static Qt)); label.resize(400, 200); label.show(); return app.exec(); }设置 qmake 的交叉编译环境变量并编译export PATH/usr/bin:/opt/aarch64-sysroot/usr/bin:$PATH export QMAKE_LRELEASE/usr/local/Qt-5.14.2-aarch64-static/bin/lrelease export PKG_CONFIG_PATH/opt/aarch64-sysroot/usr/lib/pkgconfig /usr/local/Qt-5.14.2-aarch64-static/bin/qmake MyApp.pro make -j4成功后当前目录会生成MyApp可执行文件file MyApp # 期望输出 ELF 64-bit LSB executable, ARM aarch64 du -h MyApp # 大约 20~50MB 左右然后把它和字体文件一起拷贝到板子scp MyApp usertarget:/app/ scp msyh.ttc usertarget:/app/fonts/ ssh usertarget cd /app export QT_QPA_FONTDIR/app/fonts export QT_QPA_PLATFORMxcb ./MyApp如果屏幕上出现一个 400x200 的窗口并显示文字那恭喜你一条完整的 aarch64 Qt 静态编译链路就算是打通了。7. 静态交叉编译的性价比与经验总结看到这里可能有人会问费这么大劲搞静态编译值得吗我的回答是分场景。如果你的硬件平台常年不变、库依赖干净动态链接编译快、增量编译爽没必要折腾静态。但如果你像我一样要面向多种目标板、要在不同系统版本之间部署或者客户现场根本不允许你登录系统去装依赖那么单一静态文件带来的部署优势是压倒性的。从技术维护角度讲静态编译意味着你的程序与目标板系统完全解耦Qt 升级、依赖修复只需要重新编译一个二进制升级成本极低。缺点是编译一遍实在不轻松而且所有插件都得在设计阶段预注册灵活性受限。比如我后来想加一个webengine做远程看板静态编译基本没戏只能退回动态链接或者单独跑一个浏览器进程与 Qt 程序通过 http 通信。分享两个实践里非常实用的经验sysroot 的管理要像源码库一样 commit。每次编译完一个依赖库把/opt/aarch64-sysroot的变更记录一下最好用 git 管理。因为 Qt 静态编译的周期长你中途可能升级某个依赖结果发现整个 Qt 又要重编如果你忘了之前装过哪些东西最后链路会非常混乱。我就遇到过 xkbcommon 升级一个小版本后 Qt 编译不过还得回头查旧版本。静态编译的可执行文件在 strip 前后体积差距很大。Qt 的 debug 符号默认在 release 模式不会生成但如果你的项目开了额外的 tracing 或者 breakpad记得最后用aarch64-linux-gnu-strip MyApp去掉符号表几十 MB 往往能缩掉一半以上。但注意不要 strip 掉插件否则之前所有静态插件的努力又白费了。最后想说的是交叉编译这条路没有捷径但也没有想象中那么可怕。网上资料碎片化容易今天搜到一篇 qmake 配置、明天搜到一篇 xcb 依赖能一次跑通的人很少。我把这几个月的完整经历整理成这一篇希望能让后面做 aarch64 Qt 静态交叉编译的人少走点弯路。如果还有细节不清楚欢迎在评论区交流我看到会回复。