Qt 5.14.2在aarch64上的静态交叉编译完整手册
2026/9/14 16:12:16 网站建设 项目流程

接触嵌入式Linux的朋友,估计都绕不开这个场景:手里一块aarch64架构的开发板,跑着精简版的文件系统,要在上面做一个带界面的工具,或者一套完整的设备控制程序。真到这一步,Qt基本就成了标配。可一旦用起来就会发现问题——目标板上没有Qt的运行库,动态编译的程序拷上去要么报缺so,要么版本对不上,每次都折腾半天。我自己踩过好几次这个坑之后,干脆花了点时间把Qt 5.14.2在aarch64上的静态交叉编译整个流程跑通了,从宿主机准备、工具链安装、mkspec配置,到最后编译部署,全链路走了一遍。这篇手册就是把那次搭建过程完整还原出来,所有命令、参数、报错和处理方式都按“从零开始”的标准写了,适合正在做嵌入式Qt开发、刚接触aarch64平台、或者被动态库依赖搞到头大的读者。

1. 为什么偏偏是Qt 5.14.2 + aarch64 + 静态编译

1.1 版本与架构的现实考量

先说我为什么选Qt 5.14.2。Qt的版本策略比较特殊,5.15之后开源版本主要走在线安装和商业维护,很多长期项目反而固化在5.12、5.14这些老版本上。5.14.2属于5.14系列的补丁版本,修复了不少累计问题,在嵌入式领域用得非常广。拿它做基线,至少有两点好处:第一,各种编译问题在网上几乎都有前人踩过坑解过答;第二,它支持到C++17,大部分第三方库不会因为语法标准太低而编译失败。

aarch64就更好理解了。现在市面上的主流开发板、工控机、国产化板卡基本都是ARM 64位平台,跑的都是aarch64架构的系统。x86上的交叉编译和aarch64不太一样,难点主要在工具链选择、库依赖匹配和mkspec配置上,比32位ARM时代要复杂一些。所以专门针对aarch64来说是有必要的。

1.2 静态编译和动态编译的本质差异

动态编译大家都很熟了:链接时只记录依赖的共享库名,运行时由系统加载器去找对应的.so文件。Linux桌面上的Qt应用基本都是这一套。好处是单个可执行文件小,升级Qt库时不用重新编译程序;代价是目标板上的Qt运行环境必须完整,缺一个libQt5Core.so.5程序就起不来。嵌入式精简系统里,这种“缺东少西”的问题几乎是日常。

静态编译则相反,整个Qt库和用到的第三方库代码全部链接到可执行文件里,最终产物只有一个二进制文件,拷到任何同架构的Linux系统上基本都能直接运行。为了直观理解,可以做个类比:动态编译相当于点外卖,商家把菜做好打包好,跑腿送到你手上,中间任何一个环节断了你都没得吃;静态编译相当于买预制菜包,自己加水加料一加热就行,缺点是包装重、占背包空间。放到嵌入式场景里,“背包空间”就是Flash和内存,一般几百MB的Flash空间完全能接受。

1.3 这套方案的适用场景

静态交叉编译不是万能药,但很适合下面几类情况:一是目标板出厂时文件系统很精简,不想为了Qt再把一堆运行库塞进rootfs;二是设备要交付给最终用户,应用以单一可执行文件的形式发布,部署简单、不容易被误删依赖;三是开发调试阶段,希望快速把程序传到板子上验证,不需要反复同步Qt库。但如果你的程序要动态加载第三方QML插件、或者频繁更换Qt底层库,静态方案就不太合适。

2. 搭建宿主环境与工具链

2.1 宿主机环境要求

交叉编译的第一步,是准备一台x86_64架构的Linux机器作为宿主机。我用的是Ubuntu 20.04,这个版本的glibc、gcc版本和Qt 5.14.2配合得比较顺畅。如果你用更新的Ubuntu 22.04或24.04,问题也不大,但要注意系统自带的gcc如果太新(比如gcc 12/13),某些老版本三方库可能出现编译告警或错误,排查起来多花时间。

硬件方面,编译Qt是一件比较吃资源的事。如果只编译Qt基础模块(qtbase),建议内存不小于8GB,磁盘剩余空间不少于20GB。如果后续要加WebEngine这类重量级模块,内存16GB起步,磁盘至少留50GB。编译过程中会生成大量临时文件和中间产物,磁盘空间不够的话,报错会很突然,而且不容易察觉是磁盘满了。

2.2 安装aarch64交叉编译工具链

Ubuntu仓库里现成的aarch64交叉工具链是gcc-aarch64-linux-gnu,安装很简单:

sudo apt update sudo apt install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu

装完后确认一下版本:

aarch64-linux-gnu-gcc -v

正常情况下会输出gcc version 9.x或者10.x。这套工具链虽然版本不算新,但编译Qt 5.14.2足够了。需要注意的是,工具链版本会决定目标程序的glibc版本要求。Ubuntu 20.04仓库里这套工具链编译出的程序,通常在glibc 2.31以上的系统上都能跑。如果你的目标板系统非常老,glibc版本很低,就要考虑手动编译一套更老的交叉工具链,或者用Linaro提供的预编译版本,这个坑在后面的问题排查部分我会再展开。

2.3 宿主机的依赖库处理

很多人栽在依赖库这一步。宿主机的库几乎都是x86架构的,不能直接被aarch64的交叉编译链使用。所以配置Qt时,最核心的思路是:尽量关闭不需要的模块,尽量使用Qt自带的第三方库源码,避免去交叉编译一堆外部依赖。

Qt源码里其实自带了一部分三方库,比如libpng、libjpeg、zlib、freetype、pcre这些,都放在qtbase/src/3rdparty目录下。配置时可以用-qt-zlib、-qt-libpng、-qt-libjpeg、-qt-freetype、-qt-pcre这些参数,让Qt编译时直接编译并静态链接这些自带库。这样一来,整套依赖链就闭环在Qt源码内部了,外部环境再乱也不影响。唯一要小心的是许可证问题,如果产品是商业闭源软件,得看清楚这些三方库的License是否允许静态链接和闭源分发。

3. 交叉编译必须要懂的mkspec配置

3.1 理解qtbase/mkspecs结构

Qt的构建系统不像普通autotools项目那么简单,它通过mkspec来定义目标平台的特征。mkspec可以理解为一份给qmake看的“平台描述文件”,里面写了编译器、链接器、归档器、编译选项、链接选项等一堆变量。

Qt 5.14.2源码解压后,在qtbase/mkspecs目录下能看到大量以平台命名的子目录,比如linux-g++、linux-arm-gnueabi-g++、linux-aarch64-gnu等等。aarch64对应的就是linux-aarch64-gnu这个目录。进去看一眼qmake.conf:

cat qtbase/mkspecs/linux-aarch64-gnu/qmake.conf

里面关键变量是QMAKE_CC、QMAKE_CXX、QMAKE_LINK。默认配置指向aarch64-linux-gnu-gcc、aarch64-linux-gnu-g++这些命令。如果你的工具链前缀不是aarch64-linux-gnu-,就需要自定义一份mkspec,或者在configure时通过-device-option指定交叉编译器前缀。

3.2 自定义mkspec的操作方法

工具链前缀不一致的情况其实挺常见。比如有些厂商提供的工具链名字是arm-linux-gnueabihf-gcc,或者aarch64-none-linux-gnu-gcc,这时候直接配置现有mkspec就会失败。最省事的做法是复制现有的mkspec目录,改成自己的名字,然后修改里面的qmake.conf:

cp -r qtbase/mkspecs/linux-aarch64-gnu qtbase/mkspecs/linux-aarch64-myboard

再编辑qmake.conf,把编译器相关的变量改成自己的工具链前缀:

QMAKE_CC = aarch64-none-linux-gnu-gcc QMAKE_CXX = aarch64-none-linux-gnu-g++ QMAKE_LINK = aarch64-none-linux-gnu-g++ QMAKE_LINK_SHLIB = aarch64-none-linux-gnu-g++ QMAKE_AR = aarch64-none-linux-gnu-ar cqs QMAKE_OBJCOPY = aarch64-none-linux-gnu-objcopy QMAKE_STRIP = aarch64-none-linux-gnu-strip

然后configure时用-xplatform linux-aarch64-myboard指定即可。说实话,大部分情况下直接使用自带的linux-aarch64-gnu就够用了,自定义只是备用手段。

3.3 编译器检测与sysroot的理解

还有个容易被忽略的问题是sysroot。交叉编译时,编译器和链接器需要知道目标系统的头文件和库文件在哪里。如果目标板有一个完整的rootfs目录,可以在configure时用-sysroot参数指定:

-sysroot /path/to/your/rootfs

指定后,编译器会在rootfs的/usr/include、/usr/lib里找头文件和库。Qt静态编译如果不依赖太多外部库,不指定sysroot也能编过,因为自带的三方库会直接编译进Qt。但如果你想启用某些系统库(比如libinput、libudev),就必须提供sysroot,否则Qt的configure阶段会直接检测不到这些库。

configure阶段Qt会自己编译一些小的C++测试程序,用来检测编译器能不能正常工作、某些特性是否支持。如果工具链配置错了,这一步就会报出“Cannot run C++ programs”之类的错误。我看到过最典型的失败案例,就是没指定-xplatform,导致Qt用了宿主的g++去跑编译测试,测试程序当然能编译执行,但后续真正交叉编译时所有目标文件都是x86架构的,根本链接不过去。

4. Qt源码获取与configure配置实操

4.1 获取Qt 5.14.2源码包

Qt官网的archive目录提供了所有历史版本的源码包。5.14.2对应的是qt-everywhere-opensource-src-5.14.2.tar.xz。下载命令:

wget https://download.qt.io/archive/qt/5.14/5.14.2/qt-everywhere-opensource-src-5.14.2.tar.xz

如果官网下载慢,可以用国内镜像源,比如清华TUNA镜像。下载完成后解压:

tar xf qt-everywhere-opensource-src-5.14.2.tar.xz cd qt-everywhere-opensource-src-5.14.2

源码包体积大约500MB左右,解压后接近2GB,确认磁盘空间够再操作。

4.2 依赖库的处理原则

进入源码目录后,第一件事不是急着configure,而是想清楚需要哪些模块。5.14.2默认会编译一大堆模块,包括Qt WebEngine、Qt Charts、Qt Data Visualization等。很多模块在嵌入式场景根本用不到,强行编译只会拉长编译时间、增加失败概率。典型的“能跳就跳”列表有:

-skip qtwebengine -skip qtcharts -skip qtdatavis3d -skip qtdoc -skip qtgamepad -skip qtnetworkauth -skip qtpurchasing -skip qtscript -skip qtvirtualkeyboard -skip qtwinextras

如果目标板连X11都没有,还可以把qtx11extras也跳掉。这里的原则是做减法,功能越多依赖越多,静态编译的依赖链通常经不起太多功能叠加。

4.3 configure参数逐个解析

下面是我实际跑通过的完整configure命令:

./configure \ -prefix /opt/qt5.14.2-aarch64-static \ -extprefix $HOME/qt5.14.2-aarch64-static \ -xplatform linux-aarch64-gnu \ -release -static -opensource -confirm-license \ -nomake examples -nomake tests \ -skip qtwebengine -skip qtcharts -skip qtdatavis3d \ -skip qtdoc -skip qtgamepad -skip qtnetworkauth \ -skip qtpurchasing -skip qtscript -skip qtvirtualkeyboard \ -skip qtwinextras -skip qtx11extras \ -qt-zlib -qt-libpng -qt-libjpeg -qt-freetype -qt-pcre \ -no-opengl -no-xcb -linuxfb \ -device-option CROSS_COMPILE=aarch64-linux-gnu-

逐个说下关键参数:

-prefix和-extprefix:prefix是目标板上安装Qt的路径,其实是写入qmake配置里的路径,如果程序里通过qmake获取Qt路径,会影响它的运行逻辑。extprefix是宿主机上的实际安装路径。二者可以不一样,这样既能在宿主机上找到编译产物,又不影响目标板上的路径习惯。如果懒得区分,两个设成一样也没问题。

-static:这个参数决定Qt编译成静态库。生成的库文件是libQt5Core.a这种格式,链接时直接打包进可执行文件。不加这个参数,配置出来的就是动态库方案。

-release:只编译release版本。debug版本的库体积会翻好几倍,静态编译时尤其明显,一个debug版的静态库可能有几百MB,链接出来的程序也大得离谱。

-opensource -confirm-license:表示开源版许可并确认同意。商业用户有其他选择,这里不展开。

-nomake examples -nomake tests:不编译示例和测试代码。这两项纯属浪费时间,嵌入式场景根本不需要。

-qt-zlib、-qt-libpng等:使用Qt自带的第三方库源码。这里要特别注意,即使宿主机上装了zlib、libpng,交叉编译也基本用不上,用自带库是最省心最不容易出错的方式。

-no-opengl -no-xcb:禁用OpenGL和X11支持。如果你的目标板没有GPU,也没有运行X11桌面,这两个参数能帮你省掉大量麻烦。否则configure会去检测OpenGL的头文件库文件,没有sysroot时基本会检测失败或者链接报错。

-linuxfb:启用linuxfb平台插件。linuxfb是Qt在Linux framebuffer上直接绘图的后端,不需要窗口系统,适合嵌入式设备。它通过读写/dev/fb0来显示内容,实现简单、依赖少。如果板子上有DRM/KMS,也可以启用-eglfs,但linuxfb对纯软件渲染最友好。

-device-option CROSS_COMPILE=aarch64-linux-gnu-:指定交叉编译器前缀。这一步很关键,如果不指定,Qt部分模块的编译脚本会调用gcc而不是aarch64-linux-gnu-gcc,导致编译出x86架构的二进制透明地混进链路里,最后链接阶段报一堆诡异错误。

4.4 make与make install

configure成功结束会输出类似“Qt is now configured for building”的信息。接着就可以编译了:

make -j$(nproc)

-j参数用来并行编译。8核机器大概20到40分钟能编完基础模块,如果跳过的模块不够多,可能要一两个小时。编译过程中如果报错,不要急着改全局配置,先看具体报错位置。常见的错误类型是缺少某些系统头文件,解决办法基本就是禁用对应模块或增加自带的第三方库支持。

编译完成后安装:

make install

安装产物会出现在之前指定的-extprefix目录里。看一眼目录结构:

ls $HOME/qt5.14.2-aarch64-static/lib

里面应该能看到libQt5Core.a、libQt5Gui.a、libQt5Widgets.a等静态库文件。

5. 静态编译的坑与链接问题

5.1 静态链接时的Qt插件机制

静态编译踩过的第一个大坑,十有八九是Qt的插件系统。Qt的很多功能不是直接编译进核心库的,而是通过插件机制运行时加载。平台插件(如linuxfb、eglfs)、图片格式插件(如JPEG、GIF)、字体插件都是典型例子。动态编译时,程序会从plugins目录下加载.so插件;静态编译时这些插件被打进静态库,但链接器不会自动把用不到的代码链到可执行文件里,必须显式声明“我要用哪些插件”。

如果忽略这一步,运行程序时会直接报类似“Could not find the Qt platform plugin 'linuxfb'”的错误。解决办法是在项目的.pro文件里加:

QTPLUGIN += qlinuxfb qjpeg qgif

或者在main.cpp里手动导入插件:

#include <QtPlugin> Q_IMPORT_PLUGIN(qlinuxfb) Q_IMPORT_PLUGIN(qjpeg)

这两种方式是等效的,我一般推荐用QTPLUGIN方式,改起来方便,不用动代码。但如果你用CMake构建项目,没有QTPLUGIN机制,就得用Q_IMPORT_PLUGIN这条路线。这是静态编译下“程序能编译过但运行不了”的最常见原因,一定要提前处理。

5.2 常见链接错误与解决办法

链接阶段最常见的错误就是undefined reference。出现这种问题,基本是依赖库没链全。Qt静态库本身内部依赖比较复杂,比如libQt5Gui.a依赖libQt5Core.a和libpng、libjpeg等,手动用g++直接链接时,库的顺序都有讲究。如果你用qmake生成的Makefile,不用操心这个问题,qmake会按正确顺序展开。如果是自己写的构建系统,就得注意先高层的库后低层的库、重复链接用--start-group和--end-group包裹这些细节。

另一个高频错误是“X11 headers not found”。出现这个基本就是没加-no-xcb,或者你的编译环境中存在X11相关配置干扰。静态编译时如果不需要X11,直接在configure阶段禁掉是最省心的。

还有一类问题出现在运行时,比如报“GLIBC_2.34 not found”。这是交叉工具链对应的glibc版本比目标板系统高导致的。解决办法要么换一套更老的交叉工具链,要么在目标板的rootfs里升级glibc。升级glibc有风险,做嵌入式的人都懂,能不动系统尽量不动系统,所以更建议从工具链侧解决。

5.3 编译选项与体积控制

静态编译的最直观代价就是可执行文件体积。一个最简单的Qt Widgets程序,静态编译出来轻松超过15MB,如果模块开得多,30MB以上也不奇怪。体积控制可以从几个方向入手:

strip是第一步。静态编译后不strip的二进制里面全是调试符号和符号表,体积可能是strip之后的两倍。命令很简单:

aarch64-linux-gnu-strip your_binary

一般来说,strip之后的体积大概能缩水30%到40%。如果对体积有更极致的要求,可以试试Qt的-optimize-size参数,让编译器针对体积优化而不是速度优化。LTO(链接时优化)也能减小体积,但会显著增加编译时间。我自己的经验是,对于嵌入式设备上的Qt程序,strip加上合理裁剪模块就够了,没必要为了几个MB增加排错复杂度。

6. 在目标板上部署与验证

6.1 最小部署验证流程

编译完成后,用一个简单的Qt Widgets程序验证整个链路是否正常。创建一个hello.pro:

QT += widgets SOURCES += main.cpp TARGET = hello

main.cpp写一个最简单的窗口:

#include <QApplication> #include <QLabel> int main(int argc, char *argv[]) { QApplication app(argc, argv); QLabel label("Hello aarch64 Qt!"); label.show(); return app.exec(); }

编译:

$HOME/qt5.14.2-aarch64-static/bin/qmake hello.pro make

编译出来的hello就是可执行文件。用file命令确认架构:

file hello

输出应该是“ELF 64-bit LSB executable, ARM aarch64”。

然后把程序传到目标板:

scp hello root@192.168.1.100:/root/

在板子上运行:

chmod +x /root/hello export QT_QPA_PLATFORM=linuxfb /root/hello

如果屏幕上出现“Hello aarch64 Qt!”窗口,说明Qt静态交叉编译全链路已经打通。

6.2 静态编译后运行时的注意事项

静态编译虽然消除了动态库依赖,但运行时的环境变量和硬件条件依然影响程序行为。最典型的是QT_QPA_PLATFORM环境变量。如果程序启动时报找不到platform插件,多半是这个环境变量没设置,或者设置的平台插件和编译时启用的插件不匹配。linuxfb后端还需要系统存在可用的framebuffer设备/dev/fb0,如果设备节点不存在,程序会直接启动失败。

字体也是嵌入式Qt的隐藏炸弹。默认情况下Qt会到系统目录找字体,精简文件系统里可能没有任何字体文件,运行后界面全是方框。解决方式有两种:一是把编译好的字体文件拷到板子上,设置QT_QPA_FONTDIR环境变量指向字体目录;二是直接用Qt的资源系统把字体打包进可执行文件。我建议第二种方式,一劳永逸,避免再出乱子。

权限问题也值得提一嘴。直接读写/dev/fb0通常需要root权限,如果程序以普通用户身份运行,要确保用户有相应权限,或者通过udev规则给设备节点授权。

7. 常见问题与排查技巧实录

7.1 问题速查表

问题现象可能原因解决方法
configure报“Cannot run C++ programs”没有指定-xplatform,Qt用宿主gcc做测试检查configure命令是否包含-xplatform linux-aarch64-gnu
编译过程中某些文件用宿主gcc编译-device-option CROSS_COMPILE没设置在configure时加上交叉编译器前缀
make报缺少png/jpeg/zlib头文件第三方库依赖断裂使用-qt-libpng -qt-libjpeg -qt-zlib参数让Qt使用自带库
链接报一堆undefined reference插件没导入,或依赖库顺序不对用QTPLUGIN声明插件;使用qmake生成的Makefile
程序运行时报“Could not find the Qt platform plugin”静态编译没有显式导入平台插件在.pro里加QTPLUGIN += qlinuxfb,或加上Q_IMPORT_PLUGIN
程序运行时无法打开/dev/fb0设备节点不存在或权限不足检查设备节点、用户权限或使用root运行
运行时字体全是方块目标板没有中文字体用QT_QPA_FONTDIR设置字体路径,或通过qrc打包字体
运行时报GLIBC版本不匹配工具链的glibc比目标板系统新换用更低版本的交叉工具链,或升级目标板rootfs
configure检测不到部分特性缺少sysroot配置-sysroot参数指定目标板rootfs

7.2 排查经验

交叉编译的报错信息有时候很隐蔽。建议从一开始就养成看config.log的习惯。configure阶段的所有检测结果、编译尝试、失败原因都记录在源码根目录的config.log里。报错时直接打开日志搜error关键词,比盲目改参数高效得多。

编译过程中如果怀疑某个模块出了问题,可以单独进入该模块的目录编译。比如只编译qtbase:

cd qtbase make -j$(nproc)

这样能降低排查难度。等qtbase编译通过后,再回到顶层目录编译整个项目。

另外,静态编译成功后建议保留configure时生成的config.log和Makefile,后续如果出现奇怪问题,可以对照这些文件确认模块配置。

7.3 值得收藏的几条避坑经验

第一条,按需裁剪模块,不要图省事用默认全量配置。你用不到的qtwebengine模块能直接让你的编译时间翻倍,而且很有可能会在某个中间环节编译失败。

第二条,优先使用Qt自带的第三方库。交叉编译环境里,外部依赖是最容易出问题的地方。zlib、libpng等库直接让Qt自己编,能避开系统库版本不一致、头文件缺失、架构不匹配等一系列问题。

第三条,保持工具链和目标板系统的匹配。做一段时间的aarch64交叉编译就会体会到,x86开发环境上“编译过 = 能跑”的等式在嵌入式世界里是不成立的。工具链的版本决定了程序能跑在什么样的目标系统上,这块务必提前确认清楚。

写在最后的私人体会

这套流程我在不同项目里反复用了几遍,中间踩坑无数,甚至可以负责任地说,第一次不看任何资料直接配置绝大概率会翻车。但把整个链路理顺之后再回头看,核心其实就几个点:模块裁剪、mkspec选对、静态插件导入、目标板环境匹配。把这四个点抓住,Qt 5.14.2的aarch64静态交叉编译就不再是玄学,而是完全可以按图索骥的标准化操作。如果后续编译的是Qt 6,思路也基本一致,只是mkspec路径和部分参数名称会有调整。希望这份手册能帮你少走几次弯路,一次跑通。

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

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

立即咨询