资讯动态

Qt 5.14.2 在 aarch64 上的静态交叉编译:从 configure 到部署

发布时间:2026/9/14 23:54:30 来源:尧图企业网站定制
1. 先算清楚这套方案解决什么问题又引入什么问题最近要给一块 aarch64 目标板做带界面的工具程序板子上的 rootfs 被裁得很狠连包管理器都不一定齐全想跑 Qt 5.14.2 又不想被一堆.so依赖追着跑最终只能选静态交叉编译这条路。简单说Qt5.14.2、aarch64、静态交叉编译这三个词放在一起就是把 Qt 的核心库、你用到的模块、平台插件、第三方依赖全部编成静态库最终在 x86_64 主机上生成一个可以在 arm64 上直接运行的可执行文件。这个思路适合做批量部署、离线交付、现场环境不可控的产品不适合追求快速迭代、依赖大量 GPU 能力、页面里还要跑 WebEngine 的场景。1.1 “静态”的收益只有部署时刻才体现动态链接在开发机上永远是最舒服的。Qt 装好之后qmake 自动把一堆.so路径写进二进制开发板上只要能做到同样的库路径程序就能跑。问题在于嵌入式产品的 rootfs 千奇百怪有的是 Buildroot 裁出来的有的是手工精简的 Ubuntu还有的是厂商 SDK 里不知道哪个版本的 glibc。动态部署时最常见的一幕是在开发板上ldd myapp满屏not found然后你开始一个个往 rootfs 里补库补完这个缺那个最终补出一个自己都不知道有多大、有多少互相冲突的库全家桶。静态链接把这些焦虑全部前置到编译阶段。编出来的 Qt 应用除了 glibc、libstdc、libm 这些系统级库之外基本不依赖其他目标机库文件。部署时就是一个文件拷过去chmod x跑起来。这个收益平时看不出来等你需要一次性部署到几十台设备、现场又没有网络时就非常值了。1.2 交叉编译的成本往往不在“交叉”本身很多人觉得交叉编译难在换一个编译器其实真到了 Qt 这种体量难的是三件事工具链和 sysroot 的匹配、第三方依赖的静态化、configure 阶段无数个功能检测项。aarch64 本身不算特别刁钻的架构行业内用得很广但 Qt 的 configure 检测项目非常多。你开着 X11 编译时它要检测 xcb你有 GPU 时它要检测 OpenGL你有 DBus 时它要检测 dbus-1这些依赖在交叉环境下的头文件、库、pkg-config 文件只要缺一个configure 就给你一个不太友好的报错。所以不要一上来就追求全套 Qt 静态。先剪功能再谈编译。后面所有 configure 参数的核心思路都是先让 Qt 少依赖外部世界。1.3 Qt 5.14.2 不算新但项目锁死时你就得接受Qt 官方对 Linux 桌面有离线安装包对 Windows、macOS 也有但没有人给你提供 aarch64 Linux 的静态 Qt 安装包。这很正常因为目标板环境差异太大官方不可能替你做交叉静态编译。所以只要你手里有 Qt 5.14.2 源码就必须自己搭。另外很多设备项目的第三方 SDK 是绑死 Qt 版本的比如供应商的摄像头库、算法库只针对 Qt 5.14.x 验证过。这个时候别想着顺手升级到 Qt 6老老实实把 5.14.2 编出静态版比什么都稳。这篇文章里的 configure 写法对 Qt 5.12、5.15 也有九成适用只是个别模块参数需要微调。2. 基础三件套主机环境、交叉工具链、arm64 sysroot正式开始之前必须把三个东西准备好编译主机、交叉工具链、目标 sysroot。很多人在 Qt 源码 configure 阶段才报错回过头检查其实是工具链版本不对或者 sysroot 里少了开发库。2.1 推荐的版本组合我先给出一套我实测比较稳的版本组合你可以照着用也可以根据自己目标板调整组件推荐版本/来源说明宿主系统Ubuntu 20.04 或 22.04 x86_6420.04 的 GCC 9 最稳22.04 的 GCC 11 也能用Qt 源码qt-everywhere-src-5.14.2.tar.xz官方 archive 下载约 500MB交叉工具链gcc-aarch64-linux-gnu / g-aarch64-linux-gnu发行版自带版本与目标 glibc 匹配即可sysrootUbuntu 20.04 arm64 minbase rootfsglibc 2.31兼容性较好QEMU 用户态qemu-user-static用于 configure 阶段执行目标板测试程序为什么要用 Ubuntu 20.04 的 arm64 rootfs 做 sysroot因为 Ubuntu 20.04 的 arm64 软件源里开发库齐全交叉编译第三方依赖时不需要自己从源码编一堆东西。如果你的目标板是 Yocto 或 Buildroot 系统的 rootfs也可以直接把那个 rootfs 作为 sysroot但前提是里面要有对应的.so软链接和头文件不能是纯运行时文件系统。2.2 安装工具链和 qemu 用户态直接使用发行版自带工具链省掉自己编译 GCC 的麻烦sudo apt update sudo apt install -y build-essential gcc-aarch64-linux-gnu g-aarch64-linux-gnu \ qemu-user-static binfmt-support debootstrap装完检查一下aarch64-linux-gnu-gcc --version uname -aqemu-user-static不是可有可无的东西。Qt 的 configure 在检测很多特性时会先编译出一个针对目标架构的小测试程序然后在主机上尝试运行。如果是纯交叉编译器它没法直接执行 aarch64 程序有了 qemu-user-static 和 binfmt-supportLinux 内核会自动用 qemu 去解释执行configure 才能继续往下走。2.3 制作 sysroot不要随便从网上下“打包好的根文件系统”很多人图省事到网上找一个 arm64 rootfs 下载结果 glibc 版本、软件包架构、符号版本全对不上后面编 Qt 时一堆莫名其妙的问题。我建议自己用 debootstrap 生成一份干净的最小 rootfssudo mkdir -p /opt/aarch64-rootfs sudo debootstrap --archarm64 --variantminbase focal \ /opt/aarch64-rootfs http://ports.ubuntu.com/ubuntu-ports这一步会拉取 Ubuntu 20.04 arm64 的最小基础系统完成之后进入 chroot 安装 Qt 需要的外部开发库sudo chroot /opt/aarch64-rootfs apt-get update sudo chroot /opt/aarch64-rootfs apt-get install -y \ libfontconfig1-dev libfreetype6-dev libexpat1-dev libssl-dev sudo chown -R $USER:$USER /opt/aarch64-rootfs注意这里的libssl-dev安装的是 arm64 版本的 OpenSSL 开发头文件和动态库。我们后面其实会自己编一个静态 OpenSSL但先装 dev 包可以保证 sysroot 里有一组可用的头文件configure 检测时不容易因为缺头文件直接放弃。最终链接时让 Qt 优先找我们自己编的静态库就行。sysroot 里的 glibc 版本必须和最终目标板一致或者比目标板旧。如果你的目标板是 Ubuntu 22.04 arm64那么 sysroot 就用 jammy如果目标板是 Yocto 的就导出一份 Yocto 的 sysroot。混用 glibc 版本是交叉编译里最隐蔽的坑轻则运行时version GLIBC_2.34 not found重则程序直接崩溃。3. 依赖库的静态化能少放就少放能内置就内置Qt 静态编译最耗时间的其实不是 Qt 本身而是它的一堆依赖。Qt Core 依赖 zlib、pcreQt GUI 依赖 libpng、libjpeg、freetype、harfbuzz、fontconfigQt Network 依赖 OpenSSLQt SQL 依赖 SQLite。你如果用系统动态库sysroot 里装几个 dev 包就完事但要静态度就要保证这些依赖以.a的形式存在并且它们自己的依赖也被递归带进来。3.1 先画一张依赖地图动手前先想清楚你的应用到底需要哪些模块。以最常见的 QWidget Network 程序为例我给了一张裁剪后的依赖表Qt 模块典型外部依赖本次做法Qt Corezlib、pcre、glibc用 Qt 自带的-qt-zlib -qt-pcreQt GUIlibpng、libjpeg、freetype、harfbuzz、fontconfig图片类用 Qt 自带字体类按需静态 fontconfigQt NetworkOpenSSL手动交叉编译 libssl.aconfigure 用-openssl-linkedQt SqlSQLite用 Qt 内置 SQLite不单独静态编译原则是只要 Qt 源码树里有自带版本就用-qt-*让它编进去只有 Qt 没带、绕不开、且确实需要的库才手动拿着交叉工具链去编静态版本。3.2 真正需要手动交叉编译的也就两三个库我这次为了 HTTPS 功能需要把 OpenSSL 编成静态库。Qt 源码包不会替你带 OpenSSL因为它有独立的 license 和构建方式所以这一步必须自己做。我用的 OpenSSL 1.1.1 系列最后一个版本因为 Qt 5.14.2 的年代和它最匹配。命令如下export SYSROOT/opt/aarch64-rootfs cd openssl-1.1.1w ./Configure linux-aarch64 no-shared no-tests \ --prefix$SYSROOT/usr/local \ --cross-compile-prefixaarch64-linux-gnu- make -j$(nproc) make install_swno-shared是关键它让 OpenSSL 只生成libssl.a和libcrypto.a。install_sw只装软件部分不装文档和示例。编译完之后在$SYSROOT/usr/local/lib下应该能看到两个.a文件。如果你还需要 fontconfig 的静态版本也可以照葫芦画瓢cd fontconfig-2.13.93 PKG_CONFIG_PATH$SYSROOT/usr/local/lib/pkgconfig \ CCaarch64-linux-gnu-gcc \ ./configure --hostaarch64-linux-gnu \ --prefix$SYSROOT/usr/local \ --disable-shared --enable-static make -j$(nproc) make installfontconfig 在 Linux 字体匹配上还是很重要的。如果彻底关掉 fontconfigQt 在 linuxfb 平台上也能跑但字体的 fallback、字体名匹配会非常原始中文显示很容易变成方块。所以我的建议是能静态编就静态编不要省这一步。3.3 禁用 ICU 与 OpenGL 后功能损失心里要有数ICU 是 Qt 里一个很重的依赖提供了完整的 Unicode、locale、文字排版能力。但它交叉编译特别麻烦依赖链很长静态编出来体积也很大。如果你的程序只是普通 QWidget、QLabel、QLineEdit对多语言排序、Unicode 边界判断没有变态要求直接用-no-icu就好。OpenGL 也一样。嵌入式板子上 OpenGL ES 的驱动千奇百怪Qt 的 configure 一旦检测到 GLES 相关头文件就会进入 OpenGL 的编译路径而交叉环境下这个路径非常容易挂。如果产品界面不需要 3D 加速或 QML 动画就干脆-no-opengl。Qt Widgets 的普通绘制在 linuxfb 平台上用 CPU 渲染完全够用。这两个开关省掉的不是一点半点。我实测过开了 ICU 和 OpenGL构建时间至少多 40%静态包体积大 20% 以上踩坑概率翻倍。做嵌入式 Qt第一课就是学会做减法。4. configure 参数不是背出来的是一行行试出来的Qt configure 是整个工程里最容易被轻视的环节。网上到处能搜到现成命令但你直接抄往往失败原因是你没搞懂参数为什么这样配。我把自己的完整命令贴出来然后逐个拆解。4.1 我最终用的完整 configure 命令行建议使用 shadow build也就是在源码目录之外单独建一个构建目录避免把源码目录弄脏export SYSROOT/opt/aarch64-rootfs mkdir -p /opt/qt-build cd /opt/qt-build /opt/qt-everywhere-src-5.14.2/configure \ -prefix /opt/Qt-5.14.2-aarch64-static \ -sysroot $SYSROOT \ -xplatform linux-aarch64-gnu-g \ -release -static \ -opensource -confirm-license \ -no-opengl -no-dbus -no-xcb -no-icu \ -qt-zlib -qt-libpng -qt-libjpeg -qt-pcre -qt-harfbuzz \ -fontconfig \ -I $SYSROOT/usr/local/include \ -L $SYSROOT/usr/local/lib \ -openssl-linked \ -nomake examples -nomake tests \ -skip qtwebengine -skip qtwebview -skip qtcharts -skip qtdatavis3d make -j$(nproc) make install下载源码时注意我用的是qt-everywhere-src-5.14.2.tar.xz。如果你只想要 qmake 加几个基础模块单独解压 qbase 源码也能编但用 everywhere 源码包会更省心省得后续 Qt Network、Qt Sql 等模块要单独再去编译。4.2 每个关键开关背后的真实作用把核心参数拆开看参数作用我的选择逻辑-release -static编译 release 模式并让 Qt 库生成.a静态库静态编译的目标就是不要目标机上带一堆 Qt.so-opensource -confirm-license接受 LGPL/开源协议没有商业 Qt license 时的标准做法-xplatform linux-aarch64-gnu-g告诉 Qt 用哪个交叉平台 mkspec对应 aarch64-linux-gnu-gcc 工具链-sysroot $SYSROOT指定目标根文件系统编译时找头文件和库的根路径全都在这里-no-opengl -no-dbus -no-xcb关闭 Qt 的 OpenGL、DBus、X11 支持目标板是 framebuffer/linuxfb不需要桌面组件-no-icu关闭 ICU省编译时间体积小功能足够普通应用-qt-zlib -qt-libpng -qt-libjpeg -qt-pcre -qt-harfbuzz使用 Qt 源码内置的第三方库这些库的静态交叉编译不用我自己管-fontconfig启用 fontconfig 支持前提是 sysroot 里已有静态 fontconfig-openssl-linked让 Qt Network 直接链接 OpenSSL 静态库静态编译下必须用 linked不能运行时 dlopen-skip qtwebengine跳过 WebEngine 模块WebEngine 体积巨大也不适合静态链接能关就关-openssl-linked和-openssl-runtime的差别要重点说。前者是编译期把libssl.a、libcrypto.a直接链到 QtNetwork 里后者是运行时去目标机上找libssl.so。既然都做静态了再去动态加载 OpenSSL 就没有意义所以一定要用-openssl-linked。-L $SYSROOT/usr/local/lib和-I $SYSROOT/usr/local/include也很重要。因为我们把 OpenSSL、fontconfig 装到了 sysroot 的/usr/local下编译器在 sysroot 里默认搜索路径不一定包含/usr/local不加这两个参数configure 很可能找不到静态库最后链接时报一堆 undefined reference。4.3 mkspec 没有现成平台文件时怎么办如果 configure 报错说找不到linux-aarch64-gnu-g这个平台描述文件不要慌。Qt 源码里可能默认没有这个 mkspec需要自己从linux-g复制一份出来改cd /opt/qt-everywhere-src-5.14.2/qtbase/mkspecs cp -r linux-g linux-aarch64-gnu-g然后编辑linux-aarch64-gnu-g/qmake.conf把编译器相关变量改成QMAKE_CC aarch64-linux-gnu-gcc QMAKE_CXX aarch64-linux-gnu-g QMAKE_LINK aarch64-linux-gnu-g QMAKE_LINK_SHLIB aarch64-linux-gnu-g QMAKE_AR aarch64-linux-gnu-ar cqs QMAKE_STRIP aarch64-linux-gnu-strip load(qt_config)改完之后重新跑 configure-xplatform linux-aarch64-gnu-g就能找到了。这个平台文件本质上是告诉 qmake遇到这个平台时用哪个编译器前缀、哪个 ar、哪个 strip。 30 分钟级别就能解决绝大多数“找不到平台”的问题。5. 构建信号从 make 到 file 命令确认产物configure 过了不代表万事大吉。Qt 整包静态编译的时间通常在 1 到 2 小时过程中你会看到大量编译输出要能从里面筛选出关键信息和常见坑。5.1 并行度和内存问题很多人习惯直接make -j$(nproc)但 Qt 静态编译对内存的消耗比你想象的大。我建议先看主机内存再定并行数free -h nproc如果只有 8GB 内存老老实实用make -j416GB 可以用make -j832GB 以上再考虑更高并行度。原因很简单Qt 的 moc、rcc、规则生成器会大量调用子进程并行太猛会直接把机器内存吃满最终 OOMmake 中途退出。OOM 之后再重跑 make 虽然能在增量编译基础上继续但很容易出现一些莫名其妙的半成品文件污染不如一开始稳一点。如果编译到一半失败先看是不是内存不足。用dmesg -T | tail -20如果看到Out of memory就降低并行度再试。若是某个源码编译报错则把错误信息保存下来重点看是不是缺少某个头文件或者某个 configure 特性被误判。5.2 安装目录里真正有用的文件make install 完成后到安装目录看一眼。正常情况会形成这样一个结构/opt/Qt-5.14.2-aarch64-static/ ├── bin/qmake ├── lib/libQt5Core.a ├── lib/libQt5Gui.a ├── lib/libQt5Widgets.a ├── lib/libQt5Network.a ├── plugins/platforms/libqlinuxfb.a └── mkspecs/qconfig.pri注意这里的bin/qmake是在 x86_64 主机上运行的 qmake它不是 arm64 程序。它的作用是在 x86_64 主机上为 arm64 目标生成 Makefile。所以不要拿这个 qmake 跑到目标板上去用目标板上也根本不需要安装 Qt。每个.a库旁边通常还有一个.prl文件。这个文件记录了该静态库自己的依赖项qmake 在链接你的应用时会读取.prl自动带上依赖非常关键。裁剪、拷贝安装目录时一定不要把.prl丢了。5.3 用 qmake 编一个 hello并确认 ELF 与链接依赖静态 Qt 是否真的可用最有效的验证方式就是编一个小程序。创建hello.proQT widgets CONFIG static TARGET hello SOURCES main.cpp创建一个简单的main.cpp#include QApplication #include QLabel int main(int argc, char *argv[]) { QApplication app(argc, argv); QLabel label(static Qt on aarch64); label.resize(400, 200); label.show(); return app.exec(); }然后编译mkdir -p /tmp/hello cd /tmp/hello /opt/Qt-5.14.2-aarch64-static/bin/qmake /path/to/hello.pro make file hello正常情况下file输出会包含ELF 64-bit LSB executable, ARM aarch64。你还可以再用 readelf 检查动态依赖aarch64-linux-gnu-readelf -d hello只要NEEDED里没有libQt5*.so.*就说明 Qt 已经真正链进了静态库。可能还会残留libstdc、libm、libc、libgcc_s这类系统库这是正常的。6. 部署到目标板后的三个“静态之外”的坑静态编译能解决“Qt 库找不到”的问题但解决不了“程序起来了却满屏方块”“HTTPS 请求证书错误”“平台插件找不到”这类运行期问题。这三个坑几乎每个做静态 Qt 的人都会踩提前了解能省很多现场时间。6.1 平台插件要主动导入Qt 的 GUI 程序在运行时需要一个平台插件常见的是linuxfb、xcb、eglfs。在动态 Qt 环境里插件是.so程序运行时自己去plugins/platforms目录下找。但在静态编译里插件被编成了.a链接器不会把没有被引用的插件代码合入可执行文件。所以你必须让程序主动告诉链接器我要用哪个插件。在main.cpp顶部加上#include QtPlugin Q_IMPORT_PLUGIN(QLinuxFbIntegrationPlugin)在.pro里同时加上QTPLUGIN qlinuxfb如果漏了这一步程序启动时会提示找不到linuxfb平台插件或者直接提示could not find or load the Qt platform plugin linuxfb。这是静态 Qt 最常见的新手坑没有之一。6.2 字体、证书、时区这些数据文件不会进二进制静态链接只解决代码不解决数据。你写QLabel(你好)代码编译进去了但中文字体不会自动跟着进二进制。系统里没有中文字体或者 fontconfig 找不到字体目录程序能启动字却全是方块。部署时至少要保证目标板上有一个字体文件然后设置字体路径export QT_QPA_FONTDIR/usr/share/fonts/truetype/dejavu export QT_QPA_PLATFORMlinuxfb ./helloTLS 证书也是同样的道理。OpenSSL 是静态链进去了但根证书文件不在二进制里。目标板上如果没有/etc/ssl/certs/ca-certificates.crt你的 HTTPS 请求会报证书验证失败。最简单的方式是把主机的ca-certificates.crt拷到目标板sudo cp /etc/ssl/certs/ca-certificates.crt /opt/aarch64-rootfs/etc/ssl/certs/然后再设置环境变量export SSL_CERT_FILE/etc/ssl/certs/ca-certificates.crt export QT_SSL_CERT_FILE/etc/ssl/certs/ca-certificates.crt时区数据也一样。如果不开 ICUQt 对时区的处理能力会退化程序里获取本地时间前最好确认目标板/etc/timezone和/usr/share/zoneinfo是完整的。6.3 排查运行问题的三件套现场出问题时先别急着怀疑静态编译失败按顺序做三件事file hello aarch64-linux-gnu-readelf -d hello strace -f -o /tmp/trace.log ./hello -platform linuxfb第一句确认架构没错第二句确认没有 Qt 相关动态库缺失第三句看运行到哪个系统调用附近卡住或报错。大部分静态 Qt 启动失败到最后都是文件系统里少了字体、证书、插件数据或时区数据而不是少了.so。static 解决了库的依赖但解决不了资源与环境的依赖。7. 哪些项目别硬走全静态最后的建议静态交叉编译不是万金油。如果你正在开始一个全新项目我反而建议你先认真评估一下是不是真的需要全静态。7.1 不适合的场景如果你的程序会频繁改动界面、频繁部署动态编译配合 Buildroot 或目标板上的 apt 源开发效率会高很多。静态 Qt 每次改代码只需要重编应用这个还好但如果改的是 Qt 模块或插件重新 make install 一次 Qt 的等待时间会让人崩溃。如果应用重度依赖 OpenGL ES 或 GPU 驱动比如 QML 动画、三维渲染静态 OpenGL 的适配非常麻烦。因为你不仅要静态编 Qt还要静态编 Mesa 或厂商 GPU 用户态库这些库的交叉编译难度远高于 Qt 本身。如果要用 QtWebEngine我建议直接放弃全静态。它的体积、插件机制、进程模型都和静态链接不太对付。另外如果你的产品是商业闭源分发还要额外注意 Qt 的 LGPL 条款对静态链接的要求。这不是技术问题但比技术问题更能决定你项目的上限。7.2 如果只想要可控部署还有个折中路线不是所有情况都要全静态。我见过不少项目最终采用“动态 Qt 应用目录打包”的方式# qmake 里加 rpath QMAKE_LFLAGS -Wl,-rpath,\\\$\$ORIGIN/../lib把 Qt 的.so、平台插件、QML 模块全部拷贝到可执行文件旁边的目录然后设置LD_LIBRARY_PATH。这比全静态省事很多体积也更小兼容性也不错。但它的前提是目标板 glibc 版本和你的构建机/sysroot 兼容否则还是会有GLIBC_XX not found的问题。我个人的建议是先花一晚上把项目需要的 Qt 模块清单列出来能砍就砍。Qt 静态编译最大的成本不是编译本身是你围绕一个又一个外部依赖做取舍、做静态化的过程。每次多一个静态依赖就多一个需要维护的交叉编译脚本。让 Qt 自带库去干它们擅长的事让 OpenSSL 这种绕不开的库单独静态编译其他能关的功能一律关掉这套路在我手头已经跑通多次后续在 5.15 和 Qt 6 上也能基本平移。真到了项目交付、现场部署那天你会感谢当年那个愿意在 configure 命令行前多研究半小时的自己。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价