尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

aarch64 Qt 5.14.2 静态交叉编译实战指南

aarch64 Qt 5.14.2 静态交叉编译实战指南 1. 为什么要在 aarch64 上折腾 Qt 静态交叉编译1.1 这个项目到底解决什么问题把 Qt 程序搬到 ARM 开发板上跑很多人第一反应是在板子上直接装 Qt 再编译。这条路在 x86 上很舒服到了 aarch64 板子上就变味了板子 CPU 弱、内存小、存储慢编译一次 Qt 源码动辄几个小时甚至直接 OOM 挂掉。更麻烦的是板子上的系统环境往往不完整缺头文件、缺 pkg-config、缺各种 dev 包装依赖本身就是一场拉锯战。静态交叉编译的思路是把编译这件事挪回性能充足的 x86_64 开发机上用一套针对 aarch64 的交叉工具链把 Qt 库和你的应用一起编译成静态链接的可执行文件最后只把一个二进制文件丢到板子上运行。这样做有几个直接好处目标机上不需要再装 Qt 运行库省掉一大堆 .so 依赖部署就是拷贝一个文件没有动态库版本对不上的问题程序启动时不用再去加载和重定位一堆共享库冷启动会快一些。这个手册适合谁看手上有一块 aarch64 开发板比如 Orange Pi、树莓派 64 位系统、各种国产 ARM 核心板需要在上面跑 Qt 界面程序但不想在板子上装完整 Qt 开发环境的嵌入式开发者。也适合那些被cannot mix incompatible qt library或者unknown module(s) in qt: serialport这类问题折磨过、想彻底把依赖问题一次性解决掉的人。1.2 静态编译和动态编译的本质区别动态编译时你的可执行文件里只存了函数调用的“地址占位”真正的代码在运行时从 .so 文件里加载。静态编译则是在链接阶段就把用到的库代码整段复制进可执行文件。这就像搬家动态编译是把你家家具的清单给搬家公司到了新家再按清单去仓库取静态编译是直接把所有家具塞进一辆卡车开到哪都能立刻用。代价也很明显。静态链接的二进制体积会大很多一个简单的 Qt Widgets 程序动态链接可能几百 KB静态链接轻松上到十几 MB甚至几十 MB。另外静态链接对库的编译方式有要求必须用-fPIC编译出位置无关代码否则链接时会报重定位错误。还有一点容易被忽略静态 Qt 默认不带插件像 platform 插件eglfs、linuxfb、图片格式插件jpeg、png都需要显式配置进去否则程序跑起来会提示找不到平台插件。1.3 整体方案选型与工具链确定工具链的选择直接决定后面顺不顺。aarch64 交叉编译常用的有 Linaro 的 gcc-arm 系列、ARM 官方工具链、以及各家芯片厂商自带的 SDK 工具链。我一般优先用 Linaro 或 ARM 官方发布的aarch64-linux-gnu工具链原因是版本稳定、社区资料多、glibc 版本可控。如果你的板子系统是 CentOS 7.9 aarch64 这类偏老的发行版就要特别注意工具链的 glibc 版本不能高于目标系统的 glibc否则链接出来的程序在板子上会报GLIBC_2.xx not found。Qt 版本这里选 5.14.2是个 LTS 性质的版本模块齐全、bug 相对少社区里关于它的交叉编译资料也最丰富。虽然更新的 5.15.x 也有人用但 5.15 后期版本对某些老工具链的兼容性反而更挑剔。5.14.2 的源码包结构清晰configure 参数也比较好理解适合作为从零搭建的起点。2. 环境准备与工具链落地2.1 开发机基础环境搭建开发机建议用 Ubuntu 20.04 或 22.04 的 x86_64 系统虚拟机或物理机都行。先装一批基础依赖这些是编译 Qt 源码绕不开的sudo apt update sudo apt install -y build-essential perl python3 git \ bison flex gperf libicu-dev libssl-dev \ libxcb1-dev libx11-xcb-dev libxcb-*dev \ pkg-config cmake ninja-build这里解释几个关键依赖的作用。bison、flex、gperf是 Qt 源码里解析器和哈希表生成工具需要的缺了会在 configure 阶段直接报错。libicu-dev是 Qt Core 里文本编码处理用的不装的话 configure 会提示 ICU 相关功能被禁用。libssl-dev关系到网络模块的 HTTPS 支持。libxcb系列是给 x86 本机编译用的交叉编译到 aarch64 时其实用不到但如果你同时想在本机验证 Qt 配置装上会省事。提示不要图省事用apt install qt5-default之类的包那装的是 x86 动态库和交叉编译完全是两码事混在一起反而会污染环境变量。2.2 交叉工具链的获取与验证去 Linaro 或 ARM 官方发布页下载gcc-arm-*-x86_64-aarch64-linux-gnu这类压缩包解压到一个固定目录比如/opt/toolchain/aarch64。然后把它加进 PATHexport TOOLCHAIN/opt/toolchain/aarch64 export PATH$TOOLCHAIN/bin:$PATH验证工具链是否可用直接看版本aarch64-linux-gnu-gcc --version aarch64-linux-gnu-g --version能正常打印版本号就说明工具链本身没问题。接下来要确认目标系统的 glibc 版本这一步很多人会跳过结果后面踩大坑。在板子上执行ldd --version记下这个版本号比如 2.28。然后查工具链自带的 glibc 版本aarch64-linux-gnu-gcc -print-file-namelibc.so.6 strings $TOOLCHAIN/aarch64-linux-gnu/libc/lib/libc.so.6 | grep GLIBC_2如果工具链的 glibc 版本高于板子的就得换一个更老的工具链或者用板子系统对应的 sysroot。这是静态交叉编译里最容易翻车的地方务必先确认。2.3 sysroot 的准备与作用sysroot 简单说就是“目标系统的根目录快照”里面放着目标机的头文件和库。交叉编译时编译器会去 sysroot 里找stdio.h、libc.so这些目标平台的文件而不是用开发机的。工具链通常自带一个默认 sysroot但如果你板子上的系统比较特殊比如 CentOS 7.9 aarch64最好把板子上的/usr/include和/usr/lib拷一份到开发机作为自定义 sysroot。# 在板子上打包 tar czf sysroot.tar.gz /usr/include /usr/lib /lib # 拷回开发机解压 mkdir -p /opt/sysroot/aarch64 tar xzf sysroot.tar.gz -C /opt/sysroot/aarch64用自定义 sysroot 时configure 里要加-sysroot /opt/sysroot/aarch64。好处是编译出来的程序链接的库版本和板子完全一致坏处是如果 sysroot 里缺某些开发头文件编译会失败需要手动补。我一般先用工具链自带 sysroot 试出问题再换自定义的。3. Qt 5.14.2 源码配置与静态编译3.1 源码获取与目录规划从 Qt 官方归档站下载qt-everywhere-src-5.14.2.tar.xz。解压后建议单独建一个构建目录不要直接在源码目录里 configure这样出问题可以删掉构建目录重来源码保持干净。tar xf qt-everywhere-src-5.14.2.tar.xz mkdir qt-build-aarch64 cd qt-build-aarch643.2 configure 参数逐项拆解configure 是整个流程的核心参数写错一个后面全崩。下面是我实测可用的一套配置逐项说明../qt-everywhere-src-5.14.2/configure \ -prefix /opt/qt5.14.2-aarch64-static \ -opensource -confirm-license \ -release -static -static-runtime \ -nomake examples -nomake tests \ -no-opengl \ -no-xcb \ -no-feature-opengl \ -qt-zlib -qt-libpng -qt-libjpeg \ -qt-freetype -qt-pcre \ -skip qtwebengine -skip qtdeclarative \ -xplatform linux-aarch64-gnu-g \ -sysroot /opt/sysroot/aarch64 \ -device-option CROSS_COMPILEaarch64-linux-gnu- \ -no-glib -no-icu \ -no-cups -no-pch \ -I /opt/sysroot/aarch64/usr/include \ -L /opt/sysroot/aarch64/usr/lib-prefix指定安装目录编译完make install会装到这里后面写应用时 qmake 就从这个目录找。-static和-static-runtime是静态编译的关键前者让 Qt 库本身静态化后者让运行时也静态链接。-nomake examples -nomake tests能省掉大量编译时间除非你要跑 Qt 自带测试否则没必要编。-no-opengl和-no-xcb是针对无桌面环境的嵌入式板子。如果你的板子跑的是纯 framebuffer 或者 eglfs不需要 X11 和 OpenGL关掉能减少依赖和体积。-qt-zlib -qt-libpng -qt-libjpeg表示用 Qt 自带的第三方库源码编译而不是依赖系统里的这样能避免交叉编译时找不到目标平台的 zlib 库。-xplatform linux-aarch64-gnu-g指定使用 Qt 源码里mkspecs/linux-aarch64-gnu-g这个平台配置。这个目录里有个qmake.conf里面定义了交叉编译器的名字。如果工具链前缀不是aarch64-linux-gnu-需要改这个文件里的QMAKE_CC、QMAKE_CXX等变量。-skip qtwebengine -skip qtdeclarative是跳过两个重量级模块。qtwebengine 基于 Chromium交叉编译极其痛苦除非必须用浏览器控件否则直接跳过。qtdeclarative 是 QML 相关如果你只用 Widgets也可以跳过能省不少时间。3.3 平台 mkspec 的定制Qt 源码里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) 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_OBJCOPY aarch64-linux-gnu-objcopy QMAKE_STRIP aarch64-linux-gnu-strip load(qt_config)如果你的工具链前缀不同改这里的aarch64-linux-gnu-为你的前缀即可。另外可以在这里加上QMAKE_CFLAGS -fPIC和QMAKE_CXXFLAGS -fPIC确保所有目标文件都是位置无关的静态链接时不会报重定位错误。3.4 编译与安装过程configure 通过后会生成 Makefile接下来就是漫长的编译make -j$(nproc) make install-j$(nproc)用满所有 CPU 核心能显著缩短时间。在 8 核机器上跳过 webengine 和 declarative 后整个编译大概 40 分钟到 1 小时。如果中途报错先看错误信息里是哪个模块常见的是某个第三方库找不到头文件补上对应依赖再继续。编译完成后/opt/qt5.14.2-aarch64-static下会有bin、lib、include等目录。bin里的qmake是 x86 可执行文件但它的配置指向 aarch64 的库用它来生成应用的 Makefile 就能交叉编译。4. 应用侧交叉编译与部署实操4.1 用 qmake 编译一个静态 Qt 程序写一个最简单的 Widgets 程序验证整条链路// main.cpp #include QApplication #include QLabel int main(int argc, char *argv[]) { QApplication app(argc, argv); QLabel label(Hello aarch64 static Qt); label.show(); return app.exec(); }工程文件demo.proQT widgets TARGET demo SOURCES main.cpp用交叉编译版 qmake 生成 Makefile/opt/qt5.14.2-aarch64-static/bin/qmake demo.pro make -j$(nproc)编译出来的demo用file命令确认架构file demo # 应显示 ELF 64-bit LSB executable, ARM aarch64再用ldd demo看依赖静态链接的话应该显示not a dynamic executable或者只依赖少数系统库。如果还依赖一堆 Qt 的 .so说明静态链接没生效回去检查 configure 里的-static是否真的生效了。4.2 平台插件的处理静态 Qt 最大的坑就是插件。动态 Qt 会在运行时从plugins/platforms/目录加载libqeglfs.so或libqlinuxfb.so静态编译时这些插件必须显式链接进程序。做法是在 .pro 里加QTPLUGIN qeglfs qlinuxfb或者在 main 函数开头用Q_IMPORT_PLUGIN(QEglFSIntegration)宏手动导入。如果程序跑起来报This application failed to start because no Qt platform plugin could be initialized基本就是插件没链进去。图片格式插件同理需要 jpeg、png 支持就加QTPLUGIN qjpeg qpng。字体方面静态 Qt 默认可能不带字体引擎如果界面文字显示成方块需要把 freetype 和字体文件一起处理。4.3 部署到板子与运行验证把编译好的demo拷到板子上直接运行scp demo rootboard-ip:/root/ ssh rootboard-ip ./demo -platform linuxfb-platform linuxfb指定用 framebuffer 平台插件。如果是 eglfs 环境就用-platform eglfs。运行前确认板子的 framebuffer 设备存在/dev/fb0权限也要对。如果提示找不到字体可以设置QT_QPA_FONTDIR指向字体目录。5. 常见问题与排查速查5.1 编译期典型报错报错信息原因解决unknown module(s) in qt: serialport该模块未编译或未安装configure 时不要 skip serialport或单独编译该模块cannot mix incompatible qt library混用了不同版本的 Qt 库清理环境变量确保只用交叉编译版 qmakerelocation R_AARCH64_ADR_PREL_PG_HI21目标文件未用 -fPIC在 mkspec 里加 -fPIC 重新编译GLIBC_2.xx not found工具链 glibc 高于目标系统换更老的工具链或用目标 sysrootcannot find -lGL缺少 OpenGL 库configure 加 -no-opengl 或补目标平台 GL 库5.2 运行期典型报错程序在板子上跑不起来先看报错关键词。no Qt platform plugin是插件没链进去libstdc.so.6: version not found是 C 运行库版本不匹配需要在 configure 里加-static-runtime把 libstdc 也静态链接QFontDatabase: Cannot find font directory是字体路径问题设置QT_QPA_FONTDIR即可。5.3 我踩过的几个坑第一个坑是 sysroot 不匹配。一开始图省事用工具链自带 sysroot结果板子上跑起来报 glibc 版本问题折腾半天才发现工具链的 glibc 比板子新。后来老老实实把板子的/usr/include和/usr/lib拷过来做自定义 sysroot 才解决。第二个坑是插件遗漏。程序编译链接都过了拷到板子上就是黑屏日志里一行no Qt platform plugin could be initialized。查了半天才想起来静态 Qt 不会自动带插件得在 .pro 里显式QTPLUGIN 。第三个坑是编译时间。第一次没加-skip qtwebengine编译到 webengine 直接卡死内存爆了。后来学乖了先把不需要的模块全 skip 掉编译时间从几小时降到一小时以内。提示每次改完 configure 参数最好把构建目录整个删掉重新来残留的缓存文件会导致奇怪的链接错误。6. 几个能省时间的经验补充工具链和 sysroot 的版本匹配是整件事的地基地基没打好后面全是返工。我的习惯是拿到板子第一件事就是ldd --version和uname -a把目标环境信息记下来再去选工具链。configure 参数不要一次写全先跑一个最小配置确认工具链能用再逐步加模块。这样出问题容易定位是哪个参数导致的。config.summary文件在 configure 结束后会生成里面列出了哪些功能启用、哪些禁用编译前扫一眼能提前发现配置错误。静态 Qt 的体积确实大但可以用aarch64-linux-gnu-strip把调试符号剥掉能减掉不少。如果对体积特别敏感还可以在 configure 里加-no-feature-*关掉用不到的功能比如-no-feature-printdialog、-no-feature-sql之类按需裁剪。最后说个实际体会静态交叉编译这套流程第一次搭会花不少时间但一旦跑通后面所有 Qt 应用都能复用这套工具链和 Qt 库边际成本几乎为零。比起每次在板子上现装现编长期看省下的时间非常可观。
返回列表