接手的项目是个典型的嵌入式活儿:手里一块aarch64开发板,要在上面跑一个带界面的Qt程序,但开发机和构建机都是x86_64的服务器。刚开始走的是动态编译,生成的可执行文件倒是能跑,可一到目标板上就发现牵一发动全身——Qt库、依赖库、平台插件、字库、ssl库,缺一个就起不来。为了一台设备去配一套rootfs,实在折腾。最后狠下心把Qt 5.14.2整体改成aarch64静态交叉编译,一个static链接的可执行文件拷到板子上,拉起来就跑,干干净净。这篇文章就把从零搭建的完整过程写出来,从环境准备、sysroot构建、依赖库静态编译到configure参数逐个拆解,顺便把踩过的坑和排查思路也一并放进来,给后面要做Qt aarch64交叉编译的人一条能直接走通的路。
1. 整体方案设计与前置评估
1.1 为什么选Qt 5.14.2、aarch64和静态编译
先说版本选择。Qt 5.14.2是5.14分支的最后一个补丁版本,也是Qt官方放出的几个非常成熟的LTS之一。它处在Qt 5重架构和Qt 6之间的过渡期,很多老项目在这个版本上跑得很稳,而且它的目录结构、configure脚本体系还保留着Qt 5时代那种“源码包开箱即configure”的纯粹感。5.15之后虽然也有LTS,但很多源码模块的开放策略、安装方式都开始往在线安装器倾斜,对交叉编译没那么友好。你在网上搜“qt5.14.2离线安装包下载”能翻出大量帖子,也说明这个版本的存量需求一直没消退。不过这里要提醒一句,如果目标是交叉编译,不要用那个离线安装包,它给的是预编译好的x86物,不是源码;直接去官网archive拿qt-everywhere-opensource-src-5.14.2.tar.xz源码包,后面所有事情都好办。
再说aarch64。ARM 64位架构在现在的嵌入式板子、边缘网关、工控设备里已经是绝对主流,能力和生态都成熟。很多朋友是从32位ARM转过来的,原来编译armhf的工具链思路还留着,但你真去编aarch64的时候会发现,64位的寄存器、调用约定、PIC代码模型和32位时代完全不是一个量级,用老的思维容易在链接阶段被坑得晕头转向。
最后说静态编译。为什么非要静态?因为目标设备的环境经常不可控。我手上的板子rootfs是精简过的,没有包管理器,也没有现成的Qt运行库,如果我拿动态链接的Qt可执行文件过去,第一件事就是配LD_LIBRARY_PATH或做库的字节对齐排查,折腾半天还不一定齐。静态编译之后,Qt的core、gui、widgets、平台插件、依赖的zlib/png/jpeg/pcre全部打进了同一个可执行文件,部署的时候就一个文件,scp过去、赋执行权限、启动,非常干净。
1.2 交叉编译的基本链条:工具链、sysroot、qmake的关系
交叉编译的本质是在一台架构不同的机器上,生成另一种架构的机器码。对我们这个场景来说,宿主机是x86_64,目标机是aarch64,所以需要一个既能输出ARM64机器码、又能处理ARM64二进制格式的编译器工具链,也就是aarch64-linux-gnu-gcc/g++这一套。但这只是第一步,你写代码时会include系统的头文件,链接时会找系统的库文件,而这些文件和x86_64环境下完全不同。所以在交叉编译里必须引入一个叫sysroot的概念,其实就是一个装在宿主机上的“目标机根文件系统压缩包”,里面放着aarch64的/usr/include、/usr/lib和基础运行库,编译器在找头文件和库时会被--sysroot参数锁定到这个目录,不会去碰宿主机自己的x86_64文件。
Qt的构建又比普通autotools项目多一层“qmake体系”:它先用一串configure参数生成了目标平台的qmake,这个qmake知道目标平台的编译器、系统rootfs、构建选项,然后所有的模块(qtbase、qtdeclarative、qtquickcontrols等)都由qmake来驱动编译。所以对Qt来说,“交叉编译”能不能成,关键就在configure这一关有没有把正确的-xplatform、-sysroot、-prefix给出来,以及mkspecs里的qmake.conf是不是指向了正确的交叉工具链。很多新手栽在configure“成功”了,但编到一半发现用的还是宿主机的gcc,或者链接器跑到了x86库上,问题全出在这一层。
1.3 什么场景适合静态交叉编译
这些年类似“phantomjs aarch64下载”“nginx aarch64移植”这类搜索经常上热门,背后都是同一个诉求:新架构设备上等于一片荒漠,很多软件没有现成的aarch64二进制包,就算有,版本和系统库依赖也对不上,于是自己动手交叉编译就成了绕不开的基本功。nginx相对好一点,autotools/configure系只要指定好CC和交叉编译参数基本能过,而Qt因为模块多、插件多、依赖多,复杂度是高一个量级的,静态编译更是把难度再往上拉了一截。
我的建议是,在动手之前先做个评估,确认是不是真的需要静态。适合静态的场景有几类:一是目标板rootfs不统一、库管理混乱,没法保证每个设备都有Qt运行环境;二是要做单文件分发,比如把程序发给客户,或者放到一个很干净的容器/模拟器里,直接./myapp就跑;三是希望减少目标系统的库版本漂移问题,比如动态链接openssl时经常碰到板子上的ssl版本和开发机不一致,导致握手错乱。不适合的场景也有:如果你的程序重度依赖动态加载的so插件,静态反而会破坏插件机制;如果你申请了巨大的Qt全家桶(WebEngine、QML场景特别复杂、ICU全量),静态出来的二进制会膨胀到上百MB,编译时间也会让人崩溃;另外LGPL协议要求你发布目标文件(.o)或提供重新链接的手段,商用时要让人能拿到中间产物,这一点法务上要提前评估。
我自己实际评估下来,Widgets纯界面程序、Qt Quick简单场景、内置sqlite、不依赖浏览器内核的应用,静态化完全没有问题。如果你要做WebEngine之类,我劝你最好放弃静态方案,不光体积大,链接时的怪问题多到怀疑人生。
2. 环境准备与工具链搭建
2.1 宿主机基础依赖与源码包准备
这次实操的宿主机是Ubuntu 20.04 x86_64,8核16G内存。先说一个容易忽略的点:很多人的服务器是CentOS或者精简过依赖的Linux发行版,configure脚本要求的一些基础工具可能装不齐,你在做任何操作前最好把编译链路的底子打牢。Ubuntu系直接一条命令装大部分依赖:
sudo apt update sudo apt install -y build-essential \ python2 python \ bison flex \ libgl1-mesa-dev libx11-dev libxkbcommon-dev \ libfontconfig1-dev libfreetype6-dev \ perl gperf这里的libgl1-mesa-dev和libx11-dev是给configure检测宿主图形环境用的。不过要记住,我们是交叉编译,宿主机上装这些是为了让configure的检测脚本跑起来更顺利,并不是让Qt把它们编进去。真正进到aarch64目标里的图形库依赖,应该在sysroot里单独准备。
源码包直接去Qt官网的archive目录下载:
wget https://download.qt.io/archive/qt/5.14/5.14.2/qt-everywhere-opensource-src-5.14.2.tar.xz下载完建议顺手校一下hash,官方archive页面会给SHA1值,这一步虽然没人愿意做,但能省去后面一堆不明所以的编译错误。解压后目录名字很长,我一般直接改名成qt5142,后续操作方便。
2.2 交叉编译工具链安装与验证
Ubuntu 20.04/22.04都提供aarch64交叉工具链,直接装:
sudo apt install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu装完后验证一下:
aarch64-linux-gnu-gcc -v得到类似gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04)这样的输出就说明没问题。这里要注意工具链版本尽量和目标的libc版本匹配,不要用一个gcc 12的新工具链去链一个老旧的arm64 rootfs,很容易在链接阶段因为GLIBC_2.34 not found之类的问题翻车。如果系统自带工具链版本不合适,可以自己下载Linaro或者ARM官方工具链放到/opt,然后手动把/opt/toolchain/bin加到PATH。
工具链里除了gcc/g++之外,后面会用到的几个二进制也得确认存在:aarch64-linux-gnu-ar、aarch64-linux-gnu-ranlib、aarch64-linux-gnu-strip、aarch64-linux-gnu-ld。这些是静态编译时合成库文件和裁剪符号的关键工具,Qt的qmake.conf里会直接调用它们。
2.3 构建aarch64的sysroot
sysroot是整个交叉编译里最重要的地基。最简单粗暴的方法是找一块同架构的板子或已有的根文件系统,直接把/usr、/lib整体拷到宿主机上,做成一个/opt/aarch64-sysroot目录。但很多时候手头没有现成rootfs,那就用debootstrap自造一个arm64的最小rootfs。Ubuntu系操作如下:
sudo apt install debootstrap sudo mkdir -p /opt/aarch64-sysroot sudo debootstrap --arch=arm64 --variant=minbase focal /opt/aarch64-sysroot http://ports.ubuntu.com/ubuntu-ports/等debootstrap跑完,rootfs里就有了aarch64的/usr/include、/usr/lib/aarch64-linux-gnu、/lib/aarch64-linux-gnu。然后为了qtconfigure找库方便,我一般会把多目录结构的库路径做个软链接,把/usr/lib/aarch64-linux-gnu映射到/usr/lib的位置:
sudo ln -s /opt/aarch64-sysroot/usr/lib/aarch64-linux-gnu /opt/aarch64-sysroot/usr/lib64 sudo ln -s /opt/aarch64-sysroot/lib/aarch64-linux-gnu /opt/aarch64-sysroot/lib64这个软链接不建也行,但后面的configure如果用了-sysroot且要求库路径清晰,链接期找库会顺畅很多。另外还要确保rootfs里有libgcc_s、ld-linux-aarch64.so.1这些基础运行库,debootstrap默认会带,如果你是从板子上拷的rootfs,记得检查一下/lib/ld-linux-aarch64.so.1是否存在。
2.4 下载Qt源码时顺便说下离线包的选择
好多朋友在网上刷到“qt5.14.2离线安装包下载”就下了个几百MB的.run离线安装器,点开以后是个图形界面,安装完只是往宿主机塞了一堆x86_64的Qt库,想拿来做aarch64交叉编译完全没有意义。官方离线安装器里的库全部是预编译好的X86物,而且Qt从5.15开始对离线安装包的发放越来越保守,5.14.2算是还能比较顺畅下载的版本。做交叉编译请认准qt-everywhere-opensource-src-5.14.2.tar.xz,只有它会同时包含qtbase、qtdeclarative、qtmultimedia、qtquickcontrols等所有模块的源码,配合-skip参数可以按需裁剪。Qt 5.14.2的源码包大概在500MB左右,解压后占1.5GB,编译时要保证磁盘空间不少于20GB。
3. 依赖库静态编译:把Qt的地基补齐
3.1 zlib、libpng、libjpeg这些基础库到底要不要单独编
很多人一上来就想把zlib、libpng、libjpeg一个个拿交叉工具链编译一遍,其实在Qt 5.14.2里,有个偷懒但非常稳的办法:configure阶段用-qt-zlib、-qt-libpng、-qt-libjpeg,让Qt直接编译它内置的第三方库。这些库的源码就打包在Qt源码树的qtbase/src/3rdparty下面,Qt的qmake会负责用交叉工具链把它们编成静态版本,再链到Qt库和最终应用里,整个过程自动处理,避免了自己编译第三方库版本和Qt预期不一致的坑。
什么情况下需要单独编译这些基础库呢?如果你目标机器上的库版本会和Qt内置版本冲突,或者你需要zlib支持特定的压缩特性、libpng需要APNG扩展,那就得先交叉编译这些库再让Qt用-system-zlib这类参数去链接。对绝大多数Widgets程序来说,-qt-li*就够了。我自己的经验是:能交给Qt内置的就别自己编,省下的时间可以多排查两个真正的坑。
3.2 openssl静态编译与Qt集成
Qt 5.14.2里启用openssl支持通常用-openssl或-openssl-linked。如果你用-openssl,Qt是运行时动态找系统openssl库,这对动态编译没问题,但静态编译时强烈建议用-openssl-linked,它会在链接阶段把libcrypto.a和libssl.a直接链进可执行文件。
先交叉编译openssl。openssl 1.1.1是Qt 5.14.2最稳妥的选择,1.1.1l或者1.1.1t都行,别直接用openssl 3.0,Qt 5.14.2的代码没充分适配3.0,后面跑起来容易握手失败或者版本识别异常。编译命令是这样:
wget https://www.openssl.org/source/openssl-1.1.1t.tar.gz tar xf openssl-1.1.1t.tar.gz && cd openssl-1.1.1t ./Configure linux-aarch64 no-shared no-tests \ --prefix=/opt/aarch64-sysroot/usr \ --cross-compile-prefix=aarch64-linux-gnu- make -j$(nproc) sudo make install这里几个参数要解释一下。linux-aarch64是openssl的Configure脚本里定义好的目标平台,它会自动选择aarch64的汇编优化代码,选择一个不对的target会导致后面编译莫名其妙的报错。no-shared决定只生成静态库,这是静态编译的大前提。--cross-compile-prefix会自动拼接出aarch64-linux-gnu-gcc、aarch64-linux-gnu-ar等工具,比较省事。装进去后,sysroot里就有/opt/aarch64-sysroot/usr/include/openssl/*.h和/opt/aarch64-sysroot/usr/lib/libcrypto.a、libssl.a。
装完后最好用一个简单的小程序快速验证下openssl能否交叉编译通过,不然等Qt整套编到一半才发现openssl库有问题,排查成本就高了。验证方法就是写个aes加密的demo,用aarch64-linux-gnu-gcc静态链接跑一下编译,检查能否生成ARM64可执行文件。
3.3 sqlite、pcre、harfbuzz等模块的取舍
Qt的sqlite模块如果业务用得多,可以单独编一个sqlite3静态库。Qt也带了-qt-sqlite的选项,但这个选项只是让Qt编它的内置sqlite,在链接时虽然是静态,但如果你之后想把sqlite3直接暴露给业务层使用,建议还是自己交叉编译一个最新sqlite3并放到sysroot。方法不复杂:下载amalgamation源码包,执行:
./configure --host=aarch64-linux-gnu --prefix=/opt/aarch64-sysroot/usr \ --disable-shared --enable-static make -j$(nproc) sudo make installpcre和harfbuzz直接让Qt内置就好,-qt-pcre、-qt-harfbuzz是Qt的默认项之一,不需要额外处理。ICU这块我强烈建议直接-no-icu,因为ICU静态链接体积非常可观,而且Qt 5.14.2里对ICU的依赖主要影响QTextBoundaryFinder和Unicode特性,对常见的中文和英文界面影响很小,剪掉它能让最终二进制小不少。
3.4 相关工具链的经验:所有的依赖库都要保持“静态一致”
我在这个阶段最想强调的一个原则是:所有进入Qt静态链接链路的库,都必须用同一套交叉工具链、同一个sysroot编译出来的静态版本,混合使用x86_64的.a文件或动态库,会让最后的链接阶段出现各种奇奇怪怪的符号错误。比如动态链接时你可以只给.a文件放在搜索路径里,但静态链接时一定要保证这个.a是为aarch64编译的,而且libc、libm、libdl、libpthread这些基础库最好全部来自同一套编译器运行时,不要并着两个不同版本的gcc产物。这个坑我当时就踩过:zlib用Linaro工具链编的,openssl用Ubuntu自带工具链编的,最后Qt的ARM64可执行文件在目标板上偶发crash,查了一天才意识到是不同编译器运行时导致的浮点ABI细节差异。
4. Qt源码configure配置与静态编译实操
4.1 修改mkspecs里的qmake.conf
Qt源码里给aarch64交叉编译提供的基础模板在qtbase/mkspecs/linux-aarch64-gnu-g++。第一次用Ubuntu自带的工具链时,这个模板基本不用动,qmake会自动检测系统PATH里的aarch64-linux-gnu-gcc。但我建议还是打开qmake.conf确认一下里面的编译器前缀:
QMAKE_CC = aarch64-linux-gnu-gcc QMAKE_CXX = aarch64-linux-gnu-g++ QMAKE_AR = aarch64-linux-gnu-ar cqs QMAKE_LINK = aarch64-linux-gnu-g++ QMAKE_LINK_SHLIB = aarch64-linux-gnu-g++ QMAKE_RANLIB = aarch64-linux-gnu-ranlib QMAKE_STRIP = aarch64-linux-gnu-strip如果你的工具链在/opt下且命令不带前缀,或者版本名特殊,这里就需要改,最好把完整路径写死,避免qmake在PATH里找不到。静态编译时还有一个关键点:QMAKE_LINK_SHLIB这个变量在静态环境下不会触发,但建议保持指向交叉g++,以防Qt某些内部模块还是试图做动态链接。
4.2 configure参数逐个拆解
configure是Qt交叉编译最核心的一关,下面给出一份我实测能跑通的完整参数表:
mkdir -p /opt/qt-aarch64-static-build && cd /opt/qt-aarch64-static-build ../qt5142/configure \ -release \ -opensource \ -confirm-license \ -static \ -prefix /opt/Qt5.14.2/static-aarch64 \ -xplatform linux-aarch64-gnu-g++ \ -sysroot /opt/aarch64-sysroot \ -no-feature-xcb \ -no-opengl \ -no-gtk \ -no-sm \ -no-icu \ -no-dbus \ -qt-zlib \ -qt-libpng \ -qt-libjpeg \ -qt-pcre \ -qt-harfbuzz \ -qt-freetype \ -openssl-linked \ -nomake examples \ -nomake tests \ -skip qtwebengine \ -skip qtwebview \ -skip qtnetworkauth \ -no-compile-examples逐项解释一下,看完你就知道为什么这么配:
-release:编译release版,不要debug,能显著缩小静态库体积。-opensource -confirm-license:跳过license交互,编译过程自动确认开源版本。-static:整个Qt以静态方式构建,最终产出.a库。-prefix /opt/Qt5.14.2/static-aarch64:安装路径,之后qmake、库、头文件都会放这儿。-xplatform linux-aarch64-gnu-g++:告诉configure目标平台是aarch64,并且从对应的mkspecs读取编译规则。-sysroot /opt/aarch64-sysroot:把目标系统的根目录指给编译器,交叉编译的头文件和库定位全靠它。-no-feature-xcb:砍掉XCB平台插件。xcb插件是Qt在Linux桌面环境下最常用的平台后端,但它依赖一堆X11库和xcb库,静态链接xcb是出了名的麻烦,除非你的目标系统完整具备X11/XCB库,否则第一次交叉编译建议直接禁掉。后面需要用图形界面时,可以选linuxfb或eglfs。-no-opengl -no-gtk -no-sm:关闭OpenGL和GTK,进一步减少系统库依赖。目标板如果不做GPU渲染,这些选项能让链接无限顺畅。-no-icu:砍掉ICU,减小体积,也避免ICU全量静态编译带来的时间成本。-no-dbus:如果目标板上没有DBus服务,这个选项能少一个依赖;如果应用需要DBus,则不能关。-qt-zlib -qt-libpng -qt-libjpeg -qt-pcre -qt-harfbuzz -qt-freetype:全部使用Qt内置的开源库,保证不用外部预先编译,也是省事的关键。-openssl-linked:链接上一节我们交叉编译进sysroot的静态openssl库。-skip qtwebengine/qtwebview/qtnetworkauth:这几个模块要么体积巨大要么依赖复杂,直接跳过能少编译几十分钟。
还有一个小参数容易被忽略,如果你的目标系统内存小或只是命令行应用,可以加上-no-gui——但既然做的是图形应用,就不要加。另外如果不想让Qt去检测X11等宿主机特性,加-no-xcb和-no-xkbcommon也行,注意-no-feature-xcb是qmake特性层,-no-xcb是configure的自动检测层,两者可以同时用。
4.3 configure报错怎么读
configure第一次跑完,如果没报错,会输出一段类似“Qt is now configured for building”的提示,同时显示你选择的模块列表。如果报了错,最常见的两种是:
- 某个依赖库检测不到,说“Could not find the libssl headers”之类,这时先检查
-sysroot路径下有没有openssl头文件,以及libcrypto.a是否真的存在。 - 平台特性检测失败,比如检测xcb相关的头文件失败,这时别死磕,看看是不是被
-no-feature-xcb覆盖了,如果还报就说明configure仍试图检测该模块,可以再叠加-no-xcb。
configure如果中间出现“The specified system/compiler is not supported”之类的错误,多半是-xplatform写错mkspecs名字,Qt 5.14.2里这个模板的准确路径是qtbase/mkspecs/linux-aarch64-gnu-g++,拼写不能错,不能写成linux-aarch64或linux-arm64。
4.4 开始编译:多线程选择与资源监控
configure成功之后,就可以开始编译:
make -j$(nproc)不过8核机器上全开8线程编译Qt会非常吃内存,尤其是C++模板编译最疯狂时每个编译进程能占1-2GB,16G内存的机器开8线程很容易OOM。我实测建议make -j4或-j6,如果只有8G内存,就老实-j4,慢一点但稳。编译时间取决于模块保留数量,像我这样裁剪掉webengine等模块、保留qtbase、qtdeclarative、qtquickcontrols这些基础模块,i7-9700级别的机器大概需要40到60分钟。等的时候可以随时用htop盯一下CPU和内存,看到内存快溢出就Ctrl+C,换小线程数重新make,Qt的构建系统支持增量编译,不用担心前功尽弃。
编译结束后安装:
make install安装到/opt/Qt5.14.2/static-aarch64后,检查一下目录里是否出现了lib/libQt5Core.a、lib/libQt5Widgets.a、lib/libQt5Gui.a这些静态库,另外bin/qmake也应该出现。这个qmake是aarch64交叉版的,后面我们写应用时要用它来生成Makefile。
4.5 交叉编译一个hello窗口程序验证
库都装好后,用一个最小Widgets程序验证整个工具链是否打通。
先建一个main.cpp:
#include <QApplication> #include <QLabel> int main(int argc, char *argv[]) { QApplication app(argc, argv); QLabel label("hello aarch64 qt"); label.resize(320, 120); label.show(); return app.exec(); }再写一个工程文件hello.pro:
QT += widgets TARGET = hello TEMPLATE = app CONFIG += console c++11 SOURCES += main.cpp然后去/opt/Qt5.14.2/static-aarch64/bin调用qmake:
/opt/Qt5.14.2/static-aarch64/bin/qmake hello.pro make -j4编译完成会生成一个hello可执行文件。用file命令看它的属性:
$ file hello hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, with debug_info, not stripped看到statically linked这个字段就说明这一步成功了。再用ldd看一下:
$ ldd hello not a dynamic executable可以把它通过scp传到板子上,如果前面对图形做的是linuxfb,那么跑./hello -platform linuxfb就能看到窗口。如果你的板子有EGL/DRM环境,用-platform eglfs效果更好。
5. 常见问题与排查技巧实录
5.1 链接阶段一堆undefined reference
这是静态交叉编译最让人崩溃的报错,一长串undefined reference to 'xxx'。常见位置是有ssl相关符号找不到,或者pthread、dl、m、c基础库符号找不到。
排查思路分三步走。第一步,确认所有库都是aarch64的静态库,可以用aarch64-linux-gnu-ar t libQt5Network.a | head看看库内容;第二步,确认链接顺序,静态库之间的依赖有顺序要求,被依赖的库要放后面,Qt生成链接行时通常已经处理好,但如果你自己手动加库,得注意;第三步,确认openssl库路径是否找对,-L/opt/aarch64-sysroot/usr/lib -lcrypto -lssl要能在sysroot里找到.a文件。用-v看Qt实际执行的链接命令,是最直接的排错方式。
5.2 sysroot里找不到头文件或库文件
configure阶段或者编译阶段报fatal error: X11/Xlib.h: No such file or directory,基本就是sysroot不完整。debootstrap出来的base系统不会带开发包,你必须用apt-get install往sysroot里补。比如:
sudo chroot /opt/aarch64-sysroot apt update sudo chroot /opt/aarch64-sysroot apt install -y libx11-dev libxkbcommon-dev libfontconfig1-dev libfreetype6-dev当然,如果你按前面的方案直接禁用了xcb和opengl,这个错误几乎不会出现。
5.3 静态编译后平台插件起不来
静态编译时,Qt平台插件(linuxfb、eglfs、xcb等)默认会被编译成静态库并注册到Qt内部。如果你的静态程序放到板子上跑的时候提示“Could not find the Qt platform plugin 'linuxfb'”,说明插件没被链接进来。解决办法是在.pro文件里手动导入插件:
QTPLUGIN += qlinuxfb或者直接在代码里:
#include <QtPlugin> Q_IMPORT_PLUGIN(qlinuxfb)这个坑几乎每个做Qt静态编译的人都会遇到,因为动态编译时插件so在目录里自动能找到,静态编译时插件必须被显式链接进去。Qt 5.14.2里,常见插件的导入名包括qlinuxfb、qeglfs、qoffscreen、qminimal。我一般建议多链接一个qoffscreen,程序在无显示器环境下测试也不容易蹦。
5.4 openssl交叉编译后的版本识别问题
程序跑起来后,Qt网络模块报qt.network.ssl: QSslSocket: cannot resolve CRYPTO_num_locks之类的运行时错误,多半是openssl版本不匹配。Qt 5.14.2只对openssl 1.1.1系列有完整适配,如果你链了openssl 1.0.x或者openssl 3.0,会出现符号找不到。解决办法就是老老实实编openssl 1.1.1t,并且确保sysroot里只有这一套openssl头文件和库,不要同时塞多个版本进include路径。
5.5 QML应用静态编译常见问题
如果你编QML程序,除了Q_IMPORT_PLUGIN之外还要注意qmlimportscaner的使用。Qt编译QML时要扫描import路径,把相关的qml模块和插件链进来。最简单的方式是在.pro里加:
CONFIG += qtquickcompiler QML_IMPORT_PATH += /opt/Qt5.14.2/static-aarch64/qml另外静态编译QML程序时,qmlcachegen这个工具会在构建期运行,它负责把QML文件编译成字节码缓存,但在交叉编译环境里它可能生成目标架构不匹配的缓存,导致运行时崩溃。遇到这种情况,可以在.pro里去调或设置环境变量QML_DISABLE_DISK_CACHE=1临时禁用磁盘缓存,确认问题后再调整编译配置。
5.6 常见问题速查表
| 现象 | 大概率原因 | 解决建议 |
|---|---|---|
| configure检测不到openssl头文件 | sysroot缺少openssl dev包 | 重新安装openssl到sysroot,确认include路径存在 |
| 链接报libcrypto相关符号错误 | openssl静态库目标架构不对 | 确认openssl是用aarch64工具链编的 |
| 运行时报找不到platform plugin | 静态插件未显式导入 | .pro加QTPLUGIN或代码Q_IMPORT_PLUGIN |
| 程序启动后中文乱码 | 缺字体文件,静态编译不会带字体 | 在目标板放一个ttf,或用-fontconfig链接字体库 |
| 编译时内存不足OOM | make线程数开太高 | 降到-j2或-j4重新make |
| file显示可执行文件是x86-64 | qmake不是aarch64交叉版 | 确认用的是prefix/bin/qmake,而不是系统/usr/bin/qmake |
| 编译Qt时某些模块报libX11找不到 | sysroot不含图形相关dev包 | 裁剪configure参数关闭对应模块或补齐sysroot依赖 |
一些最后的实操建议
这套Qt 5.14.2 aarch64静态交叉编译环境我前后折腾了两天才完全跑通,很多时间都花在“configure通过了但实际编译到一半才暴露问题”这个循环里。回头总结,我真正会用到的经验大概就是三条:第一,第一遍搭建时把configure参数能关的全关掉,先把linuxfb下的Widgets跑通,再按需一步步把网络、sqlite、qml往回加,这样定位问题要容易得多;第二,sysroot别图省事,务必保证和目标的libc版本一致,交叉编译的很多诡异问题最后都能追溯到sysroot残缺;第三,静态编译最终的可执行文件可以用strip裁剪掉符号,体积能再小三分之一左右,别忘在发布前做这一步。
另外一个小技巧:给Qt的configure加个-v参数,它会在编译时打印出每条编译和链接的具体命令,遇到问题能直接看到qmake到底调用了哪个gcc、哪个库路径,排查效率会高很多。
这套方法做出来后,后续不管是给aarch64板子部署小程序,还是配合gem5这类模拟器做spec2006环境下的应用性能评测,只要研发环境对目标系统库版本有“不可控”或“零安装”的要求,静态交叉编译的思路都是通用的。希望这篇手册能让你少踩几个我踩过的坑。