ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Qt 5.14.2 aarch64静态交叉编译完整指南:从环境搭建到部署避坑

2026/9/19 11:46:49 拓冰建站 浏览量
Qt 5.14.2 aarch64静态交叉编译完整指南:从环境搭建到部署避坑 前一阵要把一个 Qt 界面程序从 x86 主机搬到一块 arm64 开发板上跑目标板空间很紧张既没有完整的桌面环境也不方便在板上一遍遍装 Qt 运行库。最直接的思路就是在一台 x86 主机上做好 Qt 5.14.2 的 aarch64 静态交叉编译把 Qt 库直接链进最终可执行文件拷到板子上一个文件就能跑。这个过程踩了不少坑从选参数、调依赖到处理插件注册水挺深。如果你也准备在开发板、ARM 盒子或者体积受限的嵌入式设备上部署 Qt 应用这篇手册会帮你省掉不少试错时间。我尽量按照“从零到能跑”的顺序来写里面除了完整步骤还会把每个配置项为什么这么设、哪些地方最容易翻车都讲清楚。1. 为什么要做 aarch64 静态交叉编译1.1 动态链接和静态链接的差别普通 x86 桌面上装 Qt默认都是动态链接。程序运行时需要到系统目录里找 libQt5Widgets.so、libQt5Gui.so 这一大堆动态库缺一个就起不来。到了嵌入式环境问题就会被放大板子上的 rootfs 很精简可能根本没有 Qt 运行库或者版本跟编译环境对不上一个版本不一致就报 cannot mix incompatible Qt library。静态交叉编译则是把 Qt 库和你的代码一起编译成一个完整的 ELF 可执行文件目标板上不需要安装任何 Qt 运行时。它的优点非常直观部署简单一个二进制文件拷贝过去就能跑不需要处理依赖。依赖冲突可能性降到最低动态库版本互相污染的问题直接消失。对目标板 rootfs 的改动最小不往系统目录里写任何文件。代价也很明显编译出来的程序体积偏大而且如果以后想升级 Qt 版本必须重新编译整个工程。另外如果应用要动态加载第三方 so 插件比如浏览器内核、私有编解码库静态方案会麻烦一些。1.2 为什么选择 Qt 5.14.2 这个版本Qt 5.14.2 是 5.14 系列的最后一个补丁版本成熟度和稳定性都经过了很长时间验证。它在很多开发板的 BSP 里都有对应的交叉编译配置网上能搜到的资料也最全遇到问题容易找到参考。5.15 虽然是 LTS但 5.14.2 在嵌入式项目里存量非常大很多老项目甚至出厂软件都基于这个版本选它来搭环境风险最小。1.3 适用场景与不适用场景静态交叉编译不是银弹。在动手之前先想清楚自己的场景场景是否适合静态编译原因单一 Qt Widgets 程序板子空间紧张适合目标板无需安装任何 Qt 运行时部署成本低程序要依赖第三方闭源 so动态加载大量插件不太适合静态链接会把第三方库一起链进来处理插件注册和兼容性很费劲OpenGL / GPU 渲染强相关依赖显卡私有驱动不太适合GPU 驱动大多以动态库形式提供静态编译容易踩驱动加载的坑多模块大系统需要热更新某个界面库不太适合更新一个模块就必须重新发布整个可执行文件我这次的目标板是纯 framebuffer 环境没有 X11没有 GPU 渲染需求所以走静态编译非常合适。2. 环境准备与工具链搭建2.1 宿主机条件与目录规划交叉编译需要一台 x86_64 的 Linux 主机我用的是 Ubuntu 20.04系统比较干净只装了基础开发包。Qt 5.14.2 的编译对系统版本不算挑剔但 glibc 相关工具链的兼容性还是要注意建议用 Ubuntu 18.04 或 20.04 这类中等版本太老的系统可能导致编译工具链缺依赖太新的系统反而容易碰到目标板 glibc 版本偏低的问题。我在 /opt 下规划了一个干净的目录结构/opt/qt-cross/ ├── src/ # Qt 源码压缩包和解压目录 ├── qt-5.14.2-static/ # Qt 交叉编译后的安装目录 └── sysroot/ # 目标板根文件系统可先留空目录结构其实不用太复杂但一定要固定下来。后续 qmake 会把这个安装路径写进很多配置里中途换路径容易导致各种找不到头文件、找不到库的诡异问题。2.2 安装 aarch64 交叉编译工具链Ubuntu 20.04 的软件源里自带 aarch64 交叉工具链直接安装就行sudo apt-get update sudo apt-get install gcc-aarch64-linux-gnu g-aarch64-linux-gnu安装完成后验证一下编译器是否可用aarch64-linux-gnu-g --version如果能看到类似gcc version 9.4.0的输出说明工具链已经就绪。交叉编译器在 /usr/bin 下头文件和库可以从 /usr/aarch64-linux-gnu 下找到不过这个目录里主要包含 libc、libstdc 等基础库并不包含 Qt 所需的第三方依赖库。如果目标板对 glibc 版本比较敏感建议先用板子上的ldd --version看一眼 glibc 版本再选择匹配的交叉工具链。GCC 9 对应的 glibc 比较高如果板子系统太老后面静态编译出来的程序很可能在板子上报GLIBC_2.28 not found。2.3 确认目标板运行环境交叉编译之前一定先搞清楚目标板的实际情况不要想当然。我在板子上执行了这几个命令uname -m uname -a ldd --version | head -n1 ls /lib/aarch64-linux-gnu/ | head我的目标板是 64 位 ARM 架构内核版本中等glibc 版本足够新没有 X11 环境也没有独立的 GPU 驱动framebuffer 设备节点 /dev/fb0 存在且可访问。这些信息直接决定了后面 configure 的参数怎么选。如果板子有完整的桌面环境那么需要走 XCB 的路线准备工作量和复杂度都会高出不少。我在第 3.3 节会单独说明这条路线。3. 处理 Qt 源码与系统依赖3.1 下载 Qt 5.14.2 源码Qt 官方提供 every source 整合包所有模块都在一个压缩包里非常省事。下载地址是 Qt 官方仓库也可以从国内高校镜像站下载速度快很多。拿到压缩包后先校验哈希再看一下解压后的目录结构wget https://download.qt.io/archive/qt/5.14/5.14.2/qt-everywhere-src-5.14.2.tar.xz sha1sum 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这个整合包里有 qtbase、qtdeclarative、qtmultimedia、qtserialport 等一大堆模块体积有几个 GB。好在 configure 阶段可以跳过不需要的模块节省大量编译时间。3.2 第三方依赖库的取舍策略Qt 编译时会用到 zlib、libpng、libjpeg、pcre、freetype 等第三方库。在 x86 桌面上系统可能已经装好了这些库编译时直接链接就行。但在交叉编译场景下这些库必须存在于 aarch64 的 sysroot 里否则 configure 检测不到对应功能就会被禁用。避免麻烦的最好办法是使用 Qt 自带的第三方库源码彻底绕开 sysroot 的依赖问题。Qt 源码里已经内置了这些基础库的实现configure 时通过-qt-zlib、-qt-libpng、-qt-libjpeg、-qt-pcre、-qt-freetype等参数强制使用内置版本。这个取舍非常关键。它能让你在目标板没有完整 sysroot 的情况下完成编译也避免了交叉编译这些第三方库时遇到的头文件路径、编译选项冲突问题。代价是最终二进制体积大一点但对嵌入式场景来说完全可接受。3.3 如果目标板必须用 X11/XCB 怎么办如果你的目标板跑的是完整 Linux 桌面程序需要连接 X Server那就不能关掉 XCB反而要提前交叉编译一堆 X11 相关库。大致包括xcb-proto 和 libxcb 本体xcb-util、xcb-util-image、xcb-util-keysyms 等扩展库xkbcommonX11 相关的头文件这些库需要一块一块交叉编译而且交叉编译时最容易翻车的是 pkg-config 路径问题。如果在编译 XCB 时 PKG_CONFIG_PATH 指向了宿主机 x86 目录编出来的库混进 x86 架构后面 Qt 链接阶段会报各种架构不匹配的错误。我的经验是如果项目没有强依赖 X11能走纯 framebuffer 或 EGLFS 就尽量走省下的时间足够你好好调业务代码。这一节只是想提醒你XCB 路线不是不行而是工作量会明显上升。4. configure 配置与 Qt 编译4.1 推荐的最简配置命令回到我的目标板环境。纯 framebuffer、无 OpenGL、无 GPU 驱动所以第一步先用一个最简配置跑通整个流程cd qt-everywhere-src-5.14.2 ./configure \ -static \ -release \ -opensource \ -confirm-license \ -prefix /opt/qt-cross/qt-5.14.2-static \ -xplatform linux-aarch64-gnu-g \ -nomake examples \ -nomake tests \ -no-opengl \ -no-icu \ -qt-zlib \ -qt-libpng \ -qt-libjpeg \ -qt-pcre \ -qt-freetype \ -no-xcb \ -no-eglfs \ -no-glib \ -no-iconv \ -no-dbus \ -no-pch \ -linuxfb逐项说一下关键参数的作用-static是本次编译的核心目标告诉 Qt 只生成静态库。-xplatform linux-aarch64-gnu-g指定目标平台为 aarch64 架构。-no-opengl关闭 OpenGL 模块。目标板没有 GPU 驱动就算编译进去也跑不起来而且 OpenGL 依赖的库在交叉编译环境下很难准备齐全。-no-icu关闭 ICU 库。ICU 体积大交叉编译复杂非国际化项目一般用不到。-qt-zlib、-qt-libpng等几个参数上面说过强制使用内置第三方库。-no-xcb关闭 XCB 支持因为目标板没有 X Server。-no-eglfs关闭 EGLFS同样是因为没有 GPU 环境。-linuxfb是让 Qt 启用 Linux framebuffer 平台插件这也是后面程序在板子上显示界面的基础。-no-glib、-no-iconv、-no-dbus都是进一步削减系统依赖静态编译环境下能不开的就别开。如果你只是先验证环境这套参数是最稳的。4.2 qmake.conf 的检查与调整Qt 5.14.2 在 qtbase/mkspecs/devices 目录下自带了一些设备配置其中就包含 linux-aarch64-gnu-g 这条 mkspec。默认情况下它假设交叉编译器名为aarch64-linux-gnu-g正好我 Ubuntu 上装的工具链就是这个名字所以不需要大改。如果使用的是自研工具链或者其他厂商的命名方式需要打开这个 mkspec 文件手动指定vim qtbase/mkspecs/devices/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_CFLAGS_RELEASE和QMAKE_CXXFLAGS_RELEASE中包含了-O2 -pipe这类常规优化参数。4.3 编译、安装与模块裁剪configure 完成后直接开始编译。Qt 的编译时间不短建议给足 CPU 资源make -j$(nproc) make install全套模块编下来即使跳过了 examples 和 tests在 8 线程机器上也要一小时左右。如果你的项目只用了 Widgets、Core、Gui 几个模块可以在 configure 时加-skip参数跳过大量无关模块时间能大幅缩短。常用的裁剪命令./configure ... \ -skip qtscript \ -skip qtwebengine \ -skip qtconnectivity \ -skip qtdeclarative \ -no-feature-cups \ -no-feature-ftp \ -no-feature-printer编译安装完成后检查一下安装目录ls /opt/qt-cross/qt-5.14.2-static/lib/libQt5Core.a ls /opt/qt-cross/qt-5.14.2-static/plugins/platforms/只要 lib 目录下存在libQt5Core.a、libQt5Widgets.a这些静态库plugins/platforms 下存在libqlinuxfb.a说明编译基本成功。5. 用一个小项目验证交叉编译环境5.1 写一个最小工程环境搭完一定要用最简单的工程验证一遍。我的验证项目是一个显示“hello aarch64”的窗口程序代码非常简单#include QApplication #include QLabel int main(int argc, char *argv[]) { QApplication app(argc, argv); QLabel label(hello aarch64); label.resize(200, 100); label.show(); return app.exec(); }工程文件如下QT core gui widgets CONFIG static TARGET hello TEMPLATE app SOURCES main.cpp QTPLUGIN qlinuxfb这里有个非常关键的细节静态编译下Qt 的平台插件和动态插件机制完全不同。动态编译时程序运行时去 plugins/platforms 目录下加载libqlinuxfb.so即可。静态编译时插件代码必须和主程序链接到一起否则板子上会报could not find or load the Qt platform plugin linuxfb。为了让插件确实链入可执行文件推荐在 main.cpp 里显式声明#include QtPlugin Q_IMPORT_PLUGIN(qlinuxfb)QTPLUGIN负责把插件链接进工程Q_IMPORT_PLUGIN负责注册插件。两个都写上最保险这是静态编译 Qt 最容易忽略的一个点。5.2 交叉编译并部署到板子配置好环境变量后开始编译export PATH/opt/qt-cross/qt-5.14.2-static/bin:$PATH qmake hello.pro make -j$(nproc)编译完成后用 file 命令检查一下生成的可执行文件架构file hello正确输出应该类似hello: ELF 64-bit LSB executable, ARM aarch64如果这里显示x86-64说明 qmake 配置有问题编译器没有实际使用 aarch64 工具链。可以运行qmake -query查看 QMAKE_XSPEC 的值是否包含linux-aarch64-gnu-g以及 Makefile 里 CC 和 CXX 的指向。然后检查动态库依赖aarch64-linux-gnu-readelf -d hello | grep NEEDED正常情况下你已经看不到 libQt5Core.so 之类的 Qt 动态库依赖了最多只剩 libc、libstdc 等系统基础库。将可执行文件传到板子上运行scp hello root192.168.1.100:/tmp/ ssh root192.168.1.100 export QT_QPA_PLATFORMlinuxfb export QT_QPA_FB_FONTDIR/usr/share/fonts/truetype cd /tmp ./hello程序跑起来后如果目标板连接了 LCD 或 HDMI 屏幕就能看到窗口。如果没有屏幕可以用 offscreen 平台先验证程序逻辑export QT_QPA_PLATFORMoffscreen ./hello5.3 汉字和字体显示问题framebuffer 环境下常见一个附加问题Qt 在目标板上找不到字体中文全部显示成方块或者问号。这是因为纯 framebuffer 环境没有字体管理服务Qt 需要从文件系统加载字体文件。最简单的解决方法是把字体文件拷到目标板并告诉 Qt 字体目录export QT_QPA_FB_FONTDIR/usr/share/fonts更好的做法是把字体作为资源文件直接编译进程序静态加载QFontDatabase::addApplicationFont(:/fonts/NotoSansCJK-Regular.ttc);这种方法可靠性最高程序在任何板子上都不会出现字体缺失问题代价是可执行文件会增大几 MB换来的是省心。6. 常见错误和排查记录6.1 编译阶段典型问题静态交叉编译的报错信息多且杂很多问题看着吓人其实原因很简单。我把实际项目中遇到过的、以及网友们高频遇到的问题整理了一下报错信息原因分析解决办法Project ERROR: Unknown module(s) in QT: serialportqmake 找不到 QtSerialPort 模块可能是 configure 时模块被跳过或模块没有编译安装在 Qt 源码顶层执行make module-qtserialport后重新make install确认安装目录 lib 下存在libQt5SerialPort.acannot mix incompatible Qt library (5.15.3) with this library (5.15.2)编译时把系统里 5.15.3 的头文件或动态库与交叉编译的 5.15.2 静态库混用了检查which qmake是否指向 /opt 下的静态版本确认/usr/lib/x86_64-linux-gnu不在库搜索路径里清理工程后重新 qmakecould not find or load the Qt platform plugin linuxfb静态编译下 platform 插件没有注册或没有链接进来在 .pro 里加QTPLUGIN qlinuxfb在 main.cpp 里加Q_IMPORT_PLUGIN(qlinuxfb)重新编译GLIBC_2.28 not found目标板系统的 glibc 版本低于编译工具链要求的版本换用旧版本交叉编译工具链或升级目标板系统undefined reference toqt_version_tag静态库链接顺序错误或者某些 Qt 模块产物缺失检查 Makefile 中 LIBS 是否包含所需 Qt 库顺序从上层模块到下层模块排列QStandardPaths: XDG_RUNTIME_DIR not set运行环境没有家目录和运行时目录off-screen 模式下常见在板子上执行export XDG_RUNTIME_DIR/tmp/runtime-root mkdir -p $XDG_RUNTIME_DIRlinuxfb: Could not open framebuffer device/dev/fb0板子没启用 framebuffer 设备或当前用户无权限访问确认内核驱动加载正常尝试用 root 用户运行或检查 /dev/fb0 是否存在6.2 编译过程踩过的两个现场第一个让我卡了最久的问题是编译完成后生成的 hello 文件竟然还是 x86_64 架构。排查半天发现是系统里安装了另一个版本的 qmake终端执行 qmake 时 PATH 没有优先到 /opt/qt-cross 目录Makefile 里编译器全部指向了系统 g。解决方式是显式指定环境变量或者干脆给 /opt/qt-cross 里的 qmake 建一个软链接确保which qmake指向正确路径。第二个常见坑是链接阶段报一堆 undefined reference其中大量是 dl、pthread、z 这些系统库符号。静态编译时 Qt 内部模块之间的依赖关系很复杂链接顺序一旦搞错就会出现。碰到这种问题不要慌先看第一个 undefined symbol 来自哪个模块然后在 .pro 文件里补上对应库比如LIBS -ldl -lpthread -lz静态链接对库顺序极其敏感一定要让 Makefile 中 LIB 的顺序是 Qt 上层模块在前、下层模块在后系统库放最后。6.3 用 qemu 在宿主机上快速验证有些故障属于逻辑问题不一定需要板子就能排查。我在宿主机上装了 qemu-user这样就可以直接运行 aarch64 静态编译出来的程序sudo apt-get install qemu-user qemu-aarch64 -L /usr/aarch64-linux-gnu ./hello -platform offscreen-L参数指定 aarch64 的系统库路径。由于程序基本是静态链接qemu 运行起来非常快特别适合在写业务逻辑时快速验证 Qt 程序的启动流程比每次传文件到板子省时间得多。7. 踩坑之后的一些体会这套环境我陆陆续续搭过好几遍从最早到处搜索 configure 参数到后来能一句话说出某个报错可能的原因最大的体会是交叉编译最忌讳一把梭。第一次做一定要先用最简单的工程、最少的模块跑通再逐步加功能。一上来就把 webengine、multimedia、serialport 全编进去出了问题根本分不清是环境问题还是代码问题。另外一定要保留 configure 时的完整日志。Qt 庞大的 build 系统在配置阶段会打印大量“检测到某库”、“未检测到某库”的信息这些日志是排查问题时的第一手线索。找不到头文件、找不到库的时候回头翻日志往往一眼就能定位。最后一个小建议编译完安装目录后我习惯在 /opt/qt-cross 下放一个 environment.sh把 PATH、字体目录、QPA 平台选择等环境变量全部固化下来这样下次开新终端不用重新敲一堆 export。后期做 CI 批量构建的时候这套东西也能直接复用。交叉编译这事一旦把基础设施理顺后面就是水到渠成的事。