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

资讯详情

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

Qt5.14.2 aarch64静态交叉编译实战:从配置到部署

Qt5.14.2 aarch64静态交叉编译实战:从配置到部署 1. 为什么值得折腾Qt5.14.2的aarch64静态交叉编译如果你手上有Orange Pi CM5、树莓派这类aarch64开发板又想把Qt程序直接丢上去跑不依赖板子上的Qt运行库那静态交叉编译几乎是绕不开的一条路。我第一次接触这个需求是在一个工业HMI项目上客户要求程序拷过去就能跑板子上不装任何Qt相关的东西连libQt5Core.so都不许有。当时试过动态编译加打包依赖结果板子上的glibc版本和编译机不一致跑起来各种符号找不到最后还是老老实实回到静态编译这条路。Qt5.14.2这个版本比较特殊它是Qt5 LTS的最后一个长期支持版官方对aarch64的支持已经相当成熟但静态编译的坑依然不少。网上大部分教程要么是x86_64的要么是Qt5.12的针对5.14.2加aarch64的组合完整走一遍的并不多。这篇内容就是把我从零搭建的整个过程拆开包括工具链选型、configure参数怎么定、编译过程中遇到的报错怎么排查以及最后怎么验证产物确实是静态链接的。适合谁来参考如果你已经会用Linux基本命令知道交叉编译大概是怎么回事但没完整做过Qt的静态交叉编译那这篇正好。如果你连configure都没跑过建议先找个x86_64的Qt编译练练手不然报错会让你怀疑人生。整个流程大概需要两到三个小时取决于机器性能我用的是一台16核的Ubuntu 20.04虚拟机编译QtBase大概花了40分钟完整编译所有模块接近两个小时。注意静态编译Qt意味着最终产物会把Qt库直接链进可执行文件体积会大很多一个简单的窗口程序可能就有20MB以上。如果你的场景对体积敏感需要权衡一下。2. 工具链与源码的选型逻辑2.1 交叉编译工具链怎么选aarch64的交叉编译工具链有好几个来源常见的有Linaro的gcc-arm-*系列、Bootlin的工具链、以及各芯片厂商自己提供的。我试过Linaro的gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu和Bootlin的aarch64--glibc--stable-2022.08-1两个都能用但Linaro的文档更全遇到问题好查。选工具链的时候要注意几个点。第一是glibc的版本工具链自带的glibc版本不能高于目标板子上的glibc版本否则编译出来的程序在板子上跑不了。我用的Orange Pi CM5出厂系统是Ubuntu 22.04glibc是2.35所以工具链的glibc只要不高于2.35就行。第二是工具链的架构必须是aarch64-none-linux-gnu或者aarch64-linux-gnu不能选aarch64-none-elf那是裸机用的。# 下载Linaro工具链 wget https://releases.linaro.org/components/toolchain/binaries/7.5-2019.12/aarch64-linux-gnu/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar.xz tar -xf gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar.xz -C /opt/ export PATH/opt/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin:$PATH验证工具链是否可用aarch64-linux-gnu-gcc -v如果输出里能看到gcc version 7.5.0说明工具链没问题。这里我选的是7.5版本因为Qt5.14.2对gcc的要求是5.0以上7.5足够稳定而且Linaro这个版本的兼容性经过大量验证。2.2 Qt源码版本与模块取舍Qt5.14.2的源码包可以从官方下载文件名是qt-everywhere-src-5.14.2.tar.xz。注意不要下成qt-everywhere-opensource-src-5.14.2.tar.xz那个是旧命名内容其实一样但有些脚本会检查文件名。下载地址用国内镜像会快很多比如清华的镜像站。源码包大概500多MB解压后接近3GB。wget https://mirrors.tuna.tsinghua.edu.cn/qt/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模块取舍是个关键决策。完整编译所有模块要很久而且很多模块你根本用不到。我一般会先只编译qtbase确认工具链和配置没问题再逐步加模块。常用的模块有qtbase、qtsvg、qtdeclarative、qtquickcontrols2、qtserialport、qtcharts。如果你做的是嵌入式HMIqtbase加qtsvg基本够用如果需要QML那qtdeclarative和qtquickcontrols2必须加。提示qt-everywhere-src-5.14.2里有个configure脚本但它是顶层脚本实际编译时每个模块是独立的。我建议用顶层configure一次性配置然后make -jN编译这样省事。2.3 静态编译的configure参数怎么定configure参数是整个过程的核心参数不对后面全是坑。我先把完整的configure命令贴出来然后逐条解释。./configure -prefix /opt/qt5.14.2-aarch64-static \ -opensource -confirm-license \ -release \ -static \ -nomake examples -nomake tests \ -no-opengl \ -no-xcb \ -no-eglfs \ -no-linuxfb \ -no-kms \ -no-glib \ -no-icu \ -no-dbus \ -no-feature-accessibility \ -qt-zlib -qt-libpng -qt-libjpeg \ -qt-pcre \ -qt-freetype \ -qt-harfbuzz \ -sysroot /opt/sysroot \ -platform linux-g \ -xplatform linux-aarch64-gnu-g \ -device-option CROSS_COMPILE/opt/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu- \ -optimize-size逐条解释一下。-prefix指定安装路径我习惯放在/opt下方便管理。-static是核心开启静态编译。-nomake examples -nomake tests跳过示例和测试能省不少时间。-no-opengl、-no-xcb这些是禁用不需要的平台插件嵌入式环境一般用eglfs或者linuxfb但如果你板子上没有GPUeglfs也跑不起来所以干脆全禁了用最基础的framebuffer。-qt-zlib -qt-libpng这些是让Qt用自带的第三方库而不是系统库。静态编译时用系统库容易出问题因为系统库可能是动态的链进去会有依赖。-qt-pcre、-qt-freetype、-qt-harfbuzz同理。-sysroot指向目标系统的根文件系统。这个很重要因为编译时需要目标系统的头文件和库。如果你没有现成的sysroot可以从板子上把/lib和/usr/lib拷过来或者用工具链自带的sysroot。我一般是从板子上rsync一份出来。-xplatform linux-aarch64-gnu-g指定目标平台。Qt源码里自带这个mkspec但需要确认一下路径是qtbase/mkspecs/linux-aarch64-gnu-g。如果没有可以复制linux-arm-gnueabi-g改一下。-optimize-size是优化体积静态编译产物本来就大能省一点是一点。注意-no-dbus和-no-glib要慎重。如果你的程序用到QDBus或者Glib的事件循环就不能禁。我一般做纯Qt Widgets程序会禁掉减少依赖。3. 从零开始的完整实操流程3.1 环境准备与sysroot制作先装好必要的依赖。Ubuntu 20.04上编译Qt需要这些包sudo apt-get install build-essential perl python3 git \ libx11-dev libxext-dev libxrender-dev libxcb1-dev \ libx11-xcb-dev libxcb-glx0-dev libxcb-keysyms1-dev \ libxcb-image0-dev libxcb-shm0-dev libxcb-icccm4-dev \ libxcb-sync-dev libxcb-xfixes0-dev libxcb-shape0-dev \ libxcb-randr0-dev libxcb-render-util0-dev \ libxcb-xinerama0-dev libxcb-xkb-dev libxkbcommon-dev \ libxkbcommon-x11-dev这些包主要是给host Qt用的因为编译过程中需要先编译一个host版本的qmake。虽然我们是交叉编译但Qt的构建系统需要host工具。sysroot的制作有两种方式。一种是从板子上直接拷另一种是用工具链自带的。我推荐从板子上拷因为最准确。# 在板子上执行 rsync -avz /lib /usr/lib /usr/include root编译机:/opt/sysroot/如果板子不方便操作也可以用工具链自带的sysroot一般在工具链目录的aarch64-linux-gnu/libc目录下。但要注意工具链自带的sysroot可能缺少一些板子特有的库。sysroot准备好后需要修改里面的绝对路径符号链接。因为从板子上拷过来的/lib和/usr/lib里有很多指向绝对路径的软链接在编译机上这些路径不存在。cd /opt/sysroot find . -type l -exec sh -c file$1; target$(readlink $file); if [ ${target:0:1} / ]; then ln -sfn /opt/sysroot${target} $file; fi _ {} \;这个命令会把所有指向绝对路径的软链接改成指向/opt/sysroot下的对应路径。3.2 Qt源码配置与编译进入Qt源码目录先创建一个build目录不要在源码目录里直接编译否则清理起来很麻烦。mkdir build cd build然后运行前面提到的configure命令。configure过程大概需要几分钟会检查各种依赖。如果报错说某个库找不到一般是sysroot里缺对应的头文件或库。configure成功后会输出一个summary列出哪些模块会被编译哪些被跳过。仔细看一下确认你需要的模块都在。编译用make-jN的N建议是CPU核心数的1.5倍。我16核的机器用-j24编译qtbase大概40分钟。make -j24编译过程中如果报错最常见的是找不到头文件。这时候要检查sysroot里是否有对应的头文件以及configure时是否指定了正确的sysroot路径。编译完成后安装make install安装到/opt/qt5.14.2-aarch64-static目录下。3.3 验证静态编译产物安装完成后先验证qmake是否可用/opt/qt5.14.2-aarch64-static/bin/qmake -v如果输出显示QMake version 3.1Using Qt version 5.14.2说明安装成功。然后写一个最简单的测试程序// 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(); }用交叉编译的qmake生成Makefile/opt/qt5.14.2-aarch64-static/bin/qmake main.cpp make编译完成后用file命令检查产物file main应该输出类似ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, for GNU/Linux 3.7.0, not stripped。关键是statically linked说明静态链接成功。再用ldd检查aarch64-linux-gnu-ldd main如果输出not a dynamic executable说明确实没有动态依赖。把main拷到板子上直接运行./main如果能看到窗口说明整个流程走通了。4. 常见报错与排查技巧实录4.1 configure阶段的典型报错报错ERROR: Feature xcb was enabled, but the pre-condition features.thread libs.xcb failed.这个是因为没有禁用xcb但sysroot里又没有xcb库。解决办法是在configure里加-no-xcb。嵌入式环境一般用不到xcb。报错ERROR: Cannot find libz.sysroot里没有zlib。解决办法是加-qt-zlib让Qt用自带的zlib。报错Project ERROR: Unknown module(s) in QT: serialport这个报错通常出现在编译应用程序时说明Qt编译时没有包含qtserialport模块。解决办法是在configure时确保没有-skip qtserialport并且qtserialport模块已经编译安装。4.2 编译阶段的典型报错报错fatal error: bits/libc-header-start.h: No such file or directory这个是因为工具链的sysroot路径不对或者sysroot里缺少头文件。检查configure时的-sysroot参数是否正确以及sysroot里是否有/usr/include/bits目录。报错undefined reference to __stack_chk_fail这个是因为工具链的gcc版本和sysroot里的glibc版本不匹配。解决办法是换一个工具链或者从板子上拷对应的libc。报错error: numeric_limits is not a member of std这个是因为缺少#include limits是Qt5.14.2的一个已知问题。解决办法是在报错的文件里手动加上这个头文件或者打补丁。4.3 运行阶段的典型问题问题程序在板子上跑起来但窗口不显示检查板子上是否有framebuffer设备一般是/dev/fb0。如果没有需要配置内核开启framebuffer支持。另外Qt程序需要设置QT_QPA_PLATFORMlinuxfb环境变量。问题程序报错QFontDatabase: Cannot find font directory静态编译的Qt默认字体路径可能不对。解决办法是在板子上创建字体目录或者设置QT_QPA_FONTDIR环境变量指向字体所在目录。问题程序体积太大静态编译的产物确实大可以通过strip减小体积aarch64-linux-gnu-strip mainstrip后体积能减小一半左右。另外configure时的-optimize-size也能减小一些。4.4 常见问题速查表问题现象可能原因解决办法configure报xcb错误未禁用xcb加-no-xcb找不到libzsysroot缺zlib加-qt-zlib编译报bits头文件缺失sysroot路径不对检查-sysroot参数链接报__stack_chk_failglibc版本不匹配换工具链或拷libc程序跑起来无窗口无framebuffer配置内核或设QT_QPA_PLATFORM找不到字体字体路径不对设QT_QPA_FONTDIR体积太大静态链接strip或加-optimize-size提示编译Qt是个耗时活建议在虚拟机里做快照configure成功后先快照一次编译成功后再次快照。这样后面加模块或者改参数时不用从头再来。5. 静态编译后的部署与优化建议5.1 部署到板子上的注意事项静态编译的产物虽然不依赖Qt库但还是依赖系统的glibc和基本的系统库。所以板子上的glibc版本不能太低。我一般会在编译前确认板子的glibc版本然后选一个glibc版本不高于板子的工具链。部署时直接把可执行文件拷过去就行不需要带任何Qt的so文件。但如果你用了插件比如图片格式插件、平台插件这些插件如果是动态的还是需要拷过去。静态编译时可以把插件也静态链进去但需要在configure时加-static-runtime或者用Q_IMPORT_PLUGIN宏手动导入。#include QtPlugin Q_IMPORT_PLUGIN(QLinuxFbIntegrationPlugin) Q_IMPORT_PLUGIN(QJpegPlugin)然后在pro文件里加QTPLUGIN qlinuxfb qjpeg这样插件就会被静态链进可执行文件。5.2 体积优化的几个实用技巧静态编译的产物动辄几十MB对于存储紧张的嵌入式设备不太友好。除了strip和-optimize-size还有几个技巧。第一是禁用不需要的Qt特性。比如-no-feature-accessibility禁用无障碍支持-no-feature-printdialog禁用打印对话框这些都能减小体积。第二是用-ltcg开启链接时优化gcc的LTCG能去掉未使用的代码。但LTCG会显著增加编译时间而且有时候会引入奇怪的bug慎用。第三是编译时加-Os而不是-O2-Os优化体积。configure里的-optimize-size其实就是加-Os。第四是如果不需要QML就不要编译qtdeclarative那个模块很大。5.3 后续扩展与模块追加如果后面发现需要某个模块比如qtserialport不用重新编译整个Qt。进入qtserialport源码目录用已经安装的qmake重新配置编译就行。cd qtserialport /opt/qt5.14.2-aarch64-static/bin/qmake make -j24 make install这样只会编译qtserialport模块几分钟就搞定。前提是qtserialport的源码和Qt源码版本一致。如果需要重新编译qtbase比如改了configure参数那就要重新走一遍configure和make。这时候build目录不要删make会增量编译只重新编译改动的部分。我在实际项目里遇到过需要加qtserialport的情况当时就是单独编译的没动qtbase省了很多时间。所以建议一开始就把可能用到的模块都编译上免得后面补。5.4 关于Qt5.14.2的一些个人体会Qt5.14.2作为LTS版本稳定性确实好但有些小问题官方已经不修了。比如前面提到的numeric_limits头文件缺失还有qmake在某些情况下生成的Makefile里路径不对。这些都需要自己打补丁或者手动改。如果项目允许可以考虑Qt5.15.2那个版本对aarch64的支持更好而且修复了不少5.14的bug。但5.15不是LTS官方支持周期短。所以选哪个版本要看项目周期和维护成本。静态编译Qt是个体力活但走通一次之后后面就是复制粘贴的事。关键是configure参数要一次定对不然反复编译很浪费时间。我现在的做法是写一个shell脚本把configure命令和make命令都放进去下次直接跑脚本省得记参数。最后分享一个小技巧编译前先确认磁盘空间完整编译Qt需要至少20GB空间。我有一次编译到一半磁盘满了报了一堆莫名其妙的错排查了半天才发现是磁盘问题。所以提前df -h看一下别踩这个坑。
返回列表