ARTICLE DETAIL

资讯详情

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

aarch64 Qt静态交叉编译实战:从工具链搭建到独立部署

aarch64 Qt静态交叉编译实战:从工具链搭建到独立部署 手上有一块aarch64的Linux开发板内存小、flash也紧张传统做法是把整个Qt运行库一起拷进去再配一堆环境变量。真到交付现场尤其是离线环境客户不会给你机会现场编译、现场调试最省心的就是把整个程序静态链接成一个独立可执行文件拷过去就能跑。我在实际项目里用Qt 5.14.2做aarch64静态交叉编译从工具链搭建到最终部署踩了不少坑整理成这份完整手册送给准备动手做嵌入式Qt交付的兄弟们。这篇内容适合四类人看正在给ARM64板子适配Qt应用的嵌入式Linux开发、做HMI人机界面但不想在目标机上装Qt环境的桌面应用开发者、以及刚接触交叉编译还分不清sysroot和qmake的入门者。如果你只是想在x86主机上调试Qt这篇帮不到你。1. 为什么偏偏是aarch64静态交叉编译1.1 项目场景设备交付与部署痛点很多嵌入式项目并不是“开发完就算了”而是要交付到客户现场。现场可能没有网络、没有源码、没有编译工具链甚至没有多余的磁盘空间。动态编译方案下你需要把libQt5Core.so.5、libQt5Gui.so.5等一大堆共享库连同平台插件、字体和翻译文件一起打包这些库加起来轻松超过100MB部署脚本还得小心处理LD_LIBRARY_PATH稍不留神就出现“找不到libQt5Core.so.5”的经典报错。静态交叉编译方案则是另一条路在x86主机上用aarch64交叉编译器把Qt自身编译成静态库再把业务程序与Qt静态库链成一个独立的ARM64可执行文件。最终交付物就是一个ELF文件拷进目标板就能运行不再依赖目标板的Qt环境。对于批量设备、离线交付、现场升级这种方式的维护成本低得多。1.2 静态链接到底省了什么静态链接不是“省代码”而是“省依赖管理”。动态方案下Qt库与应用程序是两个生命周期库升级、插件更新都要同步管理静态方案下Qt代码直接成为你程序的一部分运行时不再依赖Qt库文件和插件目录。举一个我实际项目的数字动态方案部署到目标板的Qt运行环境约180MB包含Qt库、平台插件、icu数据、qtwebengine的一部分静态方案下只考虑程序本体一个包含基本Widgets功能的程序大约15~25MB如果再加-Wl,--gc-sections裁剪还能再压一压。体积不是唯一优势调试和版本管理明显更轻松。1.3 先搞懂这四个基础概念动手之前把四个高频词吃透交叉编译在x86_64架构主机上编译出aarch64架构的程序。编译器本身运行在x86上生成的指令集却是ARM64。静态库与动态库静态库在链接阶段被复制进可执行文件后缀为.a动态库在程序运行时才加载后缀为.so。Qt 5.14.2静态交叉编译就是让configure产出ARM64的.a文件最终链接进你的应用。sysroot交叉编译器的“目标系统根目录”里面放着头文件、libc、libgcc以及目标板需要的其他系统库。简单理解为目标板上/usr/include和/usr/lib的镜像。mkspecQt为每种编译平台预置的配置文件决定编译器名称、编译参数、链接参数。aarch64对应的mkspec通常是linux-aarch64-gnu-g。这四个概念贯穿整个编译过程后面每一步都会跟它们打交道。2. 从零搭建交叉编译环境2.1 宿主机选择与系统准备宿主机我推荐Ubuntu 20.04 LTS64位x86_64系统。为什么首选它因为Ubuntu官方源里自带aarch64交叉工具链不用花时间去找第三方工具链后续安装目标架构库也方便。Qt源码的依赖解析、configure脚本在Ubuntu上被验证的次数最多踩坑的概率最低。系统准备阶段先做两件事。第一件是更新软件源sudo apt update sudo apt upgrade -y第二件是为当前环境增加arm64架构支持sudo dpkg --add-architecture arm64 sudo apt update这一步不是必须的但如果希望从软件源直接安装目标板的开源依赖库比如zlib、openssl加上了就能用libssl-dev:arm64这种形式安装ARM64版本非常方便。2.2 安装aarch64交叉编译工具链工具链就三行命令sudo apt install -y gcc-aarch64-linux-gnu g-aarch64-linux-gnu sudo apt install -y build-essential sudo apt install -y cmake ninja-build # 如果你后面还要编Qt之外的第三方库装完先看版本aarch64-linux-gnu-gcc --version正常会输出gcc 9.x这是Ubuntu 20.04自带的版本。Qt 5.14.2的代码针对gcc 5~10都做过充分验证9.x非常合适不新不旧足够稳定。2.3 准备sysroot与目标依赖库交叉编译最容易被忽略的就是sysroot。Ubuntu的交叉工具链自带一套基础sysroot路径是/usr/aarch64-linux-gnu里面已经有ARM64的libc、头文件和基本的共享/静态库。为了让Qt配置阶段能找到目标架构的系统库我会显式声明sysroot避免编译器默认去抓主机的x86头文件。为了支持源里能直接装的ARM64开发库建议一次性安装常用依赖按需取舍sudo apt install -y libc6-dev-arm64-cross zlib1g-dev:arm64 \ libssl-dev:arm64 libpng-dev:arm64 libjpeg-dev:arm64 \ libfontconfig1-dev:arm64 libfreetype6-dev:arm64注意装这些库是给“功能模块”用的不是强制要求。如果你和我的做法一样把Qt的图像、字体模块统统用Qt自带源码-qt-zlib、-qt-libpng等来编上面大部分依赖都可以不装。我推荐的策略是先装libc和zlib基础件其他的等configure报错再按提示补。2.4 验证工具链是否可用写一个最基础的hello程序交叉编译并确认输出文件类型cat hello.c EOF #include stdio.h int main() { printf(hello aarch64\n); return 0; } EOF aarch64-linux-gnu-gcc hello.c -o hello file hello输出会包含ELF 64-bit LSB executable, ARM aarch64这就说明工具链工作正常。如果只是x86-64说明你调用了宿主机gcc赶紧检查PATH和编译器名字。3. 获取并准备Qt 5.14.2源码3.1 源码获取方式与版本选择Qt官方提供了源码包也可以从Qt的git仓库直接拉取。我建议下载qt-everywhere-src-5.14.2.tar.xz完整包因为它是同一时间点多个模块的聚合版本避免各模块版本不一致导致编译问题。版本为什么选5.14.25.14不是LTS版本但它在嵌入式领域的口碑积累足够深既有新架构的改进又有相对稳定的配置接口。而且5.14以后交叉编译相关的mkspec和configure schema已经定型你搜到的大多数问答也集中在5.12到5.15之间。相比之下5.15虽然也是LTS但我遇到过部分第三方库API调整造成的兼容差异所以最终采用了标题对应的5.14.2。下载完成后建议核对sha256码避免下载损坏sha256sum qt-everywhere-src-5.14.2.tar.xz3.2 源码目录结构提前理清解压后你会看到若干子目录其中核心是qtbase交叉编译的mkspec、configure、qmake都在这里。qtsvg、qtimageformats、qttranslations是常用附属模块。我这里只编译qtbase加少量模块没有编整个qt-everywhere。原因是full包里的webengine、webview等大模块在aarch64静态场景下非常痛苦既需要额外的系统库编译时间又长达数小时而大多数嵌入式程序根本用不到它们。后续有需要再单独补编译对应模块性价比最高。3.3 目录规划避免root权限坑我把源码放在/opt/qt-src安装目录放在/opt/qt5.14.2-aarch64-static。这两个目录都需要写权限建议把目录owner改成当前用户sudo mkdir -p /opt/qt-src sudo chown $USER:$USER /opt/qt-src mkdir -p /opt/qt5.14.2-aarch64-static手工指定前缀目录而不是默认/usr/local一方面避免污染系统另一方面后续要清理或复制到其他构建机时直接挪整个目录即可。解压命令cd /opt/qt-src tar -xJf qt-everywhere-src-5.14.2.tar.xz cd qt-everywhere-src-5.14.24. configure参数逐个解读configure这一步是整个流程的胜负手参数决定Qt的裁剪范围和链接方式。千万不要直接复制网上的命令要先搞清每个参数的含义。4.1 必须设置的参数./configure \ -prefix /opt/qt5.14.2-aarch64-static \ -opensource \ -confirm-license \ -release \ -static \ -xplatform linux-aarch64-gnu-g \ -sysroot /usr/aarch64-linux-gnu \ -no-opengl \ -no-dbus \ -no-xcb \ -no-icu \ -no-cups \ -linuxfb \ -evdev \ -nomake examples \ -nomake tests逐个解释-prefix静态Qt库的安装路径后面所有工具都会装到这里。-opensource -confirm-license确认开源许可证避免交互卡住。-release只编译release版省时间也符合交付需求。-static核心参数告诉Qt构建静态库。如果你的configure之后生成的是动态库这一项可能被某些模块覆盖后面会有排查方法。-xplatform linux-aarch64-gnu-g这是Qt的交叉编译平台名称。它告诉Qt使用aarch64 mkspec从而调用aarch64-linux-gnu-g编译器。-sysroot /usr/aarch64-linux-gnu指定目标系统根目录让编译器在寻找头文件、库时聚焦到ARM64的sysroot而不是宿主机。-no-opengl嵌入式环境很难配通用OpenGL静态链接更麻烦直接关掉。-no-dbus没有dbus守护进程需求时关掉可少一组外部依赖。-no-xcb如果你不是给完整的桌面X11环境开发这个选项能省掉X11相关的一大堆依赖。-no-icuICU库很大静态链接时会让体积猛增。关闭它Qt使用自带的字符串处理就能满足大部分应用。-no-cups打印支持一般用不到关掉。-linuxfb启用Linux帧缓冲平台插件目标板没有显示服务时可以直接操作/dev/fb0进行图形输出。-evdev启用evdev输入插件配合linuxfb可以从触摸屏、鼠标设备读取输入。-nomake examples -nomake tests不编译示例和测试大幅缩短编译时间。4.2 如果目标板需要X11或OpenGL当你的目标板其实跑了一个轻量级X11服务器或者业务上必须用QOpenGLWidget时上面的参数要改去掉-no-xcb改为-xcb并安装对应的ARM64开发库。去掉-no-opengl改为-opengl es2或根据GPU驱动选择EGL/GL支持。还要安装libxkbcommon-dev:arm64、libxcb-*系列库。实话实说带X11和OpenGL的静态交叉编译依赖链条会明显变长。假如不是硬性需求我建议先以linuxfb的最小配置跑通流程再逐步开功能。4.3 编译前的临时环境变量执行configure之前我习惯把交叉工具链路径加到PATH避免Qt的mkspec找不到编译器export PATH/usr/bin:$PATH export CROSS_COMPILEaarch64-linux-gnu-由于Ubuntu自带的aarch64工具链就在/usr/bin下通常什么都不用额外设置。如果你用了第三方工具链比如ARM官方GNU工具链需要在mkspec或configure命令中显式指定编译器路径。4.4 configure完成后的确认configure结束后不要急着make先在终端里敲grep -E **(configure|features)** config.summary正常情况下你会看到Build type: linux-g (aarch64, CPUFeatures: ...)类似的输出以及Using static linking字样。再检查qtbase/mkspecs/qmodule.pri里是否定义了QT_CONFIG static。如果这两处都正常再开始编译。另外configure过程中会打印很多warning比如“Package glib2 was not found”不用紧张。Qt会给出它是否决定降级使用内置实现。只要最终config.summary里关键项没问题编译就能推进。真正致命的是error级别的提示看到error就要停下来补库或改参数。5. 编译、安装、验证aarch64静态Qt库5.1 编译过程中的进度观察配置完成后在qtbase目录下执行cd /opt/qt-src/qt-everywhere-src-5.14.2/qtbase make -j$(nproc)-j$(nproc)能自动用满CPU线程但机器内存小于8GB时建议改成-j4或-j2防止内存耗尽导致编译器被杀。编译日志建议用make -j$(nproc) 21 | tee build.log留底后面排查问题非常有用。Qt静态编译的耗时一般比动态编译长15%到30%我在这台Xeon主机上编qtbase大概花了50分钟正常范围内。中间如果某个模块报错不要整体重来先看具体是哪个源文件、哪个库。绝大多数是依赖缺失补上对应系统的ARM64库后重新configure再继续。5.2 安装到自定义前缀编译成功后make install安装产物会进入/opt/qt5.14.2-aarch64-static。检查目录结构ls /opt/qt5.14.2-aarch64-static ls /opt/qt5.14.2-aarch64-static/lib | head静态库的Qt在lib目录下应该看到大量.a文件libQt5Core.a、libQt5Gui.a、libQt5Widgets.a等。看到.a就放心了一大半。5.3 验证库属性file与readelf用file命令验证静态库的架构file /opt/qt5.14.2-aarch64-static/lib/libQt5Core.a要确认它确实是ARM aarch64架构的归档文件。然后用交叉版readelf看一个Qt自带的可执行工具比如aarch64-linux-gnu-readelf -h /opt/qt5.14.2-aarch64-static/bin/qmake确认这个qmake是ARM64架构同时它又能跑在x86主机上。这里有个容易混淆的点Qt交叉编译会生成一个可以在宿主机上运行的qmake用于生成Makefile然后这个qmake的“目标平台”由mkspec决定。所以你看到file qmake显示aarch64也没错它本身是构建期工具但会用交叉编译器去生成目标平台的Makefile。实际操作中我一般只关注它能不能正常打印qmake -query/opt/qt5.14.2-aarch64-static/bin/qmake -query只要它能输出Qt安装路径和host信息说明qmake自身可运行生成Makefile时也会自动把编译器切到aarch64工具链。5.4 配置开发环境变量编译完Qt库为了让命令行直接使用这套工具我会在~/.bashrc里追加export PATH/opt/qt5.14.2-aarch64-static/bin:$PATH export QTDIR/opt/qt5.14.2-aarch64-static export QT_PLUGIN_PATH$QTDIR/plugins简单解释PATH是让你能直接敲qmakeQTDIR给一些旧的构建脚本查路径QT_PLUGIN_PATH在静态链接时多数用不上但留着没坏处。设置完source ~/.bashrc再运行qmake -v确认生效。6. 用交叉qmake构建一个完整示例项目6.1 工程文件和示例代码创建一个简单但完整的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.resize(320, 120); label.show(); return app.exec(); }再写hello.proQT core gui widgets TARGET hello_qt TEMPLATE app CONFIG static SOURCES main.cppCONFIG static是给qmake提示链接时不要使用动态Qt。正常情况下如果Qt本身是静态库qmake会自动处理但显示声明能避免某些自定义配置的干扰。6.2 编译得到ARM64可执行文件执行/opt/qt5.14.2-aarch64-static/bin/qmake hello.pro make过程中你应该看到gcc编译命令里出现aarch64-linux-gnu-g链接命令带了一长串以libQt5Widgets.a结尾的静态库列表。如果链接命令里出现-lQt5Widgets说明它走的是动态库路径需要检查CONFIG static和Qt配置。编译完成后file hello_qt输出应该包含ELF 64-bit LSB executable, ARM aarch64。6.3 检查动态依赖确保没有Qt运行时依赖静态链接不是说可执行文件完全没有动态依赖系统基础库libc、libstdc、libm、libpthread还是会动态依赖。我们需要确认的是没有任何libQt5*依赖。用交叉readelf检查aarch64-linux-gnu-readelf -d hello_qt | grep NEEDED正常输出会近似0x0000000000000001 (NEEDED) Shared library: [libstdc.so.6] 0x0000000000000001 (NEEDED) Shared library: [libgcc_s.so.1] 0x0000000000000001 (NEEDED) Shared library: [libc.so.6]如果出现libQt5Core.so.5说明某个环节走了动态链接排查思路在下一节会详细展开。注意不要在x86宿主机上直接执行ldd hello_qtldd会尝试加载ARM64的ELF系统大概率会报“not a dynamic executable”或直接抛错这是正常的。6.4 部署到目标设备并运行将可执行文件复制到aarch64目标板scp hello_qt userdevice:/home/user/在目标板上执行sudo chmod x hello_qt ./hello_qt如果目标板没有配置linuxfb参数可以先指定平台插件./hello_qt -platform linuxfb看到Qt窗口以帧缓冲方式显示出来就说明整条静态交叉编译链路已经完全打通。7. 实际项目中的常见问题与排查以下问题都是我在实际项目中真正遇到过的按阶段整理成速查表。7.1 configure阶段找不到系统库现象configure中途报错例如Cannot find -lGL或Package glib-2.0 not found。原因一是对应依赖没有以ARM64架构安装到sysroot二是Qt在交叉编译时pkg-config依然指向了x86主机的.pc文件。对策先使用dpkg -l | grep 包名确认ARM64版本装没装。如果装了还在报多半是pkg-config路径被污染。可以在configure前设置export PKG_CONFIG_PATH/usr/aarch64-linux-gnu/lib/pkgconfig export PKG_CONFIG_SYSROOT_DIR/usr/aarch64-linux-gnu再重新执行configure。我的建议是从源安装的库用源安装手工编译的第三方库安装时就要指定--prefix$SYSROOT/usr --sysconfdir$SYSROOT/etc --hostaarch64-linux-gnu否则库文件放不进sysroot。7.2 链接阶段cannot find -lGL、-lz等现象编译你自己的应用时链接器报各种cannot find -lGL、cannot find -lz、cannot find -lfontconfig。原因你的工程依赖了某个Qt模块而该模块启用时用了外部库/opt下的Qt静态库在链接时要求提供该外部库的ARM64版本但你的链接参数或sysroot里没有。对策两个方向。一、回到configure阶段把对应模块用Qt内置实现替代比如-qt-zlib、-qt-libpng、-qt-libjpeg、-qt-freetype二、如果功能上必须用外部库就安装libz-dev:arm64等并在项目的.pro里通过LIBS -lz显式补链接。7.3 运行阶段libQt5Core.so.5 not found现象可执行文件已经拷到目标板运行时报error while loading shared libraries: libQt5Core.so.5: cannot open shared object file。原因这个报错非常典型说明你的二进制根本没有静态链接Qt而是链接了某个动态Qt库。可能在configure时-static没生效或者qmake被主机的Qt抢先了。对策第一步readelf -d确认依赖第二步确保你使用的qmake来自/opt/qt5.14.2-aarch64-static/bin用which qmake核实第三步工程文件里加CONFIG static重新qmake并make。7.4 显示平台QXcbConnection could not connect现象目标板运行时提示QXcbConnection: Could not connect to display或者Aborted (core dumped)。原因本手册的简化配置没有启用X11目标是linuxfb。如果你在x86桌面环境的远程shell里跑这个程序看到这个错很正常目标板没连接显示器、没有帧缓冲设备时linuxfb也会失败。对策目标板确认/dev/fb0存在且有权限运行时指定-platform linuxfb不要在没有显示设备的sshd终端里期望GUI弹出来。如果是真实X11环境请回到4.2调整configure参数。7.5 工具链/编译器版本升级带来的坑现象用新装的高版本GCC交叉编译Qt时链接阶段大量undefined reference to ...或者Qt源码编译时模板报错。原因Qt 5.14.2发布较早对GCC 11之后的C标准行为变化没有完全适配。Ubuntu 20.04默认的gcc-9实测最稳。如果装了其他工具链比如gcc-aarch64-linux-gnu-10以上编译Qt可能会在Qt头文件里触发新版标准库的兼容问题。对策尽量使用gcc-9系列交叉工具链如果必须用新版可以在Qt源码的mkspec里增加QMAKE_CXXFLAGS -Wno-deprecated-copy -Wno-deprecated-declarations消除部分警告但无法保证完全不踩雷。7.6 问题定位速查表现象所属阶段排查方向对策cannot find -lGL应用链接OpenGL相关库缺失安装arm64的libGL-dev或configure时用-no-openglcannot find -lz应用链接zlib未进入sysroot安装zlib1g-dev:arm64或Qt内置-qt-zliblibQt5Core.so.5 not found目标运行未静态链接readelf确认依赖修正qmake路径加CONFIG staticQXcbConnection could not connect目标运行平台插件选错用-pltaform linuxfb或启用xcbconfigure找不到pkg-config库configuresysroot环境变量设置PKG_CONFIG_PATH和SYSROOT_DIR编译期间gcc内存被杀死编译内存不足make加-j2或关闭其他大进程8. 把经验沉淀成可复用手册先聊个实际教训第一次做aarch64静态编译时我图省事直接拿系统自带的aarch64工具链去编Qt结果在OpenSSL、ICU、X11这些依赖上反复横跳configure跑一次报一个错补完一个依赖又冒出下一个。后来统一了思路能裁剪的模块一律裁剪真正功能必需的库全部提前安装到sysroot工具链固定在gcc-9系不再折腾。理清这个策略后从源码解压到出可执行文件整个过程半天内就能复现。再分享一个量产部署的小技巧把编译好的工具链、Qt源码包和配置命令抄录成一个build.sh脚本连同最终静态库一起归档到版本库的release目录。下次新项目直接拉取归档在干净机器上跑一遍脚本就能还原完全相同的编译环境。线上运行出问题时用同一套环境做回归定位比在目标板上猜要高效得多。如果你后续想让这个方案更通用可以接着扩展编一个ARM64的OpenSSL静态库再让Qt开启-openssl-linked或者加一个-opengl es2验证GPU渲染。但第一版VDI方案建议保持最小依赖先跑通再逐步加。静态编译的好处就是每加一个功能只需要在configure参数和sysroot里补上对应依赖剩下的交给Qt构建系统。
返回列表