Qt5.14.2 aarch64静态交叉编译实战:从配置到部署
2026/9/20 1:52:52 网站建设 项目流程

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.04,glibc是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。如果你做的是嵌入式HMI,qtbase加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,但如果你板子上没有GPU,eglfs也跑不起来,所以干脆全禁了,用最基础的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.1,Using 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_PLATFORM=linuxfb环境变量。

问题:程序报错QFontDatabase: Cannot find font directory

静态编译的Qt默认字体路径可能不对。解决办法是在板子上创建字体目录,或者设置QT_QPA_FONTDIR环境变量指向字体所在目录。

问题:程序体积太大

静态编译的产物确实大,可以通过strip减小体积:

aarch64-linux-gnu-strip main

strip后体积能减小一半左右。另外,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看一下,别踩这个坑。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询