1. 为什么要在 aarch64 上折腾 Qt 5.14.2 静态编译
如果你手上有 Orange Pi CM5、树莓派这类 aarch64 开发板,又恰好要跑一个 Qt 界面程序,大概率会遇到一个很现实的问题:板子上跑的是精简系统,没有桌面环境,甚至没有包管理器能直接装 Qt 运行库。这时候你有两条路——要么在板子上装一堆动态库,要么把 Qt 直接静态编进你的程序里,一个可执行文件拷过去就能跑。
我选的是后者。原因很直接:部署成本。动态链接方案下,你得保证目标板上的libQt5Core.so、libQt5Gui.so、libQt5Widgets.so等一长串库的版本和编译时完全一致,差一个小版本就可能报cannot mix incompatible Qt library这种让人头大的错误。静态编译之后,你的程序就是一个独立的二进制文件,扔到板子上chmod +x就能运行,不需要在目标系统上装任何 Qt 相关的东西。
但静态编译 Qt 本身是个体力活,尤其是 aarch64 架构下,坑比 x86 多得多。网上大部分教程要么是针对 x86 的,要么是 Qt 5.15 的,要么语焉不详地跳过关键配置。我这次从零把 Qt 5.14.2 在 aarch64 上静态交叉编译跑通,中间踩了不少坑,这里把完整过程和我自己的理解整理出来。
这篇文章适合谁看?如果你满足以下任意一条,应该都能用上:
- 需要在 aarch64 开发板上部署 Qt 程序,但不想在板子上装 Qt 运行库
- 想理解 Qt 交叉编译的配置逻辑,而不是照抄命令
- 之前尝试过静态编译但卡在某个环节,比如
unknown module(s) in qt: serialport这类模块缺失问题 - 需要一套可复现、可迁移的编译流程,换台机器也能照着做
整个流程的核心思路是:在 x86 主机上,用 aarch64 交叉编译工具链,把 Qt 源码编译成 aarch64 架构的静态库,然后用这套静态库去编译你的应用程序。听起来简单,但每一步都有细节。
2. 编译环境搭建与工具链选型
2.1 主机环境与依赖准备
我用的主机是 Ubuntu 20.04 x86_64,这是比较稳妥的选择。Ubuntu 22.04 也可以,但要注意一些包名可能有变化。不建议用 CentOS 7.9 做主机,虽然它稳定,但自带的 GCC 版本太老,编译 Qt 5.14.2 时可能会遇到 C++14 特性支持不全的问题。
先装基础依赖,这一步别偷懒,缺一个包后面就可能报奇怪的错:
sudo apt update sudo apt install -y build-essential perl python3 git \ libgl1-mesa-dev libglu1-mesa-dev \ libxkbcommon-dev libxkbcommon-x11-dev \ libfontconfig1-dev libfreetype6-dev \ libssl-dev libdbus-1-dev \ libinput-dev libts-dev \ libmtdev-dev libudev-dev \ libjpeg-dev libpng-dev libgif-dev \ flex bison gperf这里解释几个容易被忽略的:
libxkbcommon-dev和libxkbcommon-x11-dev:Qt 的键盘输入处理依赖,缺了会在 configure 阶段报 xkbcommon 相关错误libts-dev和libmtdev-dev:触摸屏支持,如果你的板子接触摸屏,这两个必须有gperf:Qt 的某些模块(比如 QtWebEngine)需要,虽然我们不一定编 WebEngine,但 configure 阶段会检查flex和bison:语法分析器生成工具,Qt 的某些工具需要
提示:如果你在 Ubuntu 22.04 上编译,
libgif-dev可能已经改名或者被合并,遇到找不到包的情况,用apt search查一下实际包名。
2.2 aarch64 交叉编译工具链的选择
工具链是整个流程的基石,选错了后面全是坑。市面上常见的 aarch64 工具链有几种:
| 工具链来源 | 特点 | 适用场景 |
|---|---|---|
| Linaro GCC | 官方维护,版本更新及时 | 通用 aarch64 开发 |
| ARM 官方工具链 | 针对 ARM 架构优化 | 深度 ARM 平台开发 |
| 发行版自带(如 Ubuntu 的 gcc-aarch64-linux-gnu) | 安装方便,版本稳定 | 快速验证 |
| 厂商定制工具链 | 针对特定芯片优化 | 特定开发板 |
我这次用的是 Linaro 的 GCC 7.5.0 版本,下载地址在 Linaro 官网可以找到。为什么选 7.5.0 而不是更新的版本?因为 Qt 5.14.2 这个版本的代码对 GCC 8 以上的某些默认行为(比如-fno-common)兼容性不够好,用太新的 GCC 反而容易出问题。GCC 7.5.0 是一个经过大量项目验证的稳定版本。
下载后解压到/opt目录:
sudo tar -xvf gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar.xz -C /opt/然后把工具链的 bin 目录加入 PATH。我习惯在~/.bashrc里加一行:
export PATH=/opt/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin:$PATH验证工具链是否可用:
aarch64-linux-gnu-gcc --version如果输出了 GCC 7.5.0 的版本信息,说明工具链就绪。
注意:不要用
gcc-arm-linux-gnueabihf这类 32 位 ARM 工具链,架构不对,编译出来的东西在 aarch64 板子上跑不了。确认你的工具链前缀是aarch64-linux-gnu-而不是arm-linux-gnueabihf-。
2.3 为什么不用板子自带的编译环境
有人可能会想:我直接在板子上编译不行吗?理论上可以,但实际体验很差。aarch64 开发板的 CPU 性能通常有限,编译 Qt 这种大型项目可能要几个小时甚至更久,而且板子的内存往往不够,编译过程中容易 OOM。交叉编译在 x86 主机上做,速度快十倍不止,而且主机环境可控,出了问题好排查。
另一个原因是,板子上的系统往往是精简过的,缺少编译 Qt 需要的各种开发库。你在板子上装这些库的功夫,可能比交叉编译还费劲。
3. Qt 5.14.2 源码配置的取舍逻辑
3.1 源码获取与目录规划
Qt 5.14.2 的源码可以从 Qt 官方下载页面获取,文件名是qt-everywhere-src-5.14.2.tar.xz。这个包包含了 Qt 的所有模块源码,解压后大概有几个 GB。
tar -xvf qt-everywhere-src-5.14.2.tar.xz cd qt-everywhere-src-5.14.2我建议在源码目录外面建一个独立的构建目录,不要直接在源码目录里 configure 和 make。这样做的好处是:如果编译失败需要重新来,直接删掉构建目录就行,源码目录保持干净。
mkdir -p ~/qt-build-5.14.2-aarch64 cd ~/qt-build-5.14.2-aarch643.2 configure 参数逐条拆解
configure 是整个编译过程中最关键的一步,参数配错了后面要么编不过,要么编出来的东西不能用。下面是我实际使用的配置命令,然后逐条解释:
../qt-everywhere-src-5.14.2/configure \ -prefix /opt/qt-5.14.2-aarch64-static \ -static \ -release \ -opensource \ -confirm-license \ -nomake examples \ -nomake tests \ -no-opengl \ -no-xcb \ -no-eglfs \ -no-linuxfb \ -no-kms \ -qt-zlib \ -qt-libpng \ -qt-libjpeg \ -qt-freetype \ -qt-harfbuzz \ -qt-pcre \ -no-icu \ -no-glib \ -no-dbus \ -no-cups \ -no-sql-sqlite \ -no-sql-mysql \ -no-sql-psql \ -no-sql-odbc \ -no-sql-tds \ -no-sql-db2 \ -no-sql-oci \ -no-sql-sqlite2 \ -no-sql-sqlite3 \ -no-sql-ibase \ -no-sql-symbian \ -no-sql-interbase \ -skip qtwebengine \ -skip qtwebview \ -skip qtquickcontrols \ -skip qtquickcontrols2 \ -skip qtmultimedia \ -skip qtvirtualkeyboard \ -skip qtlocation \ -skip qtsensors \ -skip qtconnectivity \ -skip qtwayland \ -skip qtgamepad \ -skip qtserialbus \ -skip qtserialport \ -skip qtspeech \ -skip qtremoteobjects \ -skip qtscxml \ -skip qtcharts \ -skip qtdatavis3d \ -skip qtnetworkauth \ -skip qtpurchasing \ -skip qtwebchannel \ -skip qtwebglplugin \ -skip qtwebsockets \ -skip qtwinextras \ -skip qtx11extras \ -skip qtxmlpatterns \ -xplatform linux-aarch64-gnu-g++ \ -device-option CROSS_COMPILE=/opt/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu- \ -sysroot / \ -v现在逐条解释这些参数背后的逻辑:
-prefix /opt/qt-5.14.2-aarch64-static:指定安装路径。编译完成后make install会把所有头文件、静态库、工具安装到这个目录。我习惯放在/opt下,方便管理。
-static:这是核心参数,告诉 Qt 编译静态库而不是动态库。静态库的扩展名是.a,动态库是.so。
-release:编译 release 版本,不做调试符号。如果你需要调试,可以改成-debug,但编译出来的库会大很多。
-opensource -confirm-license:使用开源协议,自动确认许可。这两个参数必须一起用,否则 configure 会交互式地问你许可问题,在脚本里会卡住。
-nomake examples -nomake tests:不编译示例和测试。Qt 的示例和测试非常多,编译它们会浪费大量时间,而且对我们的目标没有帮助。
-no-opengl:禁用 OpenGL 支持。如果你的板子没有 GPU 或者不需要 OpenGL 渲染,禁用它可以避免很多依赖问题。如果你的应用需要 OpenGL,那就要保留这个选项,并且确保工具链里有对应的 GL 库。
-no-xcb:禁用 X11 后端。嵌入式板子通常不跑 X11,禁用它可以减少依赖。
-no-eglfs -no-linuxfb -no-kms:禁用各种显示后端。这些后端在特定场景下有用,但如果你只是用 Qt 做基础界面,不需要它们。禁用后可以减少对底层图形库的依赖。
-qt-zlib -qt-libpng -qt-libjpeg -qt-freetype -qt-harfbuzz -qt-pcre:使用 Qt 自带的这些库,而不是系统库。这样做的好处是避免交叉编译时找不到 aarch64 版本的系统库。Qt 源码里自带了这些第三方库的源码,用-qt-前缀就会编译它们。
-no-icu:禁用 ICU(国际化组件)。ICU 库很大,交叉编译它很麻烦,如果你的应用不需要复杂的文本国际化处理,禁用它可以省很多事。
-no-glib:禁用 GLib 依赖。GLib 是 GNOME 的基础库,Qt 默认会依赖它,但嵌入式场景下通常不需要。
-no-dbus:禁用 D-Bus。D-Bus 是 Linux 的进程间通信机制,嵌入式场景下往往用不到。
-no-cups:禁用打印支持。板子上不会接打印机。
-no-sql-*:禁用所有 SQL 驱动。如果你不需要数据库功能,全部禁用可以省很多编译时间。注意这里有个坑:如果你后面想用 QtSql 模块,就不能全部禁用,至少要保留一个驱动。
-skip qtwebengine等一系列-skip:跳过不需要的模块。QtWebEngine 是基于 Chromium 的,编译它需要大量内存和时间,而且交叉编译极其麻烦。其他跳过的模块也都是嵌入式场景下用不到的。
-xplatform linux-aarch64-gnu-g++:指定目标平台。这个值对应qtbase/mkspecs/目录下的一个子目录名。Qt 5.14.2 自带了这个 mkspec,但我们需要检查并修改它。
-device-option CROSS_COMPILE=...:指定交叉编译工具链的前缀。这个值会被 mkspec 文件引用。
-sysroot /:指定 sysroot。这里设为/表示使用主机的根文件系统作为 sysroot。严格来说,交叉编译应该用一个包含目标系统库的 sysroot,但因为我们用了-qt-系列参数让 Qt 自带第三方库,所以对 sysroot 的依赖很小。
-v:输出详细日志,方便排查问题。
3.3 mkspec 文件的检查与修改
-xplatform linux-aarch64-gnu-g++对应的 mkspec 目录是qtbase/mkspecs/linux-aarch64-gnu-g++。进入这个目录,检查qmake.conf文件:
cat qtbase/mkspecs/linux-aarch64-gnu-g++/qmake.conf正常情况下应该包含类似这样的内容:
MAKEFILE_GENERATOR = UNIX CONFIG += incremental QMAKE_INCREMENTAL_STYLE = sublib include(../common/linux.conf) include(../common/gcc-base-unix.conf) include(../common/g++-unix.conf) QT_QPA_DEFAULT_PLATFORM = linuxfb QMAKE_CC = $${CROSS_COMPILE}gcc QMAKE_CXX = $${CROSS_COMPILE}g++ QMAKE_LINK = $${CROSS_COMPILE}g++ QMAKE_LINK_SHLIB = $${CROSS_COMPILE}g++ QMAKE_AR = $${CROSS_COMPILE}ar cqs QMAKE_OBJCOPY = $${CROSS_COMPILE}objcopy QMAKE_NM = $${CROSS_COMPILE}nm -P QMAKE_STRIP = $${CROSS_COMPILE}strip load(qt_config)关键点是QMAKE_CC、QMAKE_CXX等变量引用了$${CROSS_COMPILE},这个变量就是我们在 configure 命令行里通过-device-option传进去的。如果这个文件不存在或者内容不对,configure 会失败。
注意:有些 Qt 版本的这个文件里
QT_QPA_DEFAULT_PLATFORM可能设为eglfs,如果你禁用了 eglfs,要改成linuxfb或者你实际使用的平台插件。不过因为我们禁用了所有显示后端,这个值其实影响不大,但最好还是设对。
4. 编译过程中的实际踩坑记录
4.1 configure 阶段的常见报错与处理
报错一:ERROR: Feature 'xcb' was enabled, but the pre-condition 'features.thread && libs.xcb' failed.
这个错误说明 configure 试图启用 xcb 但找不到对应的库。解决方法是在 configure 参数里加-no-xcb。我在上面的配置里已经加了。
报错二:ERROR: Cannot find libz
虽然我们用了-qt-zlib,但 configure 在某些情况下还是会检查系统 zlib。解决方法是确保主机上装了zlib1g-dev:
sudo apt install zlib1g-dev报错三:Project ERROR: Unknown module(s) in QT: serialport
这是热词里出现的问题,很多人遇到。原因是 QtSerialPort 模块被跳过了,但你的项目又依赖它。如果你确实需要串口功能,就不能-skip qtserialport,而且要确保 configure 时没有禁用相关依赖。串口模块本身不依赖太多东西,保留它通常没问题。
报错四:ERROR: The OpenGL functionality tests failed!
即使加了-no-opengl,某些 Qt 版本还是会做 OpenGL 检测。如果遇到这个错误,检查是否安装了libgl1-mesa-dev和libglu1-mesa-dev。如果装了还报错,可以尝试在 configure 参数里加-no-opengl和-no-egl双保险。
4.2 make 阶段的典型错误
configure 通过后,执行make -j$(nproc)开始编译。这个过程在性能尚可的主机上大概需要 30 分钟到 1 小时。期间可能遇到:
错误:fatal error: bits/c++config.h: No such file or directory
这是工具链的 sysroot 配置问题。Linaro 工具链自带了 sysroot,但有时候路径不对。解决方法是找到工具链的 sysroot 目录:
find /opt/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu -name "c++config.h" 2>/dev/null如果找到了,说明 sysroot 存在,可能是 configure 时-sysroot参数设错了。可以尝试把-sysroot /改成工具链的实际 sysroot 路径。
错误:undefined reference to__atomic_fetch_add_8'`
这是链接时缺少原子操作库。解决方法是在 mkspec 的qmake.conf里给QMAKE_LFLAGS加上-latomic:
QMAKE_LFLAGS += -latomic错误:编译到某个模块时内存不足被 kill
Qt 的某些模块(尤其是 QtQml 和 QtQuick)编译时非常吃内存。如果主机内存小于 8GB,建议减少并行编译数:
make -j4而不是-j$(nproc)。虽然慢一点,但不会因为 OOM 中断。
4.3 安装与验证
编译完成后:
make install所有文件会安装到/opt/qt-5.14.2-aarch64-static。验证安装是否成功:
ls /opt/qt-5.14.2-aarch64-static/lib/应该能看到libQt5Core.a、libQt5Gui.a、libQt5Widgets.a等静态库文件。注意扩展名是.a而不是.so,这是静态库的标志。
再检查 qmake:
/opt/qt-5.14.2-aarch64-static/bin/qmake -v应该输出 Qt 5.14.2 的版本信息。但这个 qmake 是 x86 架构的,用来生成 Makefile,真正编译时用的是交叉工具链。
5. 用静态 Qt 编译你的应用程序
5.1 项目配置文件的调整
有了静态 Qt 之后,编译你的应用程序就和普通 Qt 项目差不多,但有几个关键区别。在你的.pro文件里,确保:
QT += core gui widgets CONFIG += staticCONFIG += static会告诉 qmake 链接静态库而不是动态库。
然后用静态 Qt 的 qmake 生成 Makefile:
/opt/qt-5.14.2-aarch64-static/bin/qmake your_project.pro make -j$(nproc)编译完成后,用file命令检查生成的二进制文件:
file your_app应该输出类似ELF 64-bit LSB executable, ARM aarch64的信息,说明是 aarch64 架构的可执行文件。
5.2 静态编译下的插件处理
静态 Qt 有一个特殊问题:平台插件。动态 Qt 下,平台插件(比如libqlinuxfb.so)是运行时加载的,但静态编译后没有.so文件可以加载。解决方法是在代码里显式导入插件:
#include <QtPlugin> Q_IMPORT_PLUGIN(QLinuxFbIntegrationPlugin)或者在.pro文件里加:
QTPLUGIN += qlinuxfb具体用哪个平台插件取决于你的运行环境。如果你的板子有 framebuffer,用qlinuxfb;如果有 EGL,用qeglfs。因为我们 configure 时禁用了这些后端,你可能需要重新编译 Qt 并启用对应的后端。
提示:如果你不确定需要哪个插件,可以在板子上运行程序时加
-platform参数测试,比如./your_app -platform linuxfb。如果报找不到插件,就说明需要重新编译 Qt 启用对应后端。
5.3 部署到 aarch64 开发板
把编译好的可执行文件拷贝到板子上:
scp your_app user@board_ip:/home/user/然后在板子上运行:
chmod +x your_app ./your_app如果程序启动后没有界面,检查以下几点:
- 板子的 framebuffer 设备是否存在:
ls /dev/fb* - 当前用户是否有权限访问 framebuffer:
ls -l /dev/fb0 - 是否需要设置
QT_QPA_PLATFORM环境变量:export QT_QPA_PLATFORM=linuxfb
6. 几个值得单独说的经验点
6.1 关于 Qt 版本的选择
Qt 5.14.2 是一个 LTS 版本,稳定性好,但如果你没有特别的版本要求,Qt 5.15.x 系列在交叉编译方面体验更好,因为社区反馈多,很多坑已经被填了。不过 5.15 的某些版本在许可协议上有变化,商用需要留意。5.14.2 的优势是纯开源协议,用起来没有顾虑。
6.2 关于编译时间的优化
完整编译 Qt 5.14.2 即使跳过了大量模块,在普通 x86 主机上也要 30 分钟以上。如果你想进一步缩短时间,可以考虑:
- 只编译你需要的模块,用
-skip跳过更多 - 使用 ccache 缓存编译结果,第二次编译会快很多
- 在性能更好的机器上编译,比如云主机
ccache 的配置很简单:
sudo apt install ccache export CC="ccache aarch64-linux-gnu-gcc" export CXX="ccache aarch64-linux-gnu-g++"然后在 configure 时确保 qmake 能识别到 ccache。
6.3 静态编译的体积问题
静态编译的代价是二进制文件体积大。一个简单的 Qt Widgets 程序,动态编译可能只有几百 KB,静态编译后可能达到 10MB 以上。如果你的板子存储空间紧张,这一点要提前考虑。可以通过strip命令去掉符号信息来减小体积:
aarch64-linux-gnu-strip your_app这通常能减少 30% 到 50% 的体积。
6.4 关于unknown module(s) in qt: serialport的彻底解决
这个错误在热词里反复出现,说明很多人被它困扰。根本原因是:你的项目.pro文件里写了QT += serialport,但编译 Qt 时跳过了 serialport 模块,或者编译出的 Qt 里没有这个模块。
解决方法有两种:
方法一:重新编译 Qt,不要-skip qtserialport。串口模块依赖不多,保留它不会显著增加编译时间。
方法二:如果你的项目不需要串口,从.pro文件里删掉QT += serialport。
判断是否需要串口模块很简单:看你的代码里有没有#include <QSerialPort>或#include <QSerialPortInfo>。如果没有,就不需要。
6.5 交叉编译工具链的版本匹配
工具链的 GCC 版本和 Qt 版本之间有一个大致的匹配关系。太新的 GCC 编译老版本 Qt 容易出问题,太老的 GCC 可能不支持 Qt 需要的 C++ 特性。根据我的经验:
| Qt 版本 | 推荐 GCC 版本 |
|---|---|
| Qt 5.12 | GCC 6.x - 7.x |
| Qt 5.14 | GCC 7.x - 8.x |
| Qt 5.15 | GCC 8.x - 9.x |
Linaro 7.5.0 对应 Qt 5.14.2 是一个比较稳的组合。如果你用 GCC 9 以上编译 Qt 5.14.2,可能会遇到-Werror导致的编译失败,需要在 configure 时加-no-warnings-are-errors。
7. 完整流程的自动化脚本思路
手动执行这一长串命令容易出错,我习惯把整个流程写成一个 shell 脚本。脚本的核心结构是:
#!/bin/bash set -e # 配置变量 QT_SRC=~/qt-everywhere-src-5.14.2 BUILD_DIR=~/qt-build-5.14.2-aarch64 INSTALL_DIR=/opt/qt-5.14.2-aarch64-static TOOLCHAIN=/opt/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu- # 清理旧构建 rm -rf $BUILD_DIR mkdir -p $BUILD_DIR cd $BUILD_DIR # configure $QT_SRC/configure \ -prefix $INSTALL_DIR \ -static \ -release \ -opensource \ -confirm-license \ -nomake examples \ -nomake tests \ -no-opengl \ -no-xcb \ -qt-zlib \ -qt-libpng \ -qt-libjpeg \ -qt-freetype \ -qt-pcre \ -no-icu \ -no-glib \ -no-dbus \ -no-cups \ -skip qtwebengine \ -skip qtmultimedia \ -skip qtwayland \ -xplatform linux-aarch64-gnu-g++ \ -device-option CROSS_COMPILE=$TOOLCHAIN \ -sysroot / \ -v # 编译 make -j$(nproc) # 安装 make install echo "Qt 5.14.2 aarch64 static build completed."这个脚本的关键是set -e,任何一步失败都会立即停止,避免在错误的基础上继续执行。另外,rm -rf $BUILD_DIR确保每次都是干净构建,避免残留文件导致的奇怪问题。
注意:脚本里的
-skip列表我精简了一些,你可以根据自己的需求增减。但-skip qtwebengine建议保留,因为交叉编译 QtWebEngine 的复杂度远超本文范围。
8. 一些容易被忽略的细节
8.1 主机上的 Qt 工具
编译过程中,Qt 会构建一些在主机上运行的工具,比如moc、uic、rcc。这些工具是 x86 架构的,用来处理你的项目文件。静态 Qt 安装目录下的bin/里会有这些工具,它们和交叉编译工具链是两回事,不要混淆。
8.2 环境变量对编译的影响
如果你之前配置过 Qt 相关的环境变量(比如QTDIR、LD_LIBRARY_PATH),在交叉编译前最好清理一下,避免干扰。我习惯在一个干净的 shell 里做交叉编译,不加载任何 Qt 相关的配置。
8.3 关于-sysroot的深入理解
-sysroot /这个配置在简单场景下能用,但严格来说不够规范。正确的做法是准备一个包含目标系统头文件和库的 sysroot 目录。不过因为我们大量使用了-qt-系列参数让 Qt 自带第三方库,对 sysroot 的依赖降到了最低。如果你的编译过程中频繁报找不到某个系统库,可以考虑构建一个完整的 sysroot。
构建 sysroot 的方法是从目标板子上拷贝/usr/include和/usr/lib到主机的一个目录,然后在 configure 时指向它。但这样做也有风险:板子上的库版本可能和工具链不匹配。所以除非必要,我倾向于用-qt-参数绕过 sysroot 依赖。
8.4 编译日志的保存与分析
Qt 的编译输出非常多,终端里刷屏很快。建议把输出重定向到文件:
make -j$(nproc) 2>&1 | tee build.log这样出错时可以方便地搜索错误信息:
grep -i "error" build.log | head -508.5 关于-no-opengl的取舍
如果你的 Qt 程序用了 QOpenGLWidget 或者 QtQuick 的硬件加速渲染,就不能禁用 OpenGL。但启用 OpenGL 意味着你需要一个支持 OpenGL ES 的 aarch64 图形库,这通常由板子的 GPU 驱动提供。交叉编译时,你需要把板子上的 GPU 库也放到 sysroot 里。这个复杂度比纯软件渲染高不少,如果你的应用对图形性能要求不高,建议先用-no-opengl跑通流程,再根据需求决定是否启用。
9. 从编译到运行:一个最小验证示例
编译完 Qt 之后,最好用一个最小程序验证整条链路是否通畅。我通常用一个简单的 Widgets 程序:
// main.cpp #include <QApplication> #include <QLabel> int main(int argc, char *argv[]) { QApplication app(argc, argv); QLabel label("Hello from aarch64 static Qt!"); label.show(); return app.exec(); }.pro文件:
QT += core gui widgets CONFIG += static TARGET = hello_qt SOURCES += main.cpp编译:
/opt/qt-5.14.2-aarch64-static/bin/qmake hello_qt.pro make检查生成的二进制:
file hello_qt aarch64-linux-gnu-readelf -d hello_qt | grep NEEDED如果file输出显示是 aarch64 架构,并且readelf显示的动态依赖很少(理想情况下只有libc.so.6、libm.so.6、libpthread.so.0等基础库),说明静态编译成功。
把这个hello_qt拷到板子上运行,如果能看到一个显示 "Hello from aarch64 static Qt!" 的窗口,整条链路就通了。
10. 后续扩展与维护建议
静态编译的 Qt 有一个特点:一旦编译好,后续使用很省心,但更新和维护比较麻烦。如果你需要升级 Qt 版本或者添加新的模块,基本上要重新走一遍编译流程。所以我的建议是:
- 把编译脚本和 configure 参数保存好,最好纳入版本控制
- 记录每次编译的环境信息(主机系统版本、工具链版本、Qt 版本)
- 如果多个项目共用同一套静态 Qt,把它安装到一个公共目录,不要每个项目编译一份
另外,如果你的项目需要频繁更新 Qt 版本,可以考虑用动态编译加打包的方式,把依赖库和可执行文件一起打包部署。静态编译更适合版本稳定、部署环境受限的场景。
我在实际使用中发现,静态编译的 Qt 程序在 aarch64 板子上的启动速度比动态链接版本略快,因为省去了加载和重定位动态库的开销。但内存占用会高一些,因为每个进程都有一份完整的 Qt 代码副本。如果你的板子内存紧张,这一点需要权衡。
最后分享一个小技巧:如果你在编译过程中遇到某个模块反复失败,但又确实不需要它,不要犹豫,直接-skip掉。Qt 的模块化设计允许你只编译需要的部分,跳过不需要的模块不仅能节省时间,还能减少出错概率。我这次编译就跳过了二十多个模块,最终编译出来的 Qt 完全满足我的需求。