ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Qt5.14.2交叉编译aarch64静态库全程详解

Qt5.14.2交叉编译aarch64静态库全程详解 开头先说明这篇稿子是我结合实际搭建经历整理的主要面向做嵌入式Linux、需要把Qt5.14.2跑在aarch64ARM64平台上的朋友们。目标很具体在x86_64主机上用交叉编译工具链把Qt5.14.2编译成aarch64架构下的静态库和静态可执行文件最终产物可以直接拷到目标板上运行不依赖目标板上的动态库。这个需求我在项目里碰到过很多次尤其是做工业控制器、边缘网关、专用终端的时候。系统是厂家裁剪过的动态库不稳定、版本乱、缺依赖每次部署都要反复调库路径。静态编译虽然会带来体积大、许可证要留意这些问题但省掉的麻烦是真省掉了。本文会把从零搭建的完整过程拆开讲包括工具链选型、sysroot准备、Qt源码处理、configure参数逐项解释、make编译、产物验证以及我踩过的坑。没有跳过任何一步也没有用“你懂的”之类的省略说法。1. 为什么是Qt5.14.2、aarch64和静态交叉编译1.1 版本选择背后的考量Qt5.14.2是LTS版本。对于嵌入式项目来说LTS意味着长期维护、社区资源丰富、各种硬件平台上的兼容性问题基本都被趟过了。如果你去查各芯片厂商的BSP包很多aarch64平台的SDK默认带的都是5.14.x或者5.15.x说明这个版本段在工业界接受度很高。5.15以后版本开始收紧开源协议策略5.14.2是最后一个比较容易获得完整内置模块源码的版本之一。这里不聊协议合规细节但实际做商业产品的人都知道选LTS再配合严格的自用编译风险相对可控。1.2 aarch64平台的特殊性aarch64是ARM的64位架构现在市面上绝大多数新出的嵌入式SoC都是它比如瑞芯微RK3588、全志T507、飞腾、鲲鹏这些。相比老旧的armv7aarch64在性能、内存寻址、NEON/SIMD指令上都强很多但也有几个让开发者头疼的特点工具链命名复杂。同样是交叉编译器有人叫aarch64-linux-gnu-gcc有人叫aarch64-linux-gcc还有人用aarch64-none-linux-gnu-gcc前缀不同库路径就不同。板卡厂商的rootfs差别大。有的用glibc有的用musl有的BSP里gcc版本老得可怜。浮点ABI指令集的变体不少虽然64位基本都是硬浮点但不同核心对部分扩展指令的支持不一样。这就导致了一个结果任何通用教程都不能完全适配你的板子但只要你理解了编译链路的每一环具体板子的坑就只是时间问题。1.3 静态编译到底图什么静态编译就是把Qt和依赖的第三方库全部打包进最终的可执行文件里。好处三件事目标板不需要安装Qt运行环境相当于自带运行时。没有“找不到libQt5Core.so.5”这类经典翻车现场。文件拷到板子上直接运行部署简化到了极致。代价也很明显可执行文件体积大我这个hello world编译出来18MB起步带网络模块和UI的完整程序动辄50MB以上。另外Qt默认是不建议静态链接的因为静态链接的Qt和OpenSSL、xkb、GPU驱动这些系统组件的交互方式不一样配置不对会有一堆隐性故障。但很多工业项目部署的板子只有64MB存储内存小、用户就那几个动态版本天天出问题。实测下来静态版虽然大了点但跑起来反而更稳定没有运行时依赖没有路径地狱。1.4 整体编译链路先把整条链路在脑子里拉一遍主机x86_64的Ubuntu上安装aarch64交叉编译工具链。准备一块目标板文件系统sysroot里面包含aarch64的libc、libstdc以及各种静态库文件。下载Qt5.14.2完整源码。配置Qt使用“目标为aarch64”的qmake平台规范。用configure生成Makefile。执行make编译Qt库和工具。使用编译出来的qmake去编译你自己的Qt程序得到静态链接的aarch64可执行文件。整条链看起来简单每一步都有细节下面逐条展开。2. 环境准备与工具链选型2.1 主机系统建议我用的是Ubuntu 20.04用Ubuntu 18.04也完全可行只要是64位系统就行。不需要服务器版桌面版也一样反正最终也是命令行操作。编译Qt会消耗不少CPU和内存建议至少4核以上、8GB内存磁盘预留30GB以上。主机上需要安装的基础软件sudo apt update sudo apt install -y \ build-essential \ libncurses5-dev \ libglib2.0-dev \ libfontconfig1-dev \ libfreetype6-dev \ libx11-dev \ libxext-dev \ libxrender-dev \ libxcb1-dev \ libx11-xcb-dev \ libxcb-glx0-dev \ libxcb-util0-dev \ python3 \ perl \ gperf \ bison \ flex \ texinfo \ wget \ curl \ vim \ net-tools有些包是编译Qt源码时脚本检测用的比如perl、python3、gperf。如果你的系统没有装configure过程中会自动跳过一些功能最后编译出来的Qt可能少模块排查起来很麻烦所以一次性装齐。注意这里是主机原生环境安装的依赖只用于辅助脚本运行编译出来的aarch64代码不会链接这些x86库。真正的链接目标是下一节说的sysroot。2.2 交叉工具链选择aarch64交叉工具链主流有几种Linaro公司发布的gcc-aarch64-linux-gnu工具链历史最久、文档最丰富。ARM官网的GNU GCC交叉编译器现在叫AArch64 GNU/Linux工具链。板卡厂商BSP自带的工具链比如瑞芯微的rk-gcc、全志的aarch64-linux-gnu-gcc。我的建议是如果哈板卡厂商提供了配套工具链优先用板卡厂商的。原因很简单厂商的交叉工具链和它的BSP rootfs是一起验证过的版本匹配度最高。如果自己做通用移植用ARM官方下载的10.3版本或者Linaro 7.5版本都可以。我自己用下来最顺的是Linaro 7.5-2019.12版本的aarch64-linux-gnu工具链因为它的sysroot里静态库齐全Qt配置检测能通过的组件多。下载解压后放到/opt目录cd /opt sudo tar -xf gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar.xz sudo mv gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu aarch64-toolchain然后把工具链的bin目录加进PATHexport PATH/opt/aarch64-toolchain/bin:$PATH验证一下aarch64-linux-gnu-gcc --version看到版本信息输出就说明交叉工具链可以用了。2.3 sysroot是什么怎么准备sysroot就是目标板的根文件系统里面包含libc、libstdc、libm、libz等库的头文件、动态库和静态库。交叉编译时编译器根据--sysroot参数访问这套文件系统而不是访问主机自己的/usr/include和/lib。sysroot有三种准备方式直接从目标板上拷贝。把开发板的根文件系统分区用tar打包然后在主机上解压这是最直接的方式。使用工具链自带的sysroot。Linaro工具链和ARM官方工具链压缩包里都带一个aarch64-linux-gnu/libc目录这就是一个基础sysroot。用buildroot自己构建一块干净的rootfs。这种方式最灵活但耗时比较多适合深度定制项目。我推荐从开发板或者仿真器镜像里提取sysroot因为这样才能保证Qt编译时看到的头文件和库文件和你运行设备的完全一致。从目标板提取时至少要包括以下目录bin boot dev etc lib lib64 opt root sbin usr usr/include usr/lib usr/lib64 usr/local把提取出来的rootfs放到主机目录下比如/opt/sysroot-aarch64。然后重点检查几个库文件的静态版本是否存在ls /opt/sysroot-aarch64/usr/lib/aarch64-linux-gnu/libc.a ls /opt/sysroot-aarch64/usr/lib/aarch64-linux-gnu/libstdc.a ls /opt/sysroot-aarch64/usr/lib/aarch64-linux-gnu/libz.a这三个静态库缺一不可。libstdc.a在工具链的lib/gcc目录下也有但建议用sysroot里的版本避免编译器路径问题。如果只有动态库没有静态库需要在板子上安装对应的-dev包后重新提取sysroot。提示检查静态库一定要看是aarch64架构还是x86架构的用file命令验证。我曾经拿错一个x86的libc.a编译时错误信息十分迷惑。2.4 Qt源码下载Qt5.14.2的源码包是qt-everywhere-src-5.14.2.tar.xz体积大约800MB。下载方式有几种可以用官方在线安装器也可以直接下载离线源码包。命令行直接下载的方式最爽快cd ~ wget https://download.qt.io/archive/qt/5.14/5.14.2/qt-everywhere-src-5.14.2.tar.xz下载需要一段时间如果觉得慢可以找国内镜像源网易、清华、华为云都有Qt镜像。这里要说明一下在线安装器并不强制需要额外工具和某些需要特殊通道的软件是两个概念只是普通的HTTP下载。解压源码tar -xf qt-everywhere-src-5.14.2.tar.xz cd qt-everywhere-src-5.14.2/3. 配置qmake平台规范与工具链文件3.1 mkspecs是什么Qt源码里有一个目录叫qmake/mkspecs它管理不同平台下的编译规范。比如在x86_64的Linux上Qt默认用linux-g这一套在ARM Linux上有linux-aarch64-gnu-g、linux-arm-gnueabi-g这些预设的规范。mkspecs里最重要的文件是qmake.conf它告诉qmake三件事用什么编译器也就是CROSS_COMPILE环境变量对应的前缀。编译器、链接器的参数。头文件和库文件搜索路径。如果Qt源码里没有你想要的平台规范也可以自己新建一个目录。我在搭建过程中直接修改了linux-aarch64-gnu-g这个mkspecs这个目录在Qt5.14.2源码里本身就是存在的ls qmake/mkspecs/devices/linux-aarch64-gnu-g/如果用的不是Linaro工具链而是板卡厂商的工具链建议新建一个专属的mkspecs目录cp -r qmake/mkspecs/devices/linux-aarch64-gnu-g qmake/mkspecs/devices/linux-aarch64-myboard-g然后修改里面的qmake.conf。3.2 qmake.conf内容详解我实际使用的qmake.conf如下这里逐项解释关键参数# qmake.conf for aarch64 static build MAKEFILE_GENERATOR UNIX CONFIG incremental QMAKE_INCREMENTAL_STYLE sublib include(../common/linux_device_pre.conf) QMAKE_CFLAGS -fPIC -O2 -pipe QMAKE_CXXFLAGS -fPIC -O2 -pipe # 交叉编译器的前缀 CROSS_COMPILE aarch64-linux-gnu- # 编译器、汇编器、链接器 QMAKE_CC $${CROSS_COMPILE}gcc QMAKE_CXX $${CROSS_COMPILE}g QMAKE_LINK $${CROSS_COMPILE}g QMAKE_LINK_SHLIB $${CROSS_COMPILE}g QMAKE_AR $${CROSS_COMPILE}ar cqs QMAKE_RANLIB $${CROSS_COMPILE}ranlib # sysroot路径 QMAKE_CFLAGS --sysroot/opt/sysroot-aarch64 QMAKE_CXXFLAGS --sysroot/opt/sysroot-aarch64 QMAKE_LFLAGS --sysroot/opt/sysroot-aarch64 # 头文件和库路径 QMAKE_INCDIR /opt/sysroot-aarch64/usr/include QMAKE_INCDIR /opt/sysroot-aarch64/usr/include/aarch64-linux-gnu QMAKE_LIBDIR /opt/sysroot-aarch64/usr/lib/aarch64-linux-gnu QMAKE_LIBDIR /opt/sysroot-aarch64/lib/aarch64-linux-gnu # 静态构建需要加上的选项 QMAKE_LFLAGS -static-libgcc -static-libstdc include(../common/linux_device_post.conf) load(qt_config)有几个点必须重点说QMAKE_LINK_SHLIB说清楚了才能理解为什么要特意加这一行。默认情况下Qt构建共享库时使用的链接器可能不是g而是gcc或者ld这会导致C标准库链接异常。交叉编译时指定这一行能避免很多莫名其妙的“undefined reference”错误。CROSS_COMPILE这个变量名在Qt5.14.2里被严格依赖不要自作聪明改成aarch64-linux-gnu-全路径。qmake内部很多地方拿着CROSS_COMPILE拼字符串改了这个变量的语义会崩。--sysroot是GCC的参数不是qmake的参数。这里必须显式加在CFLAGS和LFLAGS里不能只依赖工具链编译时的默认配置因为Qt的configure阶段和后续模块编译阶段对sysroot的传递并不总是可靠手动指定最稳妥。-static-libgcc -static-libstdc这两个参数是给最终Qt程序链接时用的确保最后的可执行文件不依赖目标板的libstdc.so。3.3 注意glibc与工具链的版本匹配sysroot里libc的头文件版本不能高于工具链自带的头文件版本。比如工具链是gcc-linaro-7.5.0它自带的glibc头文件比较旧如果你从目标板提取的sysroot里libc是2.35的新版本编译时可能会报“glibc version mismatch”的错误。判断方法很简单/opt/sysroot-aarch64/lib/aarch64-linux-gnu/libc.so.6 | head -n1 /opt/aarch64-toolchain/aarch64-linux-gnu/libc/lib/aarch64-linux-gnu/libc.so.6 | head -n1如果两个版本相差太大建议要么换更新版本的工具链要么手动用系统的老版本libc替换sysroot里的libc。不过后者操作起来很麻烦。最省事的方法是先确认板子的glibc版本然后选对应年代的工具链。比如板子是2020年之前的系统选7.5工具链没问题板子是2023年的系统选ARM官方10.3工具链更稳妥。4. configure配置与关键参数说明4.1 configure命令全文写好消息前期的辛苦在这一个命令上集中体现。以下是我验证过可以顺利编译的configure参数./configure \ -prefix /opt/qt-5.14.2-aarch64-static \ -opensource \ -confirm-license \ -release \ -static \ -xplatform linux-aarch64-gnu-g \ -nomake examples \ -nomake tests \ -no-opengl \ -no-eglfs \ -no-gtk \ -no-xcb \ -no-wayland \ -no-cups \ -no-tslib \ -no-feature-vulkan \ -no-feature-xcb \ -qt-zlib \ -qt-libpng \ -qt-libjpeg \ -qt-freetype \ -no-fontconfig \ -no-harfbuzz \ -no-dbus \ -no-iconv \ -skip qtwebengine \ -skip qtwebchannel \ -skip qtwebsockets \ -skip qtwebview \ -skip qtdoc \ -skip qtlocation \ -skip qttools \ -skip qttranslations \ -skip qtserialport \ -skip qtquick3d \ -skip qtvirtualkeyboard \ -skip qtmqtt这个命令看起来很长实际每个参数都有存在的理由。逐条过一下-prefix安装到哪个目录编译完成后make install会拷贝到那里。这个路径会被写入qmake自己的配置里后续编译你的应用程序时qmake就知道Qt的库文件头文件在哪个位置。-static核心参数告诉Qt生成静态库.a文件而不是.so动态库文件。-xplatform linux-aarch64-gnu-g指定用哪个mkspecs。这里如果板子是新的要写成你自定义的mkspecs目录名。-release去掉调试符号静态版本特别需要不然体积分分钟翻倍。-no-opengl -no-eglfs -no-xcb -no-wayland -no-gtk这几个都是图形平台后端。如果是纯无界面或者用framebuffer/最小linuxfb后端的应用可以直接关掉。-qt-zlib -qt-libpng -qt-libjpeg -qt-freetype让Qt使用自带的第三方库源码而不是依赖sysroot里可能不存在的版本。这样能保证静态编译时这些库的.a文件一定存在。-no-fontconfig关掉字库配置工具。静态编译字体渲染本来就很麻烦如果只是显示数字和简单的英文直接用Qt内置的freetype和最简单字体加载即可。-no-dbus -no-iconv关掉DBus和iconv。这对大部分嵌入式应用都无所谓但能少编译几个模块。-skip qtwebengine务必跳过webengine这个模块太大了而且是动态链接钉子户静态编译它基本是自讨苦吃。4.2 configure执行过程与源码处理执行configure之前一定确保前面的PATH和sysroot环境变量设置好了。建议把这些写到一个环境脚本里每次打开终端source一下#!/bin/bash export PATH/opt/aarch64-toolchain/bin:$PATH export SYSROOT/opt/sysroot-aarch64 export CROSS_COMPILEaarch64-linux-gnu-执行configure以后几分钟内会打印出大量检测信息。重点盯几个关键字“The specified system/compiler is not supported” 说明mkspecs配置有问题。“Missing dependencies” 说明某些库检测失败要根据日志去补sysroot里的库。“Configure summary” 最后会列一长串feature的启用禁用情况重点看QtCore、QtGui、QtNetwork是否启用了。其中一个常见报错是找不到c11标准库头文件。解决办法是检查sysroot里的c头文件路径是否在QMAKE_INCDIR里。Linaro工具链自带C头文件在工具链目录下的aarch64-linux-gnu/include/c/7.5.0需要把这一路径也加到qmake.conf的QMAKE_INCDIR里。我在实际配置时加了这么一行QMAKE_INCDIR /opt/aarch64-toolchain/aarch64-linux-gnu/include/c/7.5.0 QMAKE_INCDIR /opt/aarch64-toolchain/aarch64-linux-gnu/include/c/7.5.0/aarch64-linux-gnu不然后面QtCore编译时会报找不到cstdint、cstdlib、bits/...这些头文件发现一个补一个非常痛苦。4.3 configure重要输出的阅读方法configure执行完毕后会生成一个config.summary文件这是你判断整个编译是否可用的第一手证据。我建议重点阅读这几段“Build type”确认是linux-g还是linux-aarch64-gnu-g。“Using static linking”确认是yes。“Qt Network”模块里的SSL支持状态。如果SSL显示no你的Qt程序要用HTTPS就得先解决OpenSSL的交叉编译这单独就是一篇教程。“Qt Gui”里的freetype、png、jpeg状态如果不是“yes”说明configure命令里的-qt-freetype没生效回去检查是否有拼写错误。这些信息如果用错了编译出来照样能过但你的程序跑起来就是缺字体、缺图片解码、网络连不上。这不叫“可用的Qt”这叫“浪费时间”。5. 开始交叉编译Qt源码5.1 make编译与分模块策略经过上面这些配置现在终于可以按下编译的启动按钮。这一步最耗时也最容易出问题。直接执行make -j$(nproc)$(nproc)会自动获取CPU核心数。如果是8核16线程的机器-j8会比较顺畅。不建议盲目-j12以上Qt的构建系统对并行度偶发有竞态问题一旦出现莫名其妙的崩溃可以先降低并行数重试。如果内存只有8GB建议加到16GB或者用-j4。Qt5.14.2的编译峰值内存占用还是比较高的尤其是Qml和WebEngine模块。我试过一次用-j12编译到一半系统直接OOM然后所有中间文件都没了只能从头再来那感觉太酸爽。由于我在configure阶段用-skip跳过了大量重模块实际编译时间在8核i7机器上大约40分钟。如果全模块编译轻松两小时以上。5.2 编译过程中的典型报错与应对编译过程中最典型的报错来自三个地方。第一个是qmake配置错误经常在编译QtCore这个最底层模块时集中爆发。报错一般是“fatal error: cstddef: No such file or directory”或者“stdio.h: No such file or directory”。出现这种问题基本就是sysroot的usr/include目录没配对。解决办法ls /opt/sysroot-aarch64/usr/include/stdio.h ls /opt/sysroot-aarch64/usr/include/aarch64-linux-gnu如果stdio.h找到了但编译器依然报找不到大概率是--sysroot参数没进到编译命令里。在qmake.conf里再加一个硬编码的CFLAGS项或者在configure之前export CFLAGS和CXXFLAGSexport CFLAGS--sysroot/opt/sysroot-aarch64 export CXXFLAGS--sysroot/opt/sysroot-aarch64第二个是链接阶段报“cannot find -lts”原因是configure阶段没有把tslib关干净。解决方法是回到configure命令确认有没有-no-tslib参数。如果忘了只能重跑configure再make distclean。第三个是“libgcc_s.so.1 must be installed for pthread_cancel to work”。这是典型的静态链接pthread残留问题解决办法是确保qmake.conf里有-static-libgcc和-static-libstdc并且在make install时确认目标板的libpthread.a存在。5.3 make install安装到前缀目录编译结束后执行make install安装过程会往/opt/qt-5.14.2-aarch64-static目录写入头文件、库文件、qmake、mkspecs和一堆辅助工具。安装完成后检查关键文件ls /opt/qt-5.14.2-aarch64-static/lib/ ls /opt/qt-5.14.2-aarch64-static/bin/qmake如果是静态编译lib目录下应该有一大批.a文件比如libQt5Core.a、libQt5Gui.a、libQt5Widgets.a等。此时需要把qmake路径加到PATH里后续编译你的应用程序都要用它export PATH/opt/qt-5.14.2-aarch64-static/bin:$PATH6. 用编译好的Qt编译你的第一个aarch64静态程序6.1 创建最小Qt工程创建一个最简单的测试工程验证整条链路是否通。mkdir ~/qt-test cd ~/qt-test写test.proQT core gui widgets TARGET testapp TEMPLATE app CONFIG c11 SOURCES main.cpp写main.cpp#include QApplication #include QLabel int main(int argc, char *argv[]) { QApplication app(argc, argv); QLabel label(Hello aarch64 Qt5.14.2 static); label.show(); return app.exec(); }6.2 使用交叉qmake编译这里有个坑必须避开不能用系统自带的qmake必须用刚才交叉编译出来的qmake。它内部的库路径是arm64的用系统的x86 qmake编译出来的程序在架构上就是错的。/opt/qt-5.14.2-aarch64-static/bin/qmake test.pro make如果一切顺利会生成一个testapp可执行文件。用file命令验证架构file testapp输出应该包含ELF 64-bit LSB executable, ARM aarch64, dynamically linked (uses shared libs)看到“statically linked”或者至少“dynamically linked”里的库列表没有Qt库。6.3 静态链接的验证判断静态链接是否成功不能光看file命令还要用readelf或者ldd检查。aarch64-linux-gnu-readelf -d testapp | grep NEEDED一个真正的Qt静态可执行文件NEEDED列表里应该只剩下一堆系统库比如libc.so.6、libstdc.so.6、libm.so.6、libgcc_s.so.1绝对不能出现libQt5Core.so.5、libQt5Gui.so.5。或者直接看ldd不过要记住这只能交叉查看aarch64-linux-gnu-readelf -l testapp | grep INTERP如果输出里没有“dynamic linker”相关信息说明连动态解释器都没有是纯静态。6.4 目标板上运行的注意事项把testapp从主机拷贝到目标板注意两点权限要加执行权限chmod x testapp。如果目标板系统是定制裁剪过的可能连libc.so.6都没有。静态版本能跑是因为它把大部分内容都打包进去了但libc.a里面仍有可能引用到动态加载器如果目标板连/lib/ld-linux-aarch64.so.1都不存在就需要进一步处理。实际上要在目标板上跑起来最靠谱的验证方式就是scp到板子上直接运行。scp testapp root192.168.1.100:/root/ ssh root192.168.1.100 chmod x /root/testapp /root/testapp如果看到窗口弹出来或者终端有Qt启动日志就说明整条静态交叉编译链路全通了。7. 依赖库静态打包的思路与注意事项7.1 核心依赖库的选取如果你开发的程序用到了Qt Network里的SSL、Qt SQL里的sqlite、Qt GUI里的字体和图片就需要额外交叉编译这些依赖库并保证它们的.a版本在sysroot里。静态链接依赖库的顺序问题在这里尤其突出。链接时库的先后顺序决定符号解析是否能成功。以前我用Qt自带网络模块连接HTTPS结果一直报OpenSSL符号未定义后来发现是openssl.a放的位置不对。Qt生成的Makefile里LIBS顺序通常是-L/opt/qt-5.14.2-aarch64-static/lib -lQt5Network -L/opt/openssl-aarch64/lib -lssl -lcrypto如果openssl.a放在Qt5Network.a前面链接器会先遇到ssl库但当时还不知道需要什么符号等Qt5Network符号解析完再回头找ssl但链接器已经不会回去处理已经扫描过的库了。解决办法是尽量把依赖的外部静态库放在Qt库后面。7.2 字体与图形后端纯静态编译时fontconfig一关字体的加载方式就变了。Qt默认用Fontconfig找系统字体这依赖一堆动态库静态编译基本不现实。我在配置里用了-qt-freetype和-no-fontconfig这样Qt会直接用自己的freetype读取字体文件。目标板上必须在能访问到的路径放一个字体文件比如/usr/share/fonts/或者程序工作目录。如果程序需要显示中文但目标板没有中文字体Qt会显示一堆方块。解决方法是把字体文件打包到程序资源里在main函数里加Qt字体加载代码QFontDatabase::addApplicationFont(:/fonts/DroidSansFallback.ttf); QApplication::setFont(QFont(Droid Sans Fallback));7.3 OpenSSL的交叉编译Qt静态链接OpenSSL是另一个常见任务。这里简化一下流程交叉编译OpenSSL并生成.a文件./Configure linux-aarch64 \ --prefix/opt/openssl-aarch64 \ --cross-compile-prefixaarch64-linux-gnu- \ shared static make make install注意OpenSSL的configure脚本用的是Configure而不是config而且必须加“static”参数。编译完成后在qmake.conf的QMAKE_LIBDIR里加上openssl的lib目录QMAKE_LIBDIR /opt/openssl-aarch64/lib然后在Qt源码的configure阶段或者修改Qt的mkspecs里加上QMAKE_INCDIR /opt/openssl-aarch64/include为了确保Qt检测到OpenSSLconfigure时还可以加-I/opt/openssl-aarch64/include -L/opt/openssl-aarch64/lib -lssl -lcrypto不过这个比较折腾大多数场景下如果你只是做简单的网络访问直接放弃HTTPS、用TCP或者HTTP反而省事得多。8. 常见问题速查表我把实际搭建过程中遇到的坑和网上的高频问题整理成一张速查表。问题现象可能原因解决方案configure提示“The specified system/compiler is not supported”mkspecs路径写错确认-xplatform参数值等于qmake/mkspecs/devices/linux-aarch64-gnu-g目录名编译QtCore时报cstddef找不到sysroot路径或C头文件路径缺失在qmake.conf的QMAKE_INCDIR里补工具链的include/c路径链接时报cannot find -lGLQt检测到OpenGL但sysroot没有libGL使用-no-opengl重新configure链接时报cannot find -lfontconfigfontconfig相关库缺失使用-no-fontconfig配合-qt-freetype静态链接程序在板子上运行报segmentation fault门面代码与sysroot中glibc版本不匹配更换匹配的工具链版本程序在x86主机上不能运行交叉编译产物是aarch64指令集这个不是bug需要拷贝到aarch64板子上qmake编译程序时提示找不到Qt5Core没有用交叉编译的qmake切换PATH环境变量执行which qmake确认路径编译过程OOM内存不够或并行度过高降低make -j数值关闭浏览器等大内存程序make install后qmake还是报sysroot错误qmake里记录的prefix路径不对检查configure时的-prefix参数是否与当前安装位置一致Qt程序无法显示中文字体目标板没有字体文件且fontconfig关闭将字体打进资源文件使用addApplicationFont加载9. 静态编译体积控制与部署技巧9.1 减小体积的基本手段静态编译出来的Qt程序普遍偏大这是固有的代价。好在有一些手段可以把体积从80MB压到30MB左右编译时使用-release加-O2去掉调试信息在qmake里加CONFIG release。strip符号表。编译完成后执行strip --strip-unneeded testapp这个操作常常能砍掉20%-40%体积。跳过不用的模块。configure阶段的-skip参数能省下一大批模块静态编译时每多一个Qt模块最终可执行文件就会大好几MB。我最小配置时只用core、gui、widgets三个模块程序最终16MB。使用Qt的feature削减宏。比如不需要拖拽、剪贴板这些功能可以通过-QT_NO_CLIPBOARD这类编译宏排除。9.2 部署时的建议部署阶段有几个经验值得分享静态编译的可执行文件最好存放在只读分区避免被意外覆盖。如果程序使用配置文件建议把配置文件路径和程序路径分开这样升级时只替换可执行文件即可。字体文件单独放不要打进可执行文件里除非你有严格的单一文件部署需求。打进程序里每次改字体都要重新编译太不方便。10. 从无到有的整体经验总结与扩展思路10.1 我的实操体会这条链路从头到尾走一遍最大的感受是静态交叉编译Qt的关键不在Qt源码本身而在sysroot的质量和工具链的匹配度。Qt的configure只是把那套依赖关系暴露出来的工具。很多初学者遇到编译失败在Configure参数里反复试其实是舍本逐末。先把工具链和sysroot搞干净Configure一次过的概率会高很多。如果你要做的项目和我的情况类似我建议第一次搭建时不要追求一次性全功能编译先把最小化配置编译通确保core、gui能出来再去逐步添加模块。我的做法是先编译一个只有QtCore的最小配置。编译一个测试程序确认静态链接成立。再开启QtGui和QtWidgets。最后才考虑网络、数据库这些重量级模块。这样每次只增加一个新的变量出了错也知道是哪个模块引起的不会一改一堆最后全乱套。10.2 后续还能扩展什么搭建完成以后这套环境能做的事情就很多了。你可以在这套静态编译的Qt上接着做Qt Quick/QML模块的交叉编译不过体积会显著增加。集成OpenSSL做HTTPS设备客户端。集成SQLite模块做本地数据存储。加tslib支持触摸屏输入。将整个系统做成镜像用buildroot或者Yocto集成进去实现一键量产。静态编译Qt只是手段真正重要的是那种“一次构建、随处运行”的稳定体验。在工业现场设备三五年不关机部署一次以后再也不用跑过去改库路径、补依赖那才能真正体会到当时花这些时间搭环境的回报。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进