Qt aarch64 静态交叉编译全流程解析:从工具链到部署
2026/9/24 23:24:06 网站建设 项目流程

1. 项目概述与整体方案解读

1.1 为什么要做 Qt aarch64 静态交叉编译

做嵌入式 Linux 图形应用开发的工程师,几乎都会撞上同一个问题:板子拿到手,交叉编译工具链装好了,程序也写好了,结果往板子上一跑,不是报缺库就是报平台插件找不到。更折腾的是,目标板上部署动态库、配置环境变量、处理依赖关系,每一步都在消耗有限的调试时间。

我这次整理的,是一套基于 Qt 5.14.2 对 aarch64 架构进行静态交叉编译的完整流程。所谓静态编译,就是把 Qt 库直接编进可执行文件里,生成一个不依赖目标板上任何 Qt 动态库的独立二进制。这么做的好处非常直观:拷一个文件到板子上就能跑,不用管 /usr/lib 下有没有 Qt 系列 .so,也不用担心多人协作时有人漏装运行库。代价是生成的文件体积偏大,但对比排查依赖问题的痛苦,这个体积开销完全值得。

Qt 5.14.2 这个版本是我个人专门选的。它属于 5.14 LTS 系列的最后一个补丁版本,bug 修复得最干净,又不像 Qt 6 那套构建系统改动太大,社区资料、交叉编译经验帖都非常丰富。真踩到坑,搜索引擎一搜基本都有解决方案。对生产项目来说,稳比新重要得多。

1.2 静态编译与动态编译的取舍

很多刚接触交叉编译的朋友会问:既然动态编译部署时拷一堆库也能跑,为什么非要静态编译?这里有个重要区别:动态编译省的是磁盘和内存,但费的是部署和排查的时间;静态编译恰恰相反,编译时费时间费磁盘,部署时却能省掉一大半麻烦。

动态库方案在调试阶段确实很顺,改了源码重新 make 一下,把新 .so push 过去就行。但等到产品要交付,或者板子要批量刷机时,依赖链问题就来了。Qt 的依赖不止 Qt 自己,背后还拉着 zlib、libpng、libjpeg、fontconfig、xcb 这一大串,任何一个版本对不上都可能在运行时弹奇怪的错误。

静态编译把这些问题一次性解决。最终产物是一个理论上"自带一切"的 ELF 文件,拷到目标板直接 chmod +x 就能运行。另外在纯内网环境、离线部署的场景下,静态编译也有不可替代的优势——你不需要在目标板上用 yum 或 apt 安装任何依赖包。

1.3 方案选型背后的几个关键决策

这套方案里我做了几个关键选择,每个都踩过坑才定下来,先说明白方便大家按需调整:

  • 宿主机系统:使用 x86_64 的 CentOS 7.9 / Ubuntu 20.04 均可,我这里以 CentOS 7.9 为例。不需要在 aarch64 机器上原生编译,一台 x86 主机配合交叉工具链即可。
  • 交叉编译工具链:用 aarch64-linux-gnu-gcc 9.x 系列。太老的 4.8 编 Qt 5.14 会报 C++11 标准相关的问题,太新的 12.x 又可能出现 glibc 版本兼容的隐性问题。
  • 显示后端:选择 linuxfb 作为默认平台插件。如果想上 xcb,静态编译时要把 libxcb 全家桶一起交叉编译进去,工作量翻倍,对大多数嵌入式场景不值。
  • 强制静态链接:让 Qt 内部全部使用 -static 编译,第三方依赖库也全部编成 .a,确保最终可执行文件不依赖任何 .so。

这几个决策的共性是:尽量走成熟、经社区验证的路径,避免在工具链兼容性上花太多时间。

2. 交叉编译环境搭建与工具链细节

2.1 宿主机环境准备

开始之前先确认宿主机的基础环境。我用的是一台 CentOS 7.9 x86_64 的服务器,8 核 16G 内存,磁盘剩余空间留了至少 30G。Qt 静态全量编译挺能吃磁盘的,中间产物加上源码解压,20G 是很保守的估计。

先更新系统基础工具,保证有 make、gcc、g++、perl、python 等常用组件:

yum install -y epel-release yum groupinstall -y "Development Tools" yum install -y wget tar bzip2 flex bison gperf \ libX11-devel libXext-devel libXrender-devel \ mesa-libGL-devel libicu-devel openssl-devel

注意这里装的 mesa、openssl 是给宿主机上的辅助工具用的,后面交叉编译 Qt 时不会直接用这些 x86 的库,但 configure 脚本在检测某些特性时可能会调用宿主机编译器做测试,缺了这些基础开发库会有莫名其妙的报错。

2.2 交叉编译工具链安装

工具链的选择是整个项目的地基,我一开始图省事用过 Linaro 老版本,结果编译 Qt 源码时频繁报错,回头排查发现是编译器太老,对 C++14/C++17 特性支持不完整。后来换成了 aarch64-linux-gnu-gcc 9.3,整个流程顺畅了很多。

安装方式分两种,任选其一:

# 方式一:直接用发行版源(推荐,省事) yum install -y gcc-aarch64-linux-gnu gcc-c++-aarch64-linux-gnu # 方式二:手动下载交叉工具链(版本可控性更强) # 这里以 ARM 官方提供的 GNU toolchain 为例 wget https://developer.arm.com/-/media/Files/downloads/gnu-a/9.3-2020.03/\ gcc-arm-9.3-2020.03-x86_64-aarch64-none-linux-gnu.tar.xz tar -xf gcc-arm-9.3-2020.03-x86_64-aarch64-none-linux-gnu.tar.xz mv gcc-arm-9.3-2020.03-x86_64-aarch64-none-linux-gnu /opt/ export PATH=/opt/gcc-arm-9.3-2020.03-x86_64-aarch64-none-linux-gnu/bin:$PATH

安装完工具链后,先写个 helloworld 验证交叉编译是否能正常工作:

cat > hello.c << 'EOF' #include <stdio.h> int main() { printf("aarch64 cross compile OK\n"); return 0; } EOF aarch64-linux-gnu-gcc -o hello hello.c file hello # 输出里必须包含 ELF 64-bit LSB executable, ARM aarch64

如果 file 命令输出的是 x86-64,说明工具链没配对或者 PATH 有问题。这一步没通过之前别碰 Qt,先排查环境。

2.3 关键字:工具链前缀与 sysroot

交叉编译工具链有两个概念必须理解清楚,不然 configure 时容易懵:

  • 工具链前缀:比如 aarch64-linux-gnu-,它只是编译器名字的前缀。编译器本身跑在 x86 宿主机上,生成的目标代码却是 aarch64。
  • sysroot:相当于目标板根文件系统的一个缩影。编译器在找头文件和库时,会以 sysroot 目录为根,而不是宿主机 /usr。比如目标板上的 /usr/include/zlib.h,在宿主机上就是 $(SYSROOT)/usr/include/zlib.h。

如果用发行版源安装的交叉工具链,sysroot 一般自动配置好了,路径可以用下面的方式查:

aarch64-linux-gnu-gcc -print-sysroot # 通常输出类似 /usr/aarch64-linux-gnu 或类似路径

后面编译第三方依赖库时,记得把 --sysroot 参数传给 configure,否则很容易编出 x86 的库,到 Qt configure 阶段链路全断。这是很多新手最容易踩的坑。

3. Qt 5.14.2 源码获取与目录规划

3.1 源码下载与校验

Qt 5.14.2 在 Qt 官方的 archive 目录下可以找到,国内环境如果下载慢可以换用国内镜像站,原理一样,校验哈希确认文件完整即可:

wget https://download.qt.io/archive/qt/5.14/5.14.2/\ single/qt-everywhere-src-5.14.2.tar.xz # 或者用国内镜像: # wget https://mirrors.tuna.tsinghua.edu.cn/qt/archive/qt/5.14/5.14.2/single/\ # qt-everywhere-src-5.14.2.tar.xz sha256sum 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

Qt Everywhere 源码包里的内容非常全,从 qmake 构建系统到 QtCore、QtGui、QtWidgets、QtNetwork 等模块源码都在一起。正因为全,编译前必须通过 configure 选择性地裁剪模块,否则全量静态编译的耗时你绝对不想体验第二次。

3.2 目录规划与输出路径设计

我的习惯是在家目录下建立一套清晰的目录结构,方便后面统一管理:

mkdir -p ~/qt-aarch64/{src,install,tools,work}
  • src:存放 Qt 源码和第三方库源码
  • install:编译产出目录,最终 Qt 库和可执行程序安装在这里
  • tools:手工编译的第三方静态库
  • work:实际的构建目录,存放 configure 产生的 Makefile 等

Qt 官方推荐在源码树外构建,这样源码可以保持干净,随时可以重新配置。我实际操作下来,在 work 目录下跑 configure 更保险,因为 Qt 的源码目录如果带有之前 configure 的残留,很容易出现各种缓存问题。

4. 依赖库准备:静态编译的重头戏

4.1 为什么要先编第三方库

Qt 的 GUI 部分不是空中楼阁,它依赖一批底层库来处理 PNG、JPEG、字体渲染、压缩等基础功能。动态编译时这些库通常由系统包管理安装,但在静态交叉编译场景下,必须手工把每个依赖库用交叉工具链编成 .a 静态库,再让 Qt 的 configure 找到它们。

很多人失败就失败在这个环节:直接从宿主机的 /usr/lib/x86_64-linux-gnu 拷贝 .a 文件链接,架构不对一报错就是几十条。老老实实交叉编译第三方库,后面反而顺利。

4.2 依赖库清单与版本选择

这里列一下我实际用到的库,全部以静态库方式编译:

依赖库版本说明
zlib1.2.11压缩基础库,几乎必选
libpng1.6.37PNG 图像支持
libjpegjpeg-9dJPEG 图像支持
openssl1.1.1k若需要 QtNetwork SSL 支持
tslib1.21触摸屏校准,视硬件决定

注意一下版本取舍。openssl 我没选 3.x,因为 Qt 5.14.2 的 SSL 模块对 3.0 的支持并不好,configure 检测的兼容性存在不少坑,用 1.1.1 系列省心得多。

4.3 一个标准依赖库的交叉编译流程

以 zlib 为例,展示交叉编译的标准姿势:

cd ~/qt-aarch64/src wget https://zlib.net/zlib-1.2.11.tar.gz tar -xf zlib-1.2.11.tar.gz cd zlib-1.2.11 CC=aarch64-linux-gnu-gcc \ AR=aarch64-linux-gnu-ar \ RANLIB=aarch64-linux-gnu-ranlib \ ./configure --static --prefix=/opt/aarch64-sysroot/usr make -j$(nproc) make install

注意 configure 时不加 --host 参数,但通过设置 CC、AR、RANLIB 环境变量指定了交叉工具链。zlib 的构建系统比较老了,这种方式是它官方支持的标准做法。

为保险起见,编译完查一下生成的 libz.a 架构:

file /opt/aarch64-sysroot/usr/lib/libz.a # 不应出现 x86-64 字样

libpng、libjpeg、openssl 的编译流程基本一致,不同点在于它们的 configure 参数更多,需要显式指定 --host=aarch64-linux-gnu:

# libpng CC=aarch64-linux-gnu-gcc ./configure \ --host=aarch64-linux-gnu \ --prefix=/opt/aarch64-sysroot/usr \ --enable-static --disable-shared # openssl ./Configure linux-aarch64 \ --prefix=/opt/aarch64-sysroot/usr \ --openssldir=/opt/aarch64-sysroot/usr/ssl \ no-shared

openssl 的 Configure 比较特殊,它有自己的 target 配置表,直接用 linux-aarch64 这个目标名就行,不需要走 autoconf 那套。

4.4 编译第三方库的几个心得

整个第三方库准备过程大概是三个晚上踩坑总结出来的,分享几个关键经验:

第一,统一安装前缀很重要。我把所有库装到 /opt/aarch64-sysroot/usr 下,这样 Qt configure 时可以统一通过 --sysroot 和 -I/-L 参数找到它们,不用每个库单独指定路径。

第二,遇到"undefined reference"链接错误时,先检查库的架构,再看库的依赖顺序。静态库之间是有依赖顺序的,比如链接 -lpng 还要带上 -lz,如果顺序不对即使有 libz.a 也会报 undefined reference。

第三,千万别图省事用宿主机上的 libpng 头文件。不同架构的头文件虽然语法差不多,但部分宏定义和 ABI 配置不一样,混用会导致结构体大小不一致,这类 bug 排查起来极其痛苦。用 sysroot 里的头文件和库是配套的,才能保证二进制兼容。

5. 配置 qmake 交叉编译平台文件

5.1 理解 Qt 的 mkspec 机制

Qmake 是 Qt 自己的构建系统,它通过 mkspec(Make Specification)来了解当前编译目标的平台特性。Qt 源码中预先提供了很多 mkspec 目录,比如 linux-g++、win32-msvc 等。交叉编译时,我们需要在 mkspec 目录下新建一个自定义平台文件,告诉 qmake:编译器叫 aarch64-linux-gnu-g++,库路径是 sysroot 下的路径,链接方式要静态。

这个过程中最核心的是两个文件:

  • qmake.conf:指定编译器和关键编译链接参数
  • qplatformdefs.h:一般直接 include 参考平台的定义

5.2 新建 linux-aarch64-gnu 平台文件

Qt 5.14.2 自带的 mkspec 目录里其实有 linux-aarch64-gnu-g++ 这个目录,但它是为动态编译设计的。我们要改造成静态编译,最稳妥的方案是复制一份出来修改:

cd qt-everywhere-src-5.14.2 cp -r qtbase/mkspecs/linux-aarch64-gnu-g++ \ qtbase/mkspecs/linux-aarch64-static

编辑 qtbase/mkspecs/linux-aarch64-static/qmake.conf:

# # qmake configuration for cross-compiling # with aarch64-linux-gnu-g++ # MAKEFILE_GENERATOR = UNIX CONFIG += incremental QMAKE_INCREMENTAL_STYLE = sublib include(../common/linux.conf) include(../common/gcc-base-unix.conf) include(../common/g++-unix.conf) # 修改编译器前缀和 sysroot QMAKE_CC = aarch64-linux-gnu-gcc --sysroot=/opt/aarch64-sysroot QMAKE_CXX = aarch64-linux-gnu-g++ --sysroot=/opt/aarch64-sysroot QMAKE_LINK = aarch64-linux-gnu-g++ --sysroot=/opt/aarch64-sysroot QMAKE_LINK_SHLIB = aarch64-linux-gnu-g++ --sysroot=/opt/aarch64-sysroot # 强制静态链接 QMAKE_LFLAGS += -static QMAKE_LFLAGS_PLUGIN += -static QMAKE_CFLAGS += -fPIC QMAKE_CXXFLAGS += -fPIC # ar 工具 QMAKE_AR = aarch64-linux-gnu-ar cqs QMAKE_OBJCOPY = aarch64-linux-gnu-objcopy QMAKE_NM = aarch64-linux-gnu-nm QMAKE_STRIP = aarch64-linux-gnu-strip # 指定 sysroot QMAKE_INCDIR = /opt/aarch64-sysroot/usr/include QMAKE_LIBDIR = /opt/aarch64-sysroot/usr/lib load(qt_config)

这里 -fPIC 是个小小的权衡。纯静态编译理论上不需要 PIC,但 Qt 内部某些模块默认加了 -fPIC 编译选项,不统一加上的话会出现 "relocation R_AARCH64_ADR_PREL_PG_HI21 cannot be used when making a shared object" 这一类链接错误。吃一堑长一智,直接在全局加上。-static 链接标志是整个静态化的开关,确保最终的 Qt 库文件都是 .a。

5.3 qplatformdefs.h 的处理

复制 linux-aarch64-gnu-g++ 目录后,qplatformdefs.h 已经存在,里面 include 了通用 Linux 平台定义。static 编译不需要对这个文件做特殊改动,保持原样即可。

真正的关键是前一步,确认 qmake.conf 里的编译器路径都对。我见过有人改了编译器名忘记改 --sysroot,结果所有的依赖库头文件都灵异地指到了宿主机路径,报错还特别隐晦。

6. configure 配置详解与完整命令

6.1 configure 命令的完整参数

进入 work 构建目录,执行 configure。这是我最终调通的完整命令:

cd ~/qt-aarch64/work ../src/qt-everywhere-src-5.14.2/configure \ -v \ -opensource \ -confirm-license \ -xplatform linux-aarch64-static \ -static \ -release \ -prefix /opt/qt-aarch64/install \ -no-opengl \ -no-eglfs \ -no-xcb \ -linuxfb \ -no-feature-accessibility \ -no-feature-xkbcommon \ -no-feature-xinput2 \ -no-feature-eglfs \ -no-compile-examples \ -no-gtk \ -no-cups \ -no-iconv \ -no-icu \ -qt-zlib \ -qt-libpng \ -qt-libjpeg \ -openssl-linked \ -I/opt/aarch64-sysroot/usr/include \ -L/opt/aarch64-sysroot/usr/lib

参数很多,一个也不能少,逐个解释一下:

  • -v:打印详细配置过程,方便查错
  • -opensource -confirm-license:接受开源协议,免交互
  • -xplatform linux-aarch64-static:指定使用刚才创建的 mkspec
  • -static:构建静态库
  • -release:只编译 release 版本,省一半时间
  • -prefix:安装路径
  • -no-opengl -no-eglfs:禁用 OpenGL 相关模块,嵌入式大多用不到
  • -linuxfb:启用 linuxfb 平台插件,帧缓冲设备直接绘制
  • -no-feature-...:禁用用不到的功能模块,减少编译量
  • -qt-zlib -qt-libpng -qt-libjpeg:这三个参数的意思是使用刚才交叉编译的第三方库
  • -openssl-linked:启用 OpenSSL 支持
  • -I / -L:告诉 configure 依赖库的位置

有个容易混淆的点:-qt-zlib 和 -qt-libpng 在这里的实际含义,是让 Qt 优先使用我们准备的第三方静态库路径,而不是 Qt 源码里自带的那些,两者不能混为一谈。如果我们的 /opt/aarch64-sysroot/usr 里已经有了交叉编译好的 .a 库,这条路就能走通。

6.2 内存与并行度控制

configure 前的源码树检查很考验 CPU。configure 脚本会编译几十个小测试程序来探测编译器特性,如果某个测试程序因为缺少系统头文件失败,configure 会直接中止并给出错误信息。

configure 本身默认单线程,耗时不长。真正耗时的是后面的 make 环节。Qt 5.14.2 全模块编译在我的 8 核 16G 机器上大约耗时 40 到 60 分钟(全量),如果内存紧张建议这样:

make -j4

-j 参数别拉满。静态编译时每个编译单元吃内存都比较大,-j8 有时候会直接把 16G 内存打满触发 OOM,反而是 -j4 更稳定。编译过程中可以随时用 top 看内存余量。

6.3 configure 阶段常见报错

报错一:找不到平台文件

ERROR: The host system architecture 'x86_64' does not match the target architecture 'aarch64'

这种情况一般是没有正确指定 -xplatform,configure 误用宿主机的 linux-g++ 配置。检查 mkspec 目录名是否拼写正确。

报错二:GL 相关失败

ERROR: Feature 'opengl' was enabled, but pre-conditions not met.

参数里 -no-opengl 的作用就是避免这个错误。除非目标板明确支持 GPU 加速并配好了 EGL,否则嵌入式环境直接禁用。

报错三:zlib 检测失败

The test for linking against zlib will fail.

这个报错常出现在 -I / -L 路径没有生效的情况下。检查 /opt/aarch64-sysroot/usr/lib/libz.a 是否存在,再用 -v 看看 verbose 日志里 compiler output 的具体错误。

7. 编译、安装与链接全过程

7.1 编译阶段的重要观察点

configure 成功之后,会生成若干 Makefile。这时先不要直接 make,先看一眼 work 目录下的 config.summary 文件,确认关键配置项符合预期:

grep -E "Platform|OpenGL|linuxfb|XCB|Qt modules" config.summary

确保 XCB 是 no,linuxfb 是 yes,OpenGL 是 no。如果和预期不符,先回 configure 阶段纠偏,不要带着错误的配置往下走。

普通编译过程:

make -j4 2>&1 | tee build.log

建议把输出存到日志文件,编译出错时可以精确回溯到第一个 error 出现的位置。Qt 的编译错误信息量和格式都比较复杂,日志文件是排查问题的第一手依据。

编译结束后,安装到指定前缀:

make install

安装完成后重点检查这些文件是否正常生成:

ls /opt/qt-aarch64/install/ # 目录下应有 bin、lib、include、mkspecs 等 find /opt/qt-aarch64/install/lib -name "*.a" | head -20 # libQt5Core.a、libQt5Gui.a、libQt5Widgets.a 必须存在

静态编译的产出物应该全是 .a,如果看到 .so 就要回查 qmake.conf 里的 -static 是否生效。

7.2 写一个测试程序验证工具链链路

安装完成之后,写一个最小 QWidget 程序验证整条链路是否可用:

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

关键一步,用我们编译出来的交叉 qmake 生成 Makefile:

export PATH=/opt/aarch64-sysroot/bin:/opt/qt-aarch64/install/bin:$PATH qmake make

编译结束后,用 file 检查产物:

file hello # 输出:ELF 64-bit LSB executable, ARM aarch64, statically linked

确认包含 statically linked 字样。再查看目标板的可执行文件传播方式,如果这一步 ok,就可以把 hello 直接拷贝到开发板运行。

7.3 部署到目标板运行验证

把 hello 拷贝到目标板后有几种运行方式:

# 方式一:指定 linuxfb 平台插件 ./hello -platform linuxfb # 方式二:如果配置了 QWS 兼容层(Qt 5 中已废弃) ./hello -qws

在 linuxfb 平台下运行时,如果界面没有显示,最常见原因是 framebuffer 设备节点权限问题。确认目标板 /dev/fb0 存在并且当前用户有读写权限:

ls -l /dev/fb0 # 如果权限不够,用 root 运行 sudo ./hello -platform linuxfb

如果开发板带触摸屏,还需要在运行时通过环境变量指定输入设备:

export QT_QPA_FB_DRM=1 export QT_QPA_FB_TSLIB=1 export TSLIB_TSDEVICE=/dev/input/event1 ./hello -platform linuxfb

这里 tslib 的配置需要和硬件实际输入事件编号对应,具体用哪个 event 节点,可以在板子上通过 cat /proc/bus/input/devices 查看。

8. 运行时细节与二次打包优化

8.1 字体问题的处理

静态编译的程序跑起来,界面中文字体直接显示成方格方块,这是非常普遍的坑,且不只在静态编译环境出现。原因是目标板的 Linux 环境缺字库,Qt 的字体数据库在系统字库里找不到预设的字形。

解决方式有两种:

一是把 PC 平台的字体文件拷贝到目标板,放到 /usr/share/fonts 或其他 Qt 搜索路径,通过设置环境变量指定:

export QT_QPA_FONTDIR=/opt/fonts

二是在代码里用 QFontDatabase 加载本地字体文件,比如常见的思源黑体或文泉驿正黑:

QFontDatabase database; QString fontPath = "/opt/fonts/SourceHanSansCN-Regular.ttf"; QString fontFamily = database.fontFamily(0).toString(); // 加载后返回字体族名 QFont font(fontFamily, 12); app.setFont(font);

更稳妥的做法是静态程序自带的资源目录里放一份精简的 TTF 文件,启动时通过 QResource 直接加载,这样连外部字体文件路径都省了。

8.2 裁剪体积

静态编译出来的 hello 动不动几百 MB 不是新鲜事。我实测过,纯 QWidget 应用,包含 QtCore、QtGui、QtWidgets 的完整静态链接,产物七百多MB是正常的。这不影响功能,但会让拷贝和刷机变得痛苦。

如果确实需要瘦身,有两条路线:

第一条路线,从编译选项上裁剪。configure 阶段把用不到的模块全关掉,比如 -no-gui、-no-network 可以让产物体积大幅下降,但这对 GUI 应用不适用。可行的做法是裁剪 QtWidgets 内部特性,比如 -no-feature-widgets-completer、-no-feature-texthtmlparser 这一类,能把部分代码段排除出静态库。

第二条路线是链接期裁段。用提供的链接器参数让未被引用的目标文件不被链接进最终可执行文件:

QMAKE_LFLAGS += -Wl,--gc-sections QMAKE_CXXFLAGS += -ffunction-sections -fdata-sections

它的原理是在编译阶段把每个函数、每个数据对象放到独立的 section 中,链接时丢弃未被引用的 section。对 Qt 这种代码量很大的库,实测能砍掉 20% 到 30% 的体积。风险是如果某些地方通过函数指针间接引用、被丢掉的代码实际上是运行期需要的,链接器不会报错,只在运行阶段出现奇怪的崩溃。加了这些参数之后,必须回归测试所有功能模块。

8.3 运行环境变量配置模板

给目标板准备一个启动脚本,把常用的 Qt 环境变量一次性配置好:

#!/bin/sh export QT_QPA_PLATFORM=linuxfb export QT_QPA_FB_DRM=1 export QT_QPA_FONTDIR=/opt/fonts if [ -c /dev/input/event1 ]; then export TSLIB_TSDEVICE=/dev/input/event1 export QT_QPA_FB_TSLIB=1 fi cd /opt/app exec ./hello -platform linuxfb

这个脚本里最值得注意的就是最后 exec 替代。使用 exec 后,shell 进程被应用替换,systemd 或 init 系统监控的 pid 保持在同一个进程上,方便后面做进程守护与异常重启。

9. 易错环节排查与优化路径

9.1 常见错误速查表

整个搭建过程中,我整理了若干高频问题,放到一张表里方便快速定位。

错误现象根本原因解决方案
configure 报 requires a C++11 compiler交叉工具链过老升级到 gcc 9.x 及以上
链接期 undefined reference 成堆第三方库架构不符或依赖顺序不对file 检查 .a 架构;调整 -l 顺序
运行时报 cannot mix incompatible Qt library可执行文件里混入宿主机 Qt 头文件全部统一用交叉 qmake 编译
linuxfb 无法打开 /dev/fb0设备节点权限不足chmod 666 /dev/fb0 或 root 运行
应用启动后黑屏无输出字体缺失 / 屏参配置不对设置 QT_QPA_FONTDIR、检查 framebuffer 参数
无法找到 platform plugin linuxfb未启用 linuxfb 模块重新 configure 并检查 config.summary
make 编译中 OOM 被杀并行度过高make -j4 甚至 -j2

其中 "cannot mix incompatible Qt library" 这个错误值得单独提一下。它的意思是当前编译程序时引用的 Qt 头文件是 5.6.1 版本附带的,而链接指向的库却是其他版本。这个问题的根源几乎都出在系统里安装了多套 Qt、PATH 环境变量没有指对我们编译出的 Qt。检查方法:

# 查看 qmake 实际指向 which qmake qmake -v # 查看 qmake 对应的 Qt 版本和路径,确认指向 /opt/qt-aarch64/install/bin/qmake

9.2 编译效率的优化

如果编译速度慢得无法忍受,除了并行的 -j 参数,还可以关闭不必要的 Qt 模块。比如只保留 qtbase,加参数:

-skip qt3d -skip qtcanvas3d -skip qtcharts -skip qtdatavis3d \ -skip qtdeclarative -skip qtgamepad -skip qtlocation \ -skip qtmultimedia -skip qtpurchasing -skip qtquickcontrols \ -skip qtscript -skip qtsensors -skip qtserialbus -skip qtserialport \ -skip qtspeech -skip qtsvg -skip qttools -skip qttranslations \ -skip qtwayland -skip qtwebchannel -skip qtwebengine -skip qtwebsockets

这里注意:如果项目用到了 Qt Charts 或 Qt Data Visualization,对应模块就不能 skip。没有使用 GUI 增强模块的纯基础应用,只编 qtbase 可以把时间压缩到二十多分钟。

9.3 给新手的几条建议

这套静态交叉编译的方案虽然折腾,但只要按顺序走一遍,后面每次出活都会很省心。我建议第一次动手的人记住三条原则:

第一,每一步都要验证。工具链编译完先跑 file 验证,第三方库编完再跑 file 验证,Qt configure 完看 config.summary 验证。每层都确认没问题再往上叠,出错时才能快速定位层次。

第二,日志一定要留。编译 zlib 的日志、编译 Qt 的 build.log、configure 的 config.log,全部保存下来。几天后想起某个参数想回头核对,没有日志只能重新猜。

第三,不要试图一次搞完。这事的复杂度决定了出问题是必然的,心态上要接受逐步排查的安排。第一次接触时预留两三天专门做环境,比逼自己在半天内跑完更现实。

我做完这套环境后,后续每个新项目都是直接把编译好的 Qt 安装目录拷到新机器上用,一次性配好收益非常可观。

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

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

立即咨询