ARTICLE DETAIL

资讯详情

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

Qt 5.14.2 aarch64静态交叉编译实战:从配置到部署

Qt 5.14.2 aarch64静态交叉编译实战:从配置到部署 1. 项目概述与核心痛点先说结论Qt5.14.2在aarch64架构下做静态交叉编译这件事本身不难难的是把整条链路理清楚。我见过太多人卡在同一个地方——configure参数抄了一堆编到一半报错然后就开始在论坛里翻帖子越翻越乱。这篇文章就是把我从零到一跑通的全过程包括踩过的坑和最终验证过的参数组合完整记录下来。为什么要盯住Qt 5.14.2这个版本它是LTS长期支持版本里一个非常微妙的节点qmake还在正常发力CMake支持也已经成熟同时又赶在Qt 6大改之前很多老项目的依赖还锚定在这一代。再加上aarch64在嵌入式工控、边缘网关、国产化板卡上越来越普及静态编译能省掉目标板上动态库的部署麻烦直接扔一个可执行文件上去就跑这对产线部署来说是实实在在的效率提升。适合看这篇文章的读者大概是这几类手里有aarch64开发板RK3588、树莓派4B 64位系统、飞腾派等想把Qt应用塞进去的做嵌入式产品交付希望目标机上不依赖Qt运行时的被各种缺库、链接失败、ELF格式不对折磨想找一份能通读到底的完整参考的先说清楚这篇不涉及具体商业板卡的BSP定制而是聚焦在通用的aarch64 Linux Qt 5.14.2 静态链接这条链路本身。原理通了换板子只是改交叉工具链路径的事。2. 交叉编译工具链与环境准备2.1 选对工具链aarch64-linux-gnu-gcc交叉编译的第一步不是下载Qt源码而是先把交叉编译器准备好。市面上常见的选择有这么几个Linaro提供的aarch64-linux-gnu-gccUbuntu自带的gcc-aarch64-linux-gnu包以及各家板卡厂商提供的定制工具链比如Rockchip的交叉编译器。我的建议很直接优先用Ubuntu官方源装的gcc-aarch64-linux-gnu除非你的目标板子的BSP指定了特定版本。原因有三个依赖好解决libc-dev-arm64-cross会自动装版本更新有保障和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理论上你会看到类似gcc version 9.x或者11.x的输出。这里有个关键点host端的gcc版本和交叉编译器的版本不必一致但交叉编译器的版本会影响你能用多新的C标准库特性。提示如果你用Ubuntu 22.04交叉编译器是gcc-11编Qt 5.14.2时会遇到一个编译错误后面第5节我会专门讲怎么处理。2.2 构建sysroot目标板的文件系统根交叉编译的难点在于你编译出的程序是跑在aarch64上的而不是跑在x86_64上的。编译器在编译时不仅需要头文件还需要尝试链接目标架构的libc、libstdc等基础库。这些文件和x86_64宿主机的库完全不同如果让交叉编译器误用了宿主机的库链接阶段就会爆出一堆奇怪的错误。为了让交叉编译时能找到正确的头文件和库文件需要准备一个目录结构类似目标系统根目录的文件夹这个目录叫sysroot。装完gcc-aarch64-linux-gnu之后默认的sysroot路径通常是/usr/aarch64-linux-gnu检查一下这个目录ls /usr/aarch64-linux-gnu/lib你会看到aarch64架构下的libc.so.6、libm.so.6、libstdc.so.6这些库。这些就是后续Qt configure时“探测目标环境”的基础。一般情况下不需要额外修改sysroot除非你的板卡的libc版本和宿主机里这个交叉包自带的libc版本差异巨大——这种情况建议还是用板卡厂商提供的工具链更稳妥。2.3 下载Qt 5.14.2源码下载源码时有两个选择官方在线安装器或者直接下载源码包。静态交叉编译必须用源码编译安装因为在线安装器提供的二进制包是针对host机架构的不能直接拿来做交叉编译。我习惯直接下载源码包下载地址在官方仓库文件名是qt-everywhere-src-5.14.2.tar.xz大小约500MB左右wget https://download.qt.io/archive/qt/5.14/5.14.2/single/qt-everywhere-src-5.14.2.tar.xz tar xf qt-everywhere-src-5.14.2.tar.xz cd qt-everywhere-src-5.14.2有人可能会问为什么不用git clonegit仓库虽然也能拿到5.14.2分支但需要额外跑init-repository拉子模块而且子模块版本不完全锁定容易和网络上的教程对不上号。源码包更保险解压即用。2.4 目标平台依赖库的准备这里重点说一个容易踩的坑Qt对依赖库的处理是“按需探测”的。也就是说configure阶段会检测目标系统里有没有某些库有就启用对应功能没有就静默禁用或者当作“外部依赖缺失”处理。典型例子是libfontconfig、libfreetype字体渲染没有它Qt Widgets程序显示中文会出问题libsqlite3Qt SQL模块的SQLite驱动插件它是通过系统库动态加载的libcups打印支持没有也能编过只是打印相关功能不可用libssl网络模块的TLS支持Qt5.14之前默认用的是OpenSSL需要你有目标板对应架构的libssl对于静态编译这些依赖库必须在configure之前就准备好并且最好也做成静态库.a文件否则即使Qt主库静态编出来了链接最终应用时还是会报“找不到-lfontconfig”之类的错误。我准备sysroot依赖的方法是直接在目标板上或者用debootstrap模拟目标架构环境把需要的库文件拷贝到交叉编译的sysroot里。具体操作在3.2节细说。3. Qt静态编译的关键配置与执行3.1 configure参数深度解析configure是整条编译链的核心。误解最多的就是这里的参数组合。我先把最终跑通的完整命令放出来然后逐个讲解每个参数的作用./configure \ -prefix /opt/Qt5.14.2-aarch64-static \ -static \ -release \ -opensource \ -confirm-license \ -xplatform linux-aarch64-gnu-g \ -sysroot /usr/aarch64-linux-gnu \ -nomake examples \ -nomake tests \ -no-compile-examples \ -no-opengl \ -no-eglfs \ -no-xcb \ -no-glib \ -no-pkg-config \ -skip qtwebengine \ -skip qtdeclarative \ -skip qtlocation \ -skip qtmultimedia \ -skip qtscript \ -skip qtsensors \ -skip qtserialbus \ -skip qtwayland \ -skip qtwebview \ -skip qtquickcontrols \ -skip qtquickcontrols2 \ -skip qtgraphicaleffects \ -skip qtvirtualkeyboard \ -skip qtspeech \ -skip qtgamepad \ -skip qtpurchasing \ -skip qtserialport \ -skip qtcharts \ -skip qtdatavis3d \ -skip qtnetworkauth \ -skip qtpim \ -skip qtqa \ -skip qtsvg \ -skip qttools \ -skip qttranslations \ -skip qtxmlpatterns \ -qt-zlib \ -qt-libpng \ -qt-libjpeg \ -qt-freetype \ -qt-harfbuzz \ -qt-pcre \ -qt-sql-sqlite \ -no-feature-cups \ -no-feature-printdialog \ -no-feature-printer \ -no-feature-printpreviewwidget \ -openssl-linked \ -I /usr/aarch64-linux-gnu/include \ -L /usr/aarch64-linux-gnu/lib这一堆参数看着吓人但拆开来看就很清晰了-static让Qt生成静态库libQt5Core.a这种而不是libQt5Core.so-release不编debug版缩小编译体积、加快速度-xplatform linux-aarch64-gnu-g告诉Qt用哪个mkspec。Qt 5.14自带的mkspec里已经有这个平台定义了路径在qtbase/mkspecs/linux-aarch64-gnu-g里面默认的编译器就是aarch64-linux-gnu-gcc-sysroot指定sysroot路径让configure阶段检测到的库和头文件都从这个路径找-nomake examples -nomake tests -no-compile-examples不编示例和测试节省的时间是用小时计的-no-opengl -no-eglfs -no-xcb如果你的目标板只有串口控制台或简单framebuffer显示这些显示后端都用不上禁用可以避免一堆X11/EGL依赖-no-pkg-config这个非常重要。Qt configure跨平台编译时pkg-config默认会去找宿主机的.pc文件极容易引入x86_64的库路径产生架构错乱。直接禁用它让Qt只按-I和-L参数找依赖-skip系列Qt 5.14有几十个模块像qtwebengine这种体积巨大且编译依赖极重的模块在静态交叉编译时几乎必挂直接跳过-qt-*系列让Qt使用自带的第三方库zlib、libpng、libjpeg、freetype、harfbuzz、pcre而不是去探测系统库。静态编译时这是最稳妥的方案——依赖全部编进Qt库里-qt-sql-sqlite把SQLite驱动编进Qt SQL模块省得运行时还要拷插件-openssl-linked这里要注意Qt 5.14的OpenSSL支持默认是运行时动态加载的。静态编译时想用系统OpenSSL就要改成-linked方式。如果你的应用不需要HTTPS这个参数可以不写省掉openssl依赖注意-no-feature-cups这类参数是feature级控制你以为它是禁用一个功能实际上它是在编译时直接裁剪相关代码路径。对于静态编译尽量裁剪不需要的功能能显著缩小最终可执行文件的体积。3.2 依赖库移植到sysroot的具体步骤我以libfontconfig和libfreetype为例说明依赖库怎么放进sysroot。这里的核心思路是在目标板或者用QEMU模拟的aarch64系统上把需要的库文件找到拷贝到交叉编译器的sysroot目录下。如果你的目标板已经跑起来了直接在板子上执行dpkg -L libfontconfig1 | grep \.so dpkg -L libfreetype6 | grep \.so把输出的.so文件包括软链指向的真实文件scp回本机放到/usr/aarch64-linux-gnu/lib/下。但静态编译需要的是.a静态库。如果你目标板上只装了动态库那么还需要安装对应的-dev包sudo apt install libfontconfig1-dev libfreetype6-dev安装完成后再在板子上找.a文件dpkg -L libfontconfig1-dev | grep \.a dpkg -L libfreetype6-dev | grep \.a同样的方式拷贝回本机sysroot。注意拷贝时保持目录结构头文件放到/usr/aarch64-linux-gnu/include/库文件.a和.so放到/usr/aarch64-linux-gnu/lib/。这步如果不做后果很直接configure时Qt检测不到fontconfig字体会退回使用自带freetype去渲染显示效果会大打折扣而且Qt源码里依赖fontconfig的代码会被条件编译剪掉后面想加回来就得从头再编一遍。3.3 执行configure与常见报错执行上面的configure命令后正常情况它会跑几分钟输出一堆检测摘要。看到如下字样基本就是成功了Qt is now configured for building如果中途报错八成是以下两种第一种找不到编译器。检查aarch64-linux-gnu-g是否在PATH里如果没在就在configure前把工具链路径加进去export PATH/usr/bin:$PATH注意/usr/bin这个目录是Ubuntu官方交叉编译器默认安装位置如果你用的是板卡厂商的工具链路径完全不一样。第二种某个依赖库检测不过。configure的日志里会明确打印ERROR: ... not found这时候根据错误提示去sysroot里补对应的头文件或库文件。经验configure过程输出非常长建议执行时加个日志重定向./configure ... 21 | tee configure_qt5.14.2_aarch64.log。后面出问题了翻日志比瞎猜快得多。3.4 make与make installconfigure通过之后就是编译环节make -j$(nproc)这里有个细节对于交叉编译-j并发数不能盲目拉满。我实测过32核的机器编译Qt时如果开32线程内存占用能飙到16GB以上小内存机器会直接OOM。建议按物理核数的1.5倍来比如8核机器用-j12。整个Qt base模块qtbase的编译耗时在8核机器上大约40分钟到1小时如果跳过了那些重型模块总时间能控制在2小时内。编完后执行make install注意安装路径由-prefix参数决定我这里是/opt/Qt5.14.2-aarch64-static。安装完成后检查ls /opt/Qt5.14.2-aarch64-static/lib/你应该能看到libQt5Core.a、libQt5Widgets.a等一堆静态库文件。如果看到的是.so文件说明configure参数没生效回去查-static有没有写对。4. 应用层如何链接这套静态Qt4.1 交叉qmake的使用方式Qt安装完成后真正落地使用时用的不是宿主机的qmake而是交叉编译版qmake。路径在/opt/Qt5.14.2-aarch64-static/bin/qmake建立交叉编译环境我习惯写一个环境变量脚本文件名随意比如setenv_qt_aarch64.shexport QT_ROOT/opt/Qt5.14.2-aarch64-static export PATH$QT_ROOT/bin:$PATH export ARCHaarch64 export CROSS_COMPILEaarch64-linux-gnu- export CC${CROSS_COMPILE}gcc export CXX${CROSS_COMPILE}g export AR${CROSS_COMPILE}ar export LD${CROSS_COMPILE}ld export RANLIB${CROSS_COMPILE}ranlib export STRIP${CROSS_COMPILE}strip export PKG_CONFIG_PATH每次开新终端先source setenv_qt_aarch64.sh。这个脚本有几个关键点PKG_CONFIG_PATH要置空否则系统pkg-config路径会干扰静态链接时的依赖查找CROSS_COMPILE前缀让后续调用的交叉工具链版本统一QT_ROOT让qmake能找到Qt自己的安装前缀4.2 一个最小Qt Widgets程序的手动编译示例写一个最小窗口程序看看静态编译的实际效果。新建一个目录放两个文件main.cpp内容#include QApplication #include QLabel int main(int argc, char *argv[]) { QApplication app(argc, argv); QLabel label(Hello from Qt 5.14.2 static on aarch64!); label.resize(400, 200); label.show(); return app.exec(); }hello.pro内容QT widgets SOURCES main.cpp TARGET hello然后执行source setenv_qt_aarch64.sh mkdir build cd build qmake ../hello.pro make编译过程如果顺利最终会在build目录生成一个hello的可执行文件。关键验证手段是看文件格式和动态依赖file hello readelf -d hello | grep NEEDEDfile输出里应该显示ELF 64-bit LSB executable, ARM aarch64readelf输出的NEEDED列表里应该只有一个libc.so.6和libstdc.so.6这种基础库绝对不应该出现libQt5Widgets.so这种动态依赖。如果NEEDED里还能看到libQt5开头的东西说明你链接的Qt库还是动态编译版本不是静态库版本。用ls -lh看一下这个hello文件大小通常在2MB到5MB之间这比动态编译加一堆.so搬板子的方案简洁太多了。5. 编译过程中的疑难杂症与解决方案5.1 GCC 11导致的编译错误专治前面提到Ubuntu 22.04自带的交叉编译器是GCC 11而Qt 5.14.2的代码是用GCC 9时代的标准写的编译时会撞上几个C标准相关问题。最经典的一个报错出现在qtbase的qglobal.h里qglobal.h: error: numeric_limits is not a member of std或者类似limits头文件找不到的问题。原因是Qt 5.14.2在某些头文件里没有显式includelimitsGCC 9的预处理链恰好通过其他头文件间接引入了它GCC 11改动后间接包含关系失效了。解决办法是改mkspec在qtbase/mkspecs/linux-aarch64-gnu-g/qmake.conf里给QMAKE_CXXFLAGS加一行QMAKE_CXXFLAGS -include limits这样等于让编译器在每个编译单元开头强制include limits头文件暴力但有效。还有人会遇到另一个GCC 11相关报错和std::result_of被废弃有关。Qt源码里有些地方用了Q_DECLARE_TYPEINFO配合std::result_ofGCC 11把std::result_of标记为deprecated之后Qt的-Werror会让这些警告直接变成错误。如果遇到去qmake.conf里把-Werror去掉QMAKE_CFLAGS - -Werror QMAKE_CXXFLAGS - -Werror5.2 链接阶段报undefined reference的处理应用编译好链接时如果报一大堆undefined reference to Fc*或者undefined reference to FT_*说明依赖库的静态库没在链接路径里。Qt自身link的时候同样会遇到。解决办法分两步。第一步确保sysroot里确实存在对应的.a文件比如ls /usr/aarch64-linux-gnu/lib/libfontconfig.a ls /usr/aarch64-linux-gnu/lib/libfreetype.a如果不存在去目标板拷或者用交叉工具链重新编一个。第二步在链接应用时手动把依赖库加进去。一种做法是在.pro文件里加LIBSLIBS -L/usr/aarch64-linux-gnu/lib -lfontconfig -lfreetype另一种更省事的做法是在configure阶段直接用-qt-freetype让Qt完全使用自带的freetype这样就不会有对外部libfreetype的依赖问题。fontconfig没法用-qt-方式内置所以如果确定不需要系统字体管理能力干脆在configure时通过-no-fontconfig禁掉它省心很多。5.3 哪些Qt模块最容易在静态交叉编译时翻车说几个我实际踩过的深水区qtwebengine是最难啃的骨头。它内部用Chromium的构建系统对交叉编译的支持非常有限对工具链版本和Python版本有苛刻要求静态编译基本等于给自己找不痛快。所以除非你的产品非要内嵌网页展示否则直接-skip qtwebengine。qtdeclarativeQML模块本身可以编但它运行时要依赖一个qmlcachegen工具交叉编译时这个工具会被编译成x86_64版本然后交叉环境里可能找不到。5.14.2这个版本有个已知问题在静态编译时qmlcachegen的架构检查会误判导致一部分QML运行时编译不过去。如果你的应用只用Widgetsskip掉qtdeclarative能省一个小时编译时间。qtserialport和qtcharts这两个模块交叉编译本身没问题但如果你的应用里用到了要注意静态链接时模块插件plugin的处理。静态编译下Qt模块插件默认是编译进库里的不会自动生成插件目录需要手动在main函数里调用Q_IMPORT_PLUGIN宏来注册否则会出现“程序运行起来功能没反应”的诡异现象。5.4 终极排查工具ldd、readelf和交叉版file整个静态交叉编译链路上我最推荐你熟练掌握的三个排查命令file看ELF文件架构是否正确。file a.out确认是ARM aarch64而不是x86-64这能立刻判断是否编译到了错误架构readelf -d xxx | grep NEEDED看动态依赖列表。如果依赖列表干净说明确实是静态链接如果出现libQt5开头的项说明Qt库路径配置错了aarch64-linux-gnu-readelf -A xxx看是否有硬浮点或软浮点ABI问题。Qt 5.14默认armv8-a架构如果目标板是armv7的老平台二者ABI不匹配运行时会直接报Illegal instruction这三个命令配合使用能解决90%的“编译过了但跑不起来”的问题。6. 静态编译产物的部署与运行验证6.1 把编译产物拷贝到目标板的注意事项静态编译最大的价值就在这里拷贝一个文件就能运行整个Qt应用。但有几个细节需要注意。首先那个单独的可执行文件依赖的libc和libstdc版本必须和目标板的系统版本匹配。比如你在Ubuntu 20.04的交叉环境里编出来的程序目标板如果还在用CentOS 7glibc 2.17就会报version GLIBC_2.28 not found。这种问题静态编译也躲不开因为libc.so.6不可能是纯静态的。解决办法有两种一是在更老的发行版里做交叉编译二是目标板升级内核和rootfs。其次是运行时资源目录。Qt应用运行时需要找qt.conf指定的资源路径否则会默认在可执行文件同目录下找plugins、qml、translations这些目录。如果你的程序不在代码里硬编码资源路径纯静态编译后这些插件目录也需要一起拷贝到目标板否则会出现“程序能启动但窗口渲染不出来”的情况。一个稳妥的做法是在可执行文件同目录放一个qt.conf内容[Paths] Prefix . Plugins plugins最后静态编译的二进制对CPU特性有隐含要求。编译器默认生成的ARMv8-A代码需要目标板的CPU支持。树莓派4B、RK3399、RK3588这些aarch64芯片都没问题但如果目标板是老旧的Cortex-A53低配环境需要额外注意编译参数里是否要加-marcharmv8-acrc这类兼容选项。6.2 目标板上运行验证清单部署到目标板之后我按顺序做这几件事验证# 1. 确认二进制可执行 chmod x hello # 2. 确认依赖干净 ldd hello正常输出只有linux-vdso.so.1 (0x0000ffff...) libstdc.so.6 /usr/lib/aarch64-linux-gnu/libstdc.so.6 libm.so.6 /lib/aarch64-linux-gnu/libm.so.6 libgcc_s.so.1 /lib/aarch64-linux-gnu/libgcc_s.so.1 libc.so.6 /lib/aarch64-linux-gnu/libc.so.6接着运行QT_QPA_PLATFORMoffscreen ./hello先用offscreen平台跑一下如果程序能正常起进程且不崩溃说明基础Qt运行时没问题。如果显示有问题再用QT_QPA_PLATFORMlinuxfb或者eglfs去适配实际显示后端。如果用到字体检查一下系统里有没有中文字体如果没有程序里用QFont指定字体的地方会输出警告。静态编译时字体文件不会被带进程序里除非你用QResource把字体嵌到二进制里。我一般建议把一部份常用字体直接打包到程序的资源文件.qrc中一劳永逸。6.3 体积优化策略静态编译的可执行文件体积总是一个绕不开的话题。一个最简单的Qt Widgets程序带-static编译出来通常在3MB到6MB之间看着还好但如果加上Qt Charts、Qt Network、Qt SVG这些模块体积会迅速膨胀到20MB以上。我实测有效的瘦身手段有这么几个编译时加-no-feature-*裁剪不需要的代码路径比如-no-feature-printdialog、-no-feature-concurrent这类链接应用时加-Wl,--gc-sections让链接器去除未引用的代码段对最终可执行文件执行aarch64-linux-gnu-strip hello去掉符号表三条叠加下来体积减一半是常态。注意strip必须在交叉编译工具链下执行用host的x86_64 strip会直接把文件搞坏。7. 常见问题速查表问题现象可能原因解决方案configure时提示cannot find -lGL启用了OpenGL相关模块但目标板没有libGL加-no-opengl重新configuremake过程中大量c internal compiler error并发数太高导致内存吃紧或工具链有bug降低-j参数考虑换用Linaro工具链链接应用时undefined reference toqt_plugin_*静态插件未注册在main中使用Q_IMPORT_PLUGIN手动导入程序放板子上提示Executor format错误编出来是x86_64架构检查qmake是否是交叉版的确认PATH环境变量运行时报找不到libQt5Core.so.5实际链接到了动态Qt库确认configure时有-static重新make installconfigure提示The test for linking against libssl failedOpenSSL版本不匹配或sysroot缺库加-no-openssl跳过TLS或手动把正确版本libssl放入sysroot程序跑起来中文全部显示豆腐块系统无中文字体在.qrc里嵌入字体或目标板安装字体这表里每一条都是我和同事实际遇到过的尤其是静态插件那个问题很多人折腾半天以为是代码bug实际上只是少了一行宏注册。8. 一些个人心得关于Qt 5.14.2的aarch64静态交叉编译最后说几点我的体会。第一不要迷信一次configure就能成功。整个编译链路里最容易出问题的就是依赖库检测。每加一个新模块就多一组系统依赖需要处理。正确做法是先按最小集qtbase跑通再逐步加模块每次加完都能编过再继续。一上来就全部编译报错了根本不知道是哪个依赖出了幺蛾子。第二日志保存是个好习惯。我在每一步输出都留了一份日志文件存档后面遇到新的问题翻旧日志常常能找到蛛丝马迹。比如某个库到底是configure时被Skip掉还是编译时才报错在这两份日志里表现得完全不一样。第三交叉编译工具链的版本极其敏感。我用Ubuntu 20.04的gcc 9编译整个Qt库一次性通过换到Ubuntu 22.04的gcc 11就需要加-include limits的补丁。如果你的项目对时间要求紧直接上20.04配合gcc 9是最省心的组合。第四这个方案真正的爽点在于交付。可执行文件加几个资源文件打包成一个tar包就能分发到任意一台同架构板子上跑不用装Qt runtime不用配环境变量不用处理.so之间的兼容性。对于产品批量部署来说这带来的工作量下降不是一点点。我倒不是建议所有场景都走静态编译——如果你的产品还需要动态加载第三方插件静态方案会让事情变复杂。但对于那些“写一个工具性应用、跑在固定硬件上、不想被运行时环境束缚”的场景这套Qt 5.14.2 aarch64静态交叉编译的方案我个人觉得是当前阶段性价比很高的选择。按上面的流程走一遍两三天内你就能得到一套自己可控的交叉编译环境后续维护和调试也有据可依。
返回列表