ARTICLE DETAIL

资讯详情

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

Qt 5.14.2 aarch64静态交叉编译全流程:从环境搭建到单文件部署

Qt 5.14.2 aarch64静态交叉编译全流程:从环境搭建到单文件部署 最近在给一块aarch64板卡做Qt界面程序迁移。原方案是直接在设备上补齐动态库结果依赖链越拉越长——libQt5Widgets.so后面跟着ICU、xcb、fontconfig一大堆so板子上缺哪个都要单独补版本稍有偏差就崩。被折腾了几天后我换了思路用Qt5.14.2做aarch64静态交叉编译把Qt和所有依赖全部静态链接进一个可执行文件目标设备只需要放一个文件就能跑。这篇手册是我从零搭建全过程的完整记录覆盖工具链、sysroot、依赖库、configure参数、常见报错排查。如果你也在做ARM Linux设备上的图形界面程序或者希望开发的应用能做到拷贝即用这篇内容也许能帮你少走几天弯路。1. 方案组合逻辑Qt版本、架构与静态编译怎么理解1.1 为什么选Qt 5.14.2而不是新版Qt版本选择直接决定了后续配置方式和工作量。我实测下来的结论是在嵌入式交叉编译场景Qt 5.14.2是一个非常稳的中间态版本。原因有三。第一5.14.2是LTS长期支持版本修复了大量稳定性问题文档和网上资料都很全。碰到编译报错时基本能搜到对应的解决方案这对交叉编译来说特别重要。交叉编译的问题往往很“个性化”资料少的话排查成本极高。第二它还在qmake体系内。从Qt 6开始官方全面转向CMakeconfigure参数和构建方式变化很大而5.14.2的configure参数体系非常清晰交叉编译可以一条命令控制不用额外写一堆CMake toolchain文件。对于嵌入式项目来说少一层抽象就少一堆可变因素。第三不少芯片厂商的BSP、SDK、开发例程都基于Qt 5.14或5.15选5.14.2更容易和厂商代码对齐。就算你拿到的是5.12或者5.15的工程迁移到5.14.2的改动通常也可控。如果项目对技术栈没有硬性要求我一般建议在5.12和5.15之间做选择。5.14.2是这两者中间比较折中的存在比5.12新bug修得多比5.15极致场景少但稳定性更好。1.2 “静态交叉编译”到底在编译什么“Qt5.14.2-aarch64静态交叉编译”这句话里包含了两个维度的动作交叉编译和静态链接。交叉编译指的是在x86的Linux开发机上使用aarch64工具链编译出能在ARM 64位设备上运行的代码。编译器的行为、头文件、库文件都必须指向ARM体系而不是开发机本地的x86体系。静态链接指的是最终的可执行文件不依赖目标设备上的动态库。Qt本身以及openssl、zlib、freetype等依赖库全部以.a静态库的形式打包进最终可执行文件里。很多人容易在这里理解偏以为“交叉编译出来的Qt程序”就算完成任务。实际上如果你用开发机上的Qt动态库做交叉编译编译出来的程序拷到板子上依然要带上一堆Qt库的.so文件依然要解决库路径、版本匹配的问题部署体验没有本质改善。只有把Qt库本身也编译成静态库并在链接应用时全部打包进去才是真正意义上的“单文件部署”。有个类比很贴切动态编译像是出门借工具到了目标设备上还要找到相应的工具库才能开工静态编译则是把整套工具箱装进自己背包走到哪儿都能直接开干。代价是背包变重——静态编译出来的可执行文件体积要比动态版大不少但换来的是部署省心。我的一个工程单纯一个带Qt Widgets界面的程序静态编译后大约9MB。相比动态版要大但比起在板子上逐个安装依赖库、处理兼容性问题的成本这点体积完全值得。2. 环境准备工具链、sysroot与源码获取2.1 工具链选型aarch64交叉编译工具链最常用的是aarch64-linux-gnu-gcc系列。版本选择上我建议用GCC 9到GCC 11之间太老的编译器对C11/14支持不足太新的GCC 12/13在编译Qt 5.14.2时偶尔会出现与旧版源码宏的兼容问题虽然也可通过补丁绕过但不值得在这上面浪费时间。Ubuntu 20.04和22.04的软件源里都有现成的交叉工具链包安装命令如下sudo apt update sudo apt install gcc-aarch64-linux-gnu g-aarch64-linux-gnu安装后检查版本aarch64-linux-gnu-gcc --version这里有一个更重要的问题如果你的目标板是某家芯片厂商的定制板卡优先使用板厂BSP里自带的工具链而不是系统源里的通用工具链。原因在于交叉编译链是否和板子rootfs里的glibc、libstdc版本匹配直接决定了编译出的程序在目标板上能否运行。这一点在第五章的GLIBCXX问题里会详细展开先记住这个结论工具链版本要尽量跟着板子的rootfs走而不是跟着开发机的系统源走。2.2 制作sysroot交叉编译器在编译时需要头文件和库文件这些文件必须来自aarch64体系而不是开发机自带的x86系统目录。这个专门提供给交叉编译器使用的“目标系统根目录”就是sysroot。制作sysroot最简单可靠的方式从目标板拷贝整个rootfs。mkdir -p /opt/sysroot rsync -avz --exclude/proc --exclude/sys --exclude/dev --exclude/tmp --exclude/run root板子IP:/ /opt/sysroot/拷贝完成后检查关键目录ls /opt/sysroot/usr/lib/aarch64-linux-gnu/如果板厂提供了rootfs压缩包或SDK包直接用压缩包解压到/opt/sysroot也一样。需要注意rootfs里必须包含基础的libc、libstdc以及你程序运行时会用到的系统库。如果是裁剪过的生产rootfs缺少某个系统库时后续链接可能报“cannot find -lxxx”之类的错误那时候再回到rootfs补库即可。交叉工具链一般自带一个默认sysroot路径。比如Ubuntu的gcc-aarch64-linux-gnu包默认sysroot在/usr/aarch64-linux-gnu目录下。我习惯把板子rootfs里的对应内容同步到工具链的默认sysroot这样编译时不用额外传--sysroot参数减少出错概率。2.3 Qt源码与构建工具准备Qt 5.14.2全量源码包是qt-everywhere-src-5.14.2.tar.xz。源码包解压后比较大下载前先确认开发机磁盘剩余空间至少20GB以上源码解压加上中间编译文件很快会超过10GB。下载时建议从Qt官方或国内高校镜像站获取。源码包约500MB如果下载速度不理想也可以选择只下载qtbase等核心模块但对于交叉编译全量包更稳妥毕竟你不知道后面哪个模块会用到。另外需要安装构建Qt所依赖的基础工具sudo apt install python3 perl make g libclang-14-devQt的configure脚本是Perl脚本系统里没有Perl会直接失败这是最先检查的一项。Python也在构建过程中被用到。libclang主要用于Clang相关插件和qdoc文档生成如果不需要可以跳过但装了也无妨。源码解压时注意路径。我习惯放到/opt/qt-src/目录下mkdir -p /opt/qt-src tar -xf qt-everywhere-src-5.14.2.tar.xz -C /opt/qt-src请确保路径中不要有中文、空格或特殊符号。这个问题在make阶段会以莫名其妙的报错出现浪费时间。3. 依赖库处理三分编译七分依赖3.1 基础库优先使用Qt内建版本Qt源码包自带了一批第三方库——zlib、libpng、libjpeg、freetype、harfbuzz都在里面。configure时用-qt开头的参数即可让Qt直接编译这些内建库的静态版本不需要单独去交叉编译它们。我推荐的配置项如下-qt-zlib -qt-libpng -qt-libjpeg -qt-freetype -qt-harfbuzz这样做的好处有两个版本完全匹配不受系统库版本波动影响省去单独交叉编译zlib/libpng等库的步骤减少工作量和出错点。尤其是freetype它在Qt的文字渲染链路里是核心角色。很多嵌入式rootfs自带的freetype版本与Qt期望的版本不一致直接使用Qt内建版本能规避绝大部分字体渲染问题。如果你的程序只是做一个带按钮、列表、文本显示的Widgets界面这套组合基本够用了。如果需要处理PNG、JPEG图片格式上面的配置也已经覆盖。3.2 外部依赖库的处理以openssl为例如果程序要使用HTTPS比如通过QNetworkAccessManager访问接口那么还得引入openssl。这部分无法使用Qt自带版本需要单独交叉编译。openssl 1.1.x系列与Qt 5.14.2兼容性较好建议优先选择这一系列不要一上来就用openssl 3.x某些API调整和符号变化需要额外适配。交叉编译openssl静态库的步骤tar -xf openssl-1.1.1w.tar.gz cd openssl-1.1.1w ./Configure linux-aarch64 -static no-tests --prefix/opt/ossl-aarch64 --cross-compile-prefixaarch64-linux-gnu- make -j16 make install编译完成后头文件和.a库都会安装到/opt/ossl-aarch64目录。把该目录下的include和lib内容同步到工具链的sysroot中cp -r /opt/ossl-aarch64/include/* /usr/aarch64-linux-gnu/include/ cp -r /opt/ossl-aarch64/lib/* /usr/aarch64-linux-gnu/lib/这样Qt configure时就能找到openssl的头文件和库。不过这里我要多说一句静态交叉编译openssl的坑不少比如需要目标板rootfs里有对应的openssl配置目录、随机数设备节点等。如果你的业务场景只是局域网设备通信没有硬性HTTPS需求那我的建议是configure时不要启用openssl支持直接少一件麻烦事。3.3 如何确认依赖库架构正确交叉编译过程中比较让人头痛的报错是链接时出现“file format not recognized”。这个报错通常是因为编译器拿到了x86架构的库文件。养成一个习惯任何准备进入sysroot的库文件先检查架构file /usr/aarch64-linux-gnu/lib/libssl.a正常的aarch64静态库输出类似这样ELF 64-bit LSB relocatable, ARM aarch64, version 1 (SYSV)如果输出是“x86-64”说明这个库是给开发机用的不能用于交叉链接。也可以批量检查一个目录下所有库文件的架构find /usr/aarch64-linux-gnu/lib -name *.a -o -name *.so | xargs file | grep -v ARM aarch64这个命令会把架构不是aarch64的库全部筛出来是排查问题时很好用的一招。检查静态库时注意一点.a文件通常显示为“ELF 64-bit LSB relocatable”这是正常的因为静态库里面存放的是可重定位目标文件不是最终的可执行文件或动态库不要误判为异常。4. 核心构建步骤configure、make、install4.1 configure参数逐项拆解一切准备就绪后进入Qt源码目录执行configure。我这里给出一个经过实测的完整命令并解释每个参数的作用。cd /opt/qt-src/qt-everywhere-src-5.14.2 ./configure \ -opensource \ -confirm-license \ -static \ -release \ -prefix /opt/Qt5.14.2-aarch64-static \ -xplatform linux-aarch64-gnu-g \ -device-option CROSS_COMPILEaarch64-linux-gnu- \ -no-opengl \ -no-icu \ -qt-zlib \ -qt-libpng \ -qt-libjpeg \ -qt-freetype \ -qt-harfbuzz \ -no-feature-glib \ -skip qtwebengine \ -skip qtdeclarative \ -skip qtscript \ -skip qtwayland \ -nomake examples \ -nomake tests \ -make libs各参数含义-opensource -confirm-license接受开源协议避免交互式确认。-static整个Qt库以静态库方式构建这是本项目的核心前提。-release只编译release版不要用debug和release混合模式那是空间和时间的浪费。-prefix安装目标路径最后编译好的Qt库和工具都会放在这里。-xplatform linux-aarch64-gnu-g告诉Qt使用哪个跨平台mkSpec文件。这个mkSpec文件决定了Qt对目标架构、编译器参数的预设。-device-option CROSS_COMPILEaarch64-linux-gnu-指定交叉编译工具链前缀。Qt构建时会在内部拼出aarch64-linux-gnu-gcc、aarch64-linux-gnu-g等命令行。如果你的工具链前缀不同替换这个前缀即可。-no-opengl如果你的板子没有GPU或不需要OpenGL渲染直接关闭。这是嵌入式开发中最常需要调整的条件参数。-no-icuICU库体积巨大、交叉编译也麻烦。如果程序不做复杂文本排序和Unicode处理建议关闭。普通中文界面显示不受影响。-qt-zlib等内建库参数让Qt编译自带第三方库具体原因已经在第三章说明。-no-feature-glib很多裁剪的嵌入式rootfs没有glib先关闭避免configure报错。-skip qtwebengineQtWebEngine体积庞大、静态化困难务必跳过。-skip qtdeclarative如果只做Widgets界面可以跳过QML模块省下大量时间。-nomake examples -nomake tests禁掉示例和测试代码的编译。-make libs只构建库文件和必要工具进一步控制构建范围。configure执行后终端最后会打印一份配置摘要请重点关注几个位置前缀目录、编译模式是否为Static、目标平台是否为arm64。确认无误后再进入make阶段。4.2 可能需要处理的显示后端问题configure时对显示后端很敏感。有几点值得提前明确。如果你要跑在带X服务器的环境上需要xcb支持那configure命令里要加入-qt-xcb同时sysroot里必须包含libxcb及相关依赖库。许多Linux桌面rootfs自带这些库但裁剪过的最小rootfs很可能没有需要单独补齐。如果板子没有X服务直接用Qt内置的linuxfb或eglfs平台插件就不需要xcb这种情况下千万别启用xcb模块否则configure会因缺少头文件报错。我做的板子是直接接HDMI显示的嵌入式Linux没有桌面环境所以使用linuxfb平台插件也就是framebuffer直接输出稳妥简单。如果板子带GPU可以考虑eglfs但需要把libEGL、libGLESv2等库准备到sysroot里复杂度会高不少。这一点是很多教程里一笔带过的“小事”但实际卡在这儿的概率非常高。开始configure之前先想清楚你的显示链路会让你后面轻松很多。4.3 make安装与模块级增量构建configure没报错的话进入编译阶段make -j16这里的-j16并行编译任务数需要根据开发机CPU核心数和内存来设置。16G内存的机器-j8比较稳内存32G以上可以考虑-j16。并行度太高遭遇内存不足反而会报错或导致编译中途系统卡死。整个Qt全量编译在16核机器上大约需要半小时到一小时具体看磁盘性能和系统负载。如果只保留了Widgets相关模块会快很多。如果编译过程中途失败不建议直接重复make -j16那样错误日志被刷屏很难定位。改用单线程重新编译make -j1 21 | tee build.log然后去build.log里搜索error关键字。定位并修复问题后重新make即可。如果你只修改了某个模块的配置不需要全量重编。Qt的Makefile支持按模块构建make module-qtbase make module-qtsvg先make module-qtbase把基础模块搞定再逐个构建其他模块排查问题时有条理得多。编译完成后安装make install安装结束检查静态库是否生成ls /opt/Qt5.14.2-aarch64-static/lib/ | grep \.a$正常情况下你会看到libQt5Core.a、libQt5Widgets.a这一串.a文件。如果这些文件不存在多半是configure时-static参数没生效或者安装路径不对需要回头重新检查。4.4 用交叉qmake编译自己的程序Qt安装好之后需要编译自己的Qt应用。这里最常犯的错误是使用开发机系统里已有的qmake那会生成x86架构的Makefile编译出来的程序根本不能在aarch64上跑。正确的做法是直接使用刚构建出的qmake/opt/Qt5.14.2-aarch64-static/bin/qmake hello.pro make在工程的.pro文件里如果是静态链接Qt模块可以显式声明一下要用的模块QT widgets TARGET hello SOURCES main.cppqmake会自动读取安装路径下的Qt配置生成针对aarch64交叉编译的Makefile。Makefile里会自动带上工具链前缀aarch64-linux-gnu-g和安装目录下的头文件、静态库路径。一个需要留意的点是环境变量PATH。如果你开发机上原本有其他Qt目录在PATH环境变量前面可能发生qmake路径冲突。建议在编译前显式设定export PATH/opt/Qt5.14.2-aarch64-static/bin:$PATH然后用which qmake确认已经指向/opt/Qt5.14.2-aarch64-static/bin/qmake再执行qmake操作。编译产出的可执行文件同样用file命令检查架构file hello输出应以“ELF 64-bit LSB executable, ARM aarch64”开头然后就可以拷贝到目标板上运行了。5. 常见问题与排查技巧实录5.1 configure报错缺少Perl或无法找到qmakeQt 5.14.2的configure脚本依赖Perl。如果你在精简环境或容器里构建最容易遇到的报错就是Perl相关错误。解决方式很简单安装Perl即可。还有一种情况是源码放在非Linux原生文件系统上比如共享文件夹、NTFS挂载目录编译时会出现各种权限或路径转换异常报错位置往往莫名其妙。我碰到过一次在共享目录下configure成功但make阶段大量“Permission denied”的经历排查了很久最后把源码挪到/opt/下原生ext4分区问题就消失了。另外如果configure过程中想要从头再来不要直接在旧配置上覆盖先把状态清理干净make distclean再重新configure。跳过这一步有时会残留旧配置项导致新的参数不生效。5.2 链接报错cannot find -lGL / -lEGL这个报错很常见。即使你在configure时加了-no-opengl某些Qt模块特别是qt3d、qtquick等仍然会在Makefile里引用OpenGL相关库。如果你的sysroot没有对应的aarch64库链接时就会失败。解决思路有两种第一种如果确实不需要OpenGL相关功能把对应的Qt模块也skip掉-skip qt3d -skip qtquick第二种如果板子有GPU且确实需要OpenGL那就在sysroot里补上libGL、libEGL、libGLESv2等开发包。这个过程比较麻烦因为OpenGL的库往往有后续依赖一个库接一个库地补容易陷入依赖地狱。我的建议很简单没有明确GPU需求的嵌入式项目统一走-no-opengl linuxfb方案能把这块复杂度降到最低。5.3 运行报错GLIBCXX_3.4.29 not found这是交叉编译场景里最容易踩、也最让人困惑的错误。编译过程中如果你的交叉工具链的libstdc版本比目标板rootfs里的libstdc更高链接器有可能会把新版本的C标准库符号写进可执行文件。程序拷到板子上运行时板子的libstdc.so.6不提供这些符号就会直接报错。排查方法strings /usr/lib/aarch64-linux-gnu/libstdc.so.6 | grep GLIBCXX看看板子上的libstdc.so.6支持到哪个GLIBCXX版本再用strings检查可执行文件strings hello | grep GLIBCXX如果你看到可执行文件里存在目标板不支持的高版本符号说明工具链和rootfs版本不同步。这个问题的根治办法是使用板厂BSP提供的工具链而不是开发机软件源里的通用交叉工具链。因为板厂工具链通常是配合他们rootfs的glibc/libstdc版本构建的天然匹配。如果项目还在早期阶段换工具链成本不高建议尽早换。等项目代码多了再换编译配置和工作流的改动量会大不少。5.4 运行报错could not find or load the Qt platform plugin linuxfb静态编译的Qt程序和动态编译有一个显著区别动态编译时平台插件比如libqlinuxfb.so、libqxcb.so是独立文件Qt程序启动时会自动到插件目录加载但静态编译后插件也被静态链接进了可执行文件程序默认不会主动加载它于是运行时报错找不到平台插件。解决方法是在.pro文件里显式声明要使用的插件QTPLUGIN qlinuxfb然后重新执行qmake、make。如果用的是xcb就写成QTPLUGIN qxcb如果程序需要在多个平台插件间切换可以在.cpp里加宏声明#include QtPlugin Q_IMPORT_PLUGIN(qlinuxfb)这个问题几乎每个第一次做Qt静态编译的人都会遇到因为这个错误信息没有提示“插件已经被静态链接”很容易让人往插件路径配置方向排查浪费不少时间。5.5 部署到目标板静态程序的验证流程程序在开发机编译完成后拷贝到目标板之前有几个基本检查我建议按顺序做一遍先在开发机上用file确认可执行文件架构file hello确认是ARM aarch64架构后再用readelf查看动态段依赖readelf -d hello | grep NEEDED静态编译的程序动态依赖应该非常少通常只有libc、libstdc、libm、libgcc_s这类基础系统库。如果看到libQt5Widgets.so.5、libQt5Core.so.5这类依赖说明你的Qt还是编译成了动态链接编译配置有问题需要回头检查configure参数里-static是否生效。把这些检查做完再拷贝到板子上执行。权限问题记得加上chmod x hello如果在板子上仍然缺某个系统库先查清楚缺的是哪个然后回到sysroot补库并重新链接而不是在板子上硬改库文件。6. 从零到跑通的最后一点体会整个流程走下来我最深的感受是静态交叉编译的难度并不在编译本身而在对依赖链路和版本匹配的把握。工具链与rootfs的匹配关系决定底层稳定sysroot决定编译器能拿到哪些库configure参数决定Qt模块的裁剪方向这三者只要有一环没对齐就会在后面某个阶段以报错的形式还回来。所以如果你还在项目规划阶段一定要先确定板子的rootfs版本和对应的工具链别到写代码的时候再回头补工具链那真的会非常被动。另一点是记录。第一次配置成功后我知道后面项目更新环境时大概率还要再用到这个配置所以把完整的环境变量、configure命令、sysroot路径、依赖库版本全部写进了一个部署文档。有条件的话建议把工具链、sysroot、configure参数连同源码版本都存档保存下次在新机器上重建环境时直接按文档恢复不用再踩一遍当初的坑。Qt 5.14.2的aarch64静态交叉编译这条路走通一次之后后面再做ARM板卡上的Qt开发会顺手很多。希望这篇记录能帮你少走一点弯路。
返回列表