十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

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

x86主机交叉编译aarch64静态Qt 5.14.2实战指南 1. 为什么要在 x86 主机上折腾 aarch64 静态 Qt很多人第一次接触这个需求场景都差不多手头有一块 ARM 开发板比如 Orange Pi、树莓派或者各种国产核心板系统跑起来了想在上面跑一个自己写的 Qt 界面程序。最直接的做法是在板子上装 Qt 开发环境直接编译但真这么干过的人都知道有多难受——板子性能有限编译 Qt 这种体量的库动辄几个小时甚至直接卡死内存不够还会触发 OOM。所以更合理的路子是在性能强劲的 x86 开发机上用交叉编译工具链把 Qt 编译成 aarch64 架构的库再把编译好的程序拷到板子上运行。那为什么还要强调静态动态链接的交叉编译其实更常见编译出来的可执行文件小但部署时你得把一堆.so文件也拷到板子上还要处理LD_LIBRARY_PATH、库版本冲突、符号找不到这些破事。静态编译则是把 Qt 库直接打进可执行文件里最终产物是一个或少数几个独立文件拷过去就能跑特别适合板子文件系统精简、不方便装一堆运行库的场景。代价就是可执行文件体积会大不少一个简单的窗口程序编译出来几十 MB 是常态。这篇手册面向的是需要从零搭建这套环境的开发者不管你是刚接触交叉编译的新手还是以前编过动态版想转静态版的老手都能照着走一遍。我会把每一步的意图、容易踩的坑、参数为什么这么设都讲清楚而不是甩一堆命令让你复制粘贴。整个流程基于 Qt 5.14.2 源码、aarch64 交叉编译工具链在 Linux 主机上完成最终产出可用的静态 Qt 库和一套能编译出独立可执行文件的 qmake。需要提前说明的是交叉编译 Qt 是个牵一发动全身的活工具链版本、系统 glibc 版本、Qt 配置参数三者之间必须匹配否则会出现各种莫名其妙的链接错误。所以下面的步骤里凡是涉及版本选择的地方我都会解释清楚为什么这么选。2. 动手前的环境盘点与工具链选型2.1 主机环境与目标平台的基本约定先明确两个概念避免后面混淆。主机host是你用来编译的 x86_64 Linux 机器目标target是最终运行程序的 aarch64 设备。交叉编译的本质就是让主机上的编译器生成目标平台能执行的代码所以工具链必须是给 aarch64 用的。主机系统我建议用 Ubuntu 20.04 或 22.04这两个版本的软件源里工具链齐全社区资料也多。如果你用的是 CentOS 7.9 这类老系统要注意它的 glibc 版本偏老2.17编译新版 Qt 时可能会遇到 C17 特性支持不全的问题需要额外升级 GCC。目标平台这边确认你的板子系统架构是 aarch64也叫 arm64可以用uname -m查看输出aarch64就对了。一个非常关键但经常被忽略的点目标板上的 glibc 版本必须不低于工具链自带的 glibc 版本。因为静态链接 Qt 库时libc 部分通常还是动态链接的完全静态链接 libc 会带来一堆问题后面会讲。如果你用了一个 glibc 2.31 的工具链而板子上只有 2.28程序跑起来就会报GLIBC_2.xx not found。所以选工具链之前先在板子上执行ldd --version记下版本号。2.2 aarch64 交叉编译工具链的获取与验证工具链的选择上主流有三条路发行版自带的gcc-aarch64-linux-gnu、Linaro 官方发布的工具链、以及芯片厂商如瑞芯微、全志提供的 SDK 工具链。我的经验是优先用芯片厂商 SDK 里的工具链因为它和目标板的系统库匹配度最高能最大程度避免 glibc 版本问题。如果拿不到厂商工具链退而求其次用发行版的。在 Ubuntu 上装发行版工具链很简单sudo apt update sudo apt install gcc-aarch64-linux-gnu g-aarch64-linux-gnu装完之后验证一下这一步千万别跳过aarch64-linux-gnu-gcc -v aarch64-linux-gnu-gcc -print-sysroot第一条命令会打印出 GCC 版本和配置信息重点看Target: aarch64-linux-gnu这一行确认目标架构正确。第二条命令打印 sysroot 路径这个路径后面配置 Qt 时会用到。如果-print-sysroot输出为空或者指向一个不存在的目录说明工具链的 sysroot 没配好需要手动指定。如果你用的是厂商 SDK 工具链通常是一个压缩包解压后里面有个environment-setup-xxx脚本source一下就会把CC、CXX、PATH这些环境变量设好。这种情况下工具链的前缀可能不是aarch64-linux-gnu-而是类似aarch64-poky-linux-或aarch64-none-linux-gnu-后面配置 Qt 时要相应替换。提示把工具链的bin目录加到PATH里或者用绝对路径调用别依赖当前目录。交叉编译过程中会多次调用工具链路径问题会导致中途失败。2.3 主机侧依赖库的准备编译 Qt 本身需要主机上有一堆工具和库缺一个都可能在 configure 阶段报错。一次性装齐比较省事sudo apt install build-essential perl python3 git \ libgl1-mesa-dev libglu1-mesa-dev \ libxkbcommon-dev libfontconfig1-dev \ libfreetype6-dev libpng-dev libjpeg-dev \ libssl-dev这里解释几个容易漏的。perl和python3是 Qt 构建系统qmake、moc、rcc 等的依赖没有它们 configure 直接失败。libxkbcommon-dev是处理键盘布局的如果你要编译 Qt 的 GUI 模块就必须要。libfontconfig1-dev和libfreetype6-dev关系到字体渲染缺了的话编译出来的程序在板子上可能显示不出文字。注意这些是主机侧的依赖是给编译过程用的不是给目标板用的。目标板上的运行库是另一回事静态编译的好处之一就是目标板不需要装这些开发库。3. Qt 5.14.2 源码的获取与目录规划3.1 源码包下载与校验Qt 5.14.2 的源码包官方提供两种形式单个模块包和qt-everywhere-src全家桶。做静态交叉编译我强烈建议用qt-everywhere-src-5.14.2.tar.xz因为它包含了所有模块配置时可以按需裁剪比一个个下模块包省心得多。下载地址在 Qt 官方下载站的 archive 目录下找到single或archive/qt/5.14/5.14.2/路径即可。下载完成后校验一下完整性避免解压到一半报错sha256sum qt-everywhere-src-5.14.2.tar.xz把输出和官网提供的校验值对比。这一步看起来多余但网络下载中断导致文件损坏的情况真不少见提前发现能省很多时间。解压到一个路径不含中文和空格的目录比如/opt/qt-srcmkdir -p /opt/qt-src tar -xf qt-everywhere-src-5.14.2.tar.xz -C /opt/qt-src cd /opt/qt-src/qt-everywhere-src-5.14.23.2 为什么必须做影子编译out-of-source buildQt 官方明确要求不能在源码目录里直接 configure必须新建一个独立的构建目录。原因有两个一是源码目录里 configure 会生成大量中间文件污染源码树想重新配置时清理不干净二是不同配置比如动态和静态需要不同的构建目录混在一起会互相干扰。所以正确做法是mkdir -p /opt/qt-build-static cd /opt/qt-build-static然后在这个空目录里执行源码目录下的 configure 脚本。这样所有中间产物都在/opt/qt-build-static里源码树保持干净。想换配置重新编译直接删掉构建目录重建即可非常清爽。3.3 安装路径的规划静态编译的 Qt 装到哪里这个要想清楚。我习惯装到一个独立前缀下比如/opt/qt-5.14.2-aarch64-static这样和主机自带的 Qt、其他版本的 Qt 完全隔离不会互相污染。后面用 qmake 编译项目时直接指定这个路径下的 qmake 就行。mkdir -p /opt/qt-5.14.2-aarch64-static这个路径在 configure 时通过-prefix参数指定。注意-prefix指定的是安装路径不是构建路径两者别搞混。4. configure 参数逐项拆解与配置实战4.1 一份可直接用的 configure 命令先给出完整命令然后逐项解释。这份配置是我在多个项目里验证过的编译出的静态 Qt 支持 GUI、网络、数据库等常用模块../qt-everywhere-src-5.14.2/configure \ -prefix /opt/qt-5.14.2-aarch64-static \ -opensource -confirm-license \ -release \ -static \ -nomake examples -nomake tests \ -no-opengl \ -no-xcb \ -no-eglfs \ -linuxfb \ -qt-zlib -qt-libpng -qt-libjpeg -qt-freetype \ -qt-pcre \ -no-iconv \ -skip qtwebengine \ -skip qtdeclarative \ -xplatform linux-aarch64-gnu-g \ -sysroot /usr/aarch64-linux-gnu \ -no-gcc-sysroot这条命令里的每个参数都有讲究下面拆开讲。4.2 平台配置-xplatform 与 mkspec 的关系-xplatform linux-aarch64-gnu-g是交叉编译的核心参数它告诉 Qt 去qtbase/mkspecs/目录下找名为linux-aarch64-gnu-g的 mkspec 文件。mkspec 里定义了编译器路径、编译选项、目标架构等关键信息。Qt 5.14.2 自带了一些 aarch64 的 mkspec但未必和你的工具链完全匹配。如果自带的不能用就需要自己复制一份改。比如从linux-aarch64-gnu-g复制出linux-aarch64-custom-g然后修改里面的qmake.confQMAKE_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如果你的工具链前缀不是aarch64-linux-gnu-就把这里全部替换成你的前缀。这一步是新手最容易出错的地方前缀写错会导致 configure 阶段就报compiler not found。4.3 静态与裁剪-static 和 -skip 的取舍-static开启静态编译这是本篇的核心。但要注意Qt 的静态编译并不是所有模块都能静态化比如qtwebengine依赖 Chromium静态编译极其困难且体积巨大所以直接-skip qtwebengine跳过。qtdeclarativeQML 相关静态编译也有不少坑如果你的项目用纯 Widgets 不用 QML也建议 skip 掉能省大量编译时间。-nomake examples -nomake tests跳过示例和测试代码这两个不跳过的话编译时间会翻倍而且对最终库没影响。-release编译发布版不带调试符号体积更小。如果你需要调试可以改成-debug但静态调试版体积会非常大。4.4 图形后端为什么关掉 opengl 和 xcb目标板通常没有 X11 环境也没有 GPU 加速所以-no-xcb关掉 X11 后端-no-opengl关掉 OpenGL 支持。图形输出用-linuxfb直接操作 framebuffer这是嵌入式 Linux 上最通用的显示方式。如果你的板子支持 EGLFS比如有 GPU 和对应的驱动可以保留-eglfs性能会更好。-qt-zlib -qt-libpng -qt-libjpeg -qt-freetype这几个是让 Qt 使用自带的第三方库而不是依赖系统库。静态编译时强烈建议这么设否则链接阶段会因为找不到目标平台的这些库而失败。-qt-pcre同理用 Qt 自带的 PCRE 正则库。4.5 sysroot 与 -no-gcc-sysroot 的配合-sysroot /usr/aarch64-linux-gnu指定目标平台的根文件系统路径Qt 会从这里找头文件和库。这个路径要和工具链的 sysroot 一致用前面aarch64-linux-gnu-gcc -print-sysroot的输出。-no-gcc-sysroot是个容易让人困惑的参数。它的作用是告诉 Qt 不要给 GCC 传--sysroot选项因为 Qt 自己已经通过-sysroot处理了。如果不加这个参数可能会出现 sysroot 被设置两次导致路径错乱的问题。这两个参数要成对使用。5. 编译过程中的资源控制与常见中断处理5.1 并行编译与内存的平衡配置成功后用make开始编译。Qt 是个大工程单线程编译可能要几个小时所以要用-j并行make -j$(nproc)但这里有个坑-j的值不是越大越好。每个编译进程都要吃内存Qt 的某些模块比如 qtbase 里的 qmake、moc编译时单个进程可能占用 1-2GB 内存。如果你机器只有 8GB 内存-j8很可能触发 OOM编译进程被系统杀掉报错信息还很不明显通常是一堆 Killed 或者莫名其妙的编译错误。我的经验是内存 16GB 用-j432GB 用-j8更保守一点没坏处。宁可慢一点也别中途崩。如果编译过程中发现某个进程被 Killed先dmesg | tail看看是不是 OOM是的话降低并行度重来。5.2 编译中断后的续编Qt 编译时间长中途因为各种原因中断很正常。好消息是make支持续编中断后重新执行make -j4会从上次中断的地方继续不会从头再来。但前提是不要删构建目录也不要改 configure 参数。如果改了参数必须make distclean或者直接删目录重来。如果编译到某个模块反复失败可以先单独编译那个模块定位问题cd qtbase make -j4这样错误信息更集中容易看出是缺依赖还是代码问题。5.3 几个高频报错的定位思路编译 Qt 时最常见的几类错误我整理成表格方便对照报错关键词大概率原因处理方向cannot find -lxxx目标平台缺库或 sysroot 路径不对检查 sysroot确认库在目标路径下GLIBC_2.xx not found工具链 glibc 高于目标板换低版本工具链或升级板子系统undefined reference to __atomic_xxx缺少原子操作库链接时加-latomicKilled内存不足降低-j并行度error: unknown type name xxxC 标准不匹配检查工具链 GCC 版本是否支持 C17undefined reference to __atomic_xxx这个错误在 aarch64 静态编译里特别常见因为 aarch64 的某些原子操作需要 libatomic 支持。解决办法是在 mkspec 的qmake.conf里给QMAKE_LFLAGS加上-latomic或者在项目 pro 文件里加LIBS -latomic。6. 安装、验证与交叉编译一个真实项目6.1 make install 与产物结构编译完成后执行安装make install产物会装到-prefix指定的/opt/qt-5.14.2-aarch64-static下。装完后看一下目录结构ls /opt/qt-5.14.2-aarch64-static你会看到bin、lib、include、mkspecs、plugins等目录。bin里最重要的是qmake这是后面编译项目要用的。lib里是一堆.a静态库文件注意是.a不是.so这是静态编译的标志。验证 qmake 是不是给 aarch64 用的/opt/qt-5.14.2-aarch64-static/bin/qmake -query输出里QT_HOST_PREFIX是主机路径QT_INSTALL_PREFIX是目标路径QMAKE_XSPEC应该是你配置的 mkspec 名字。如果这些对不上说明安装有问题。6.2 用 qmake 编译一个最小 Qt 程序写一个最简单的测试程序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(); }项目文件test.proQT widgets TARGET test SOURCES main.cpp用交叉编译版的 qmake 生成 Makefile 并编译/opt/qt-5.14.2-aarch64-static/bin/qmake test.pro make -j4编译完成后用file命令检查产物架构file test输出里应该包含ARM aarch64字样如果是x86-64说明用错了 qmake。再用ldd test看看依赖静态编译的程序应该只依赖少数几个系统库libc、libm、libpthread 等不会有一堆 Qt 的.so。6.3 部署到目标板与运行验证把test拷到板子上用 scp、U 盘或者网络传输都行然后直接运行./test如果板子有 framebuffer 和屏幕应该能看到一个显示文字的窗口。如果报错常见的有两类一是GLIBC_2.xx not found说明工具链 glibc 版本高于板子需要换工具链二是cannot open /dev/fb0说明 framebuffer 设备权限或路径问题检查/dev/fb0是否存在、当前用户是否有权限。注意静态编译的程序体积大传输时如果板子存储空间紧张可以先aarch64-linux-gnu-strip test去掉符号表体积能减小一半以上。7. 静态交叉编译里那些文档不会写的经验7.1 插件加载是静态编译最大的暗坑静态编译 Qt 后很多人会遇到图片显示不出来、数据库驱动找不到的问题。原因是 Qt 的插件机制在静态编译下不会自动加载需要手动导入。比如你要用 PNG 图片得在代码里加#include QtPlugin Q_IMPORT_PLUGIN(QPngPlugin)数据库驱动同理用 SQLite 就要Q_IMPORT_PLUGIN(QSQLiteDriverPlugin)。这个坑非常隐蔽因为动态编译时插件自动加载静态编译时不会程序能编译能运行就是功能不正常。解决办法是在 pro 文件里显式链接插件库QTPLUGIN qpng qsqlite或者在代码里用Q_IMPORT_PLUGIN宏。我建议两者结合pro 文件里声明代码里导入双保险。7.2 字体和本地化数据的处理静态编译的 Qt 默认不包含字体程序在板子上可能显示方块或者空白。解决办法是把字体文件比如文泉驿、思源黑体打包进程序用QFontDatabase::addApplicationFont加载或者用 Qt 的资源系统qrc把字体嵌进去。本地化翻译文件.qm同理静态编译时不会自动加载需要手动QTranslator::load并指定资源路径。7.3 编译时间的现实预期与优化在一台 8 核 16GB 的机器上完整编译 Qt 5.14.2 静态版跳过 webengine 和 declarative大概需要 40 分钟到 1 小时。如果全模块编译时间会翻好几倍。想缩短时间除了 skip 不用的模块还可以用-no-feature-xxx关掉不需要的特性比如-no-feature-printer关掉打印支持用 ccache 缓存编译结果重复编译时能省大量时间把构建目录放在 SSD 上IO 速度对编译影响很大ccache 的配置很简单装好后在 configure 前设置export CCccache aarch64-linux-gnu-gcc export CXXccache aarch64-linux-gnu-g这样重复编译同一个文件时会直接命中缓存第二次编译快很多。7.4 版本匹配的连锁反应最后强调一个贯穿全程的原则工具链、Qt 版本、目标板系统三者必须版本匹配。我见过太多案例工具链用最新的Qt 用 5.14.2板子系统是两年前的老版本结果编译出来的程序在板子上跑不起来。正确的做法是先确定板子的 glibc 版本和内核版本再选不高于这个版本的工具链最后选 Qt 版本。Qt 5.14.2 对 GCC 的要求是 5.0 以上对 glibc 没有硬性下限但实际使用中建议工具链 glibc 不高于板子。如果实在拿不准最稳妥的办法是用板子厂商提供的 SDK 工具链因为那是厂商针对自家系统验证过的组合。虽然可能版本旧一点但兼容性有保障能省掉大量排查时间。
返回列表