尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Qt 5.14.2 aarch64静态交叉编译完整实战手册

Qt 5.14.2 aarch64静态交叉编译完整实战手册 Qt 5.14.2 的 aarch64 静态交叉编译这套流程我前前后后搭过不下五回从最早在 x86 台式机上折腾 Ubuntu 环境到后来为 RK3399、树莓派这类 ARM 板卡做界面程序每次都会在 configure 参数、sysroot 完整性这些地方卡一下。这篇手册就是我把这些经验整理下来的一份从零到一的完整笔记你只要照着一步一步走基本能一次跑通。这个内容适合谁主要面向三类人一是准备在 ARM64 设备上做 Qt 界面程序、但对交叉编译环境还不太熟的同学二是已经在用动态编译、但受够了目标板上动态库依赖问题的老手三是需要给产线或者客户交付可执行文件的嵌入式工程师。我会把工具链怎么选、sysroot 怎么搭、Qt 源码怎么配置、编译完怎么部署到板子上以及我踩过的坑全部写清楚。1. 版本选型与编译方案设计1.1 为什么是 Qt 5.14.2Qt 版本很多但 5.14.2 在嵌入式圈子里一直口碑不错。它在 2020 年发布属于 LTS 长期支持版本对 Linux 嵌入式场景的支持非常成熟QWidget 和 QML 的表现都很稳定。相比更早的 5.9、5.12它对高分辨率屏和较新的编译器兼容性更好相比 5.15 以及后来 Qt 6 系列它又没有那么重的 Python 工具链要求configure 的时候省心很多。做嵌入式项目的选型有时真不是追新反而要守旧。很多工控板卡的 BSP 内核还停留在 4.x配套的交叉编译器也只有 9.x 或者 10.xQt 5.14.2 在这个环境下几乎是零摩擦。我手头有一块 RK3399 的板子跑的是 Debian 10 的 rootfs用 Qt 5.14.2 静态编译出来的程序拷过去直接能跑完全不用关心目标板上有没有 Qt 运行库这就是静态编译最大的诱惑。1.2 静态编译 vs 动态编译的取舍静态编译和动态编译不是对错问题而是取舍问题。动态编译出来的程序体积小只有几 MB但目标板上必须有一整套 Qt 运行库而且版本要跟你编译时用的完全一致或兼容bin、lib、plugins、qml 目录一个都不能少。你想想交付的时候客户拿到的是一坨目录结构少一个插件程序界面就白屏排查起来头大。静态编译则把所有 Qt 库直接打包进可执行文件里拷到目标板上只要文件系统有基本的 libc、libstdc 和内核相关依赖就能运行。代价有两个一是可执行文件体积大随便一个带界面的小工具就是 20~30MB这是正常现象编译时看到别慌二是部分 Qt 插件的使用方式有变化不再是从 plugins 目录加载而是需要你手动在代码里注册或者通过 configure 参数静态内置。这两种方式我都有过实战经验。如果项目还在快速迭代界面频繁改版用动态编译能省去每次重新静态链接的时间但如果目标是要交付安装包或者固件静态编译才是能睡个安稳觉的方案。这篇手册只讲静态编译因为它链路更长、坑更多学会了静态的动态的就是降维操作。1.3 整体构建流程一览在正式动手前建议你先在脑子里把这条链路画出来其实就是三个阶段的流水线宿主机准备装好 Linux 系统、安装编译依赖、下载解压好的工具链和 Qt 源码。configure 配置与交叉编译通过 configure 脚本指定 target 架构、sysroot、编译特性再用 make 编译出 aarch64 静态库。部署验证把编译好的 Qt 库和程序拷贝到目标板用 qmake 生成工程并确认程序可以运行。这三个阶段里最容易被轻视的是 sysroot。很多人以为交叉编译就是拿个 aarch64 的 gcc 就能搞结果 configure 时报找不到libstdc.so或者limits.h就是 sysroot 没搭好。所以接下来我先把环境搭建这一步讲透。2. 构建环境与工具链准备2.1 宿主机系统与基础依赖我推荐在 Ubuntu 18.04 或 20.04 的 64 位系统上做这件事。虽然理论上任意 Linux 发行版都可以但 Ubuntu 的包管理最省心网上能搜到的踩坑记录也最多出了问题容易找到答案。先更新系统并安装必要的基础依赖sudo apt update sudo apt install build-essential g make cmake ninja-build \ libgl1-mesa-dev libfontconfig1-dev libdbus-1-dev \ libxkbcommon-dev libxcb-xkb-dev libxcb-icccm4-dev \ libxcb-image0-dev libxcb-keysyms1-dev libxcb-render-util0-dev \ libxcb-xinerama0-dev libxcb-xv0-dev libxcb-glx0-dev \ libssl-dev libfreetype6-dev libx11-dev libx11-xcb-dev \ libxext-dev libxi-dev libxrender-dev libxrandr-dev libxfixes-dev \ wget tar xz-utils宿主机上的这些开发库主要是为了让 Qt 能在编译阶段检查宿主机的编译器和一些通用组件。这里有个坑Qt configure 脚本在检查某些特性比如 xcb、eglfs时会顺手查宿主机的库文件如果你宿主机缺少基础库就算后面用-qt-xxx参数让 Qt 使用自带实现也可能在特性检测阶段被卡住。所以这一顿 apt install 别偷懒。2.2 交叉编译器选择与安装aarch64 的交叉编译器方案很多最常见的有三种ARM 官方提供的aarch64-none-linux-gnu-gcc这几个版本里我用得最多的是 9.2 和 10.3稳定可靠。Linaro 提供的aarch64-linux-gnu-gcc工具链老牌很多 BSP 厂商基于它的内核改。板卡厂商比如 Rockchip、NXP、全志自家 SDK 里打包的工具链。我个人的建议是如果有板卡厂商提供的工具链优先用厂商的因为它的 glibc 版本和内核头文件跟你的 BSP 匹配度最高如果手里只有一块裸板没有配套 SDK那就用 ARM 官方的 GNU-A 工具链。以 ARM 官方 9.2 版本为例下载安装步骤mkdir -p /opt/toolchain cd /opt/toolchain wget https://developer.arm.com/-/media/Files/downloads/gnu-a/9.2-2019.12/binrel/gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu.tar.xz tar -xf gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu.tar.xz -C /opt/toolchain export PATH/opt/toolchain/gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu/bin:$PATH装完验证一下aarch64-none-linux-gnu-gcc -v能正常输出版本信息说明编译器没问题。这里我提醒一句工具链装的路径不要太深更不要有中文和空格Qt 的 configure 脚本对路径比较敏感有时候路径太长或含特殊字符会出现莫名其妙的报错。2.3 sysroot 搭建与验证sysroot 可以理解为目标板的根文件系统的一个子集交叉编译器在编译时需要从这里找到目标平台的头文件和库文件。用一个类比你要给一个外国人写信你需要知道他所在国家的语言规范和常用词汇sysroot 就是那本词典。sysroot 有几个来源交叉工具链自带的 libc 目录比如gcc-arm-9.2.../aarch64-none-linux-gnu/libc/里面包含了基本的 glibc 头文件和库。目标板 rootfs 的对应目录比如你把板子上的/usr、/lib整个同步下来再整理出需要的部分。嵌入式 Linux 发行版如 Buildroot、Yocto生成的目标系统镜像。最稳健的做法是把工具链自带的 libc 先拷过来作为基础再把目标板 rootfs 里实际用到的头文件和库补进去。步骤参考mkdir -p /opt/aarch64-sysroot # 使用工具链自带 libc 作为基础 cp -r /opt/toolchain/gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu/aarch64-none-linux-gnu/libc/* /opt/aarch64-sysroot/ # 把编译器的自带头文件也拷进去解决 limits.h 等问题 cp -r /opt/toolchain/gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu/aarch64-none-linux-gnu/libc/usr/include/* /opt/aarch64-sysroot/usr/include/注意libc/usr/include目录名是usr/include拷贝后要确保/opt/aarch64-sysroot/usr/include/里能看到stdio.h这类文件。如果目标是 Yocto 或 Buildroot 镜像里面一般直接有 usr 目录整体拷过去即可。sysroot 搭好之后别急着往下走先做一个真实编译测试cd /tmp cat hello.c EOF #include stdio.h int main() { printf(hello aarch64\n); return 0; } EOF aarch64-none-linux-gnu-gcc --sysroot/opt/aarch64-sysroot -o hello hello.c file hello如果file输出显示ELF 64-bit LSB executable, ARM aarch64说明工具链和 sysroot 基本可用。这一步能提前过滤掉八成后续的玄学报错。3. Qt 源码配置与 configure 参数详解3.1 下载源码与检查完整性Qt 5.14.2 的完整源码包是qt-everywhere-src-5.14.2.tar.xz大约 500MB 左右。可以从 Qt 官网 archive 目录下载cd /opt wget https://download.qt.io/archive/qt/5.14/5.14.2/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下载后建议算一下 MD5 或者 SHA256 做一次完整性校验因为源码包太大网络中断导致文件损坏是常事。解压完会有qtbase、qtdeclarative、qtsvg等一堆子模块目录每个模块最后会编成对应的 Qt 库。3.2 configure 参数逐项拆解configure 是 Qt 构建体系里最关键的一步相当于告诉编译器你要按什么规则把我组装起来。参数多且杂我把最核心的一组参数列在下面并逐个解释它们的作用./configure -static -release -opensource -confirm-license \ -prefix /opt/qt5.14.2-aarch64-static \ -xplatform linux-aarch64-gnu-g \ -sysroot /opt/aarch64-sysroot \ -qt-zlib -qt-libpng -qt-libjpeg \ -qt-freetype -qt-pcre -qt-harfbuzz \ -no-opengl -no-xcb -no-eglfs -no-glib \ -skip qtwebengine -skip qt3d -skip qtcanvas3d \ -skip qtlocation -skip qtmultimedia -skip qtserialbus \ -skip qtcharts -skip qtdatavis3d -skip qtpurchasing \ -skip qtvirtualkeyboard -skip qtgamepad -skip qtscript \ -nomake examples -nomake tests逐个来看-static最核心的参数告诉 Qt 生成静态库。没有它后面编译出来的还是动态库整个手册就白搭了。-release编译发布版不带调试信息。嵌入式设备存储空间宝贵调试版本体积翻倍没必要。-opensource -confirm-license接受开源许可协议。-prefix /opt/qt5.14.2-aarch64-static安装路径。建议跟你的架构和编译方式挂钩一眼能认出这是 aarch64 静态版。-xplatform linux-aarch64-gnu-g这是告诉 Qt 我要用哪个平台描述文件。Qt 在qtbase/mkspecs/下预置了多种平台配置linux-aarch64-gnu-g是专门给 aarch64 Linux 用的。-sysroot /opt/aarch64-sysroot指定前面搭好的 sysroot 路径Qt 从这里找目标板的系统头文件与库。-qt-zlib -qt-libpng -qt-libjpeg -qt-freetype -qt-pcre -qt-harfbuzz这些参数让 Qt 使用源码包自带的第三方库实现。在交叉编译场景下我强烈建议全部加上的因为你 sysroot 里很可能没有这些库的 aarch64 版本就算有版本也未必匹配。-no-opengl -no-xcb -no-eglfs -no-glib禁用不需要的模块。静态编译环境下OpenGL 相关组件依赖 Mesa 的 EGL/GLES 库如果目标板 GPU 方案不匹配编进去只会带来一堆链接错误。大部分纯 QWidget 程序根本用不到 OpenGL直接禁用最省事。-skip后面一堆跳过不需要的 Qt 模块例如浏览器引擎 webengine、3D、多媒体、串口等在静态编译下要么体积巨大要么依赖复杂不需要就跳过能节省大量编译时间。-nomake examples -nomake tests不编译示例和测试代码。这些参数不是死规矩要根据你的实际项目调整。比如如果你要做 QML 界面就不能过度 skip 模块了要保留qtdeclarativeQML/Quick 的核心。我早期做车载仪表盘时界面是 QWidget 做的就没编 QML只保留了 core、gui、widgets、network、svg 这几个核心模块。3.3 你可能需要一个新的 mkspeclinux-aarch64-gnu-g这个 mkspec 在 Qt 5.14.2 里其实是存在的但有些情况下不一定兼容你自己的工具链版本。如果你 configure 时报Spec linux-aarch64-gnu-g could not be found或者编译过程出现编译选项错误可以自定义一个 mkspec。操作方法是复制已有的 spec 再改cp -r qtbase/mkspecs/linux-aarch64-gnu-g qtbase/mkspecs/linux-aarch64-custom-g然后编辑qtbase/mkspecs/linux-aarch64-custom-g/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-custom-g即可。这种做法在工具链定制化程度高的 BSP 环境里非常常见。3.4 configure 输出检查要点configure 执行完之后终端会输出一大段检测结果。别急着make先花半分钟看一下关键部分Build type: linux-g (x86_64)说明宿主机识别正常。Cross compiling: yes这行很重要如果是 no说明它没识别成交叉编译后面编出来的就是 x86 的东西。Platform: linux-aarch64-gnu-g确认目标平台正确。Qt is in static mode.确认静态模式开启。最后一行显示Qt is now configured for building...基本就成了。如果 configure 中途报错要看错误具体是找不到某个头文件还是syntax error前者一般是 sysroot 缺失或路径不对后者通常是工具链版本太老或参数不支持。4. 编译、安装与目标板部署验证4.1 多线程编译与耗时管理configure 通过后就是漫长的 make。Qt 全模块编译在 8 核 i7 上差不多要 1.5 到 2 个小时期间建议日志重定向方便出问题时排查make -j$(nproc) 21 | tee build.log-j$(nproc)会自动使用所有 CPU 核心。不过要提醒一句交叉编译时如果内存小于 16GB建议-j值降到核心数一半因为每个编线程都会消耗不少内存我有一台 8GB 内存的旧笔记本试过-j8直接 OOM 崩溃最后make -j4反而更快。如果中间某个模块编译失败不要急着全部重来可以直接定位到对应子模块目录单独编译那个模块cd qtbase make -j44.2 make install 与目录结构确认编译完成、没有致命错误后执行安装make install 21 | tee install.log安装到/opt/qt5.14.2-aarch64-static后检查一下目录结构ls /opt/qt5.14.2-aarch64-static # bin include lib libexec mkspecs plugins qml注意看lib目录下的库文件静态编译下应该都是.a后缀的ls /opt/qt5.14.2-aarch64-static/lib/ | grep -E libQt5(Core|Gui|Widgets)正常输出是libQt5Core.a、libQt5Gui.a、libQt5Widgets.a这类没有.so的档案文件。如果看到.so说明 configure 的-static没生效返回去重来。4.3 用 qmake 交叉编译一个测试程序Qt 不是直接执行 gcc而是通过qmake生成 Makefile再交给 make。所以环境变量里必须优先指向我们新编译出来的 Qt。写一个最简工程# mytest.pro QT core gui widgets TARGET mytest TEMPLATE app SOURCES main.cpp// main.cpp #include QApplication #include QLabel int main(int argc, char *argv[]) { QApplication app(argc, argv); QLabel label(Hello Qt on aarch64!); label.resize(320, 160); label.show(); return app.exec(); }编译命令export PATH/opt/qt5.14.2-aarch64-static/bin:$PATH export QMAKE_CROSS_COMPILE1 cd /path/to/mytest /opt/qt5.14.2-aarch64-static/bin/qmake make file mytestfile输出同样应该是ARM aarch64的 ELF 可执行文件。接着用readelf检查依赖readelf -d mytest | grep NEEDED静态编译 基础 glibc 的情况下NEEDED列表里应该只有libc.so、libstdc.so.6、libm.so.6这些系统库不该有 Qt 相关的动态库。这就能直观感受到静态编译的价值。4.4 目标板部署与环境变量设置把mytest拷贝到目标板任意目录直接执行。如果程序能弹出窗口恭喜你整个链路已经打通了。不过实际部署时可能有几个细节要处理。首先是 QPA 平台插件静态编译的 Qt 默认内置了一些 platform plugin取决于 configure 时是否启用了 linuxfb、eglfs 等。如果你的板子没有显示器或没有 framebuffer 设备程序启动会报qt.qpa.plugin: Could not find the Qt platform plugin。这个不是没编进去而是程序运行时不知道用哪个平台。此时可以显式指定export QT_QPA_PLATFORMoffscreen ./mytestoffscreen 平台不适合看界面但能验证程序逻辑是否正确运行。如果有真实显示屏通常用linuxfbexport QT_QPA_PLATFORMlinuxfb:fb/dev/fb0 ./mytest其次是很常见的中文字体问题。aarch64 板卡的文件系统通常很精简很可能没有字体目录。Qt 渲染中文时找不到字体界面上的中文全是方框。解决方法是把一套中文字体比如文泉驿微米黑或者 Noto Sans CJK 的子集拷贝到板子上并指定字体目录export QT_QPA_FONTDIR/usr/share/fonts/最后还有一个 static 编译特有的问题如果你是动态加载第三方库或用dlopen方式加载插件静态编译默认是不开启-rdynamic的可能会影响某些反射或插件机制。对于纯 Qt 界面应用一般没影响但如果你集成了一些需要符号导出的第三方库要在 .pro 里加上QMAKE_LFLAGS -rdynamic。5. 常见问题速查与避坑实录5.1 配置阶段典型报错configure 阶段最容易出问题我把遇到的典型报错和解决办法整理成了一个速查表报错信息原因分析解决方案Spec linux-aarch64-gnu-g could not be foundQt 源码里没有对应 mkspec或 mkspec 被修改坏了自定义 mkspec参考 3.3 节The specified sysroot does not exist-sysroot路径错误或目录不存在确认/opt/aarch64-sysroot是否存在必要时mkdir -pCannot find -lpthreadsysroot 里缺少 glibc 相关库或符号链接没建好检查/opt/aarch64-sysroot/usr/lib下是否有libpthread.so及关联的软链basic_ostream: error: expected initializergcc 版本太老不支持某些 C 特性换用 gcc 9.x 或 10.x 工具链Could not determine cross compiler version工具链不在 PATH 中或可执行文件名不对先执行aarch64-none-linux-gnu-gcc -v验证 PATH第一批坑里最折磨人的是 sysroot 里符号链接缺失。工具链自带的 libc 目录里libpthread.so本身是一个指向libpthread-2.28.so的软链接如果你用cp -r拷贝时落了符号链接链接阶段就会报 Cannot find -lpthread。建议拷贝后检查一下软链接是否完好ls -l /opt/aarch64-sysroot/usr/lib/libpthread.so如果显示的是普通文件而不是符号链接就需要手动重建。5.2 编译阶段典型报错configure 通过后make 阶段也有一道坎。最常见的几个fatal error: stdio.h: No such file or directorysysroot 里缺 glibc 头文件。检查/opt/aarch64-sysroot/usr/include/stdio.h是否存在。undefined reference to qt_version_tag这个在静态编译多模块时偶发。原因是版本标签相关代码编进了静态库但没有被链接器导出。解决方案是在 .pro 里定义DEFINES QT_VERSION_STR或者检查是否误用了两个不同版本的头文件/库混编。ld: cannot find -lGL前面 configure 里如果没有-no-opengl链接时就会尝试找 OpenGL 库。静态编译环境下常见直接确认 configure 参数里加了-no-opengl。如果确实需要 OpenGL就得把 Mesa 的 libGL.a 放到 sysroot 里这是另一个深坑不在本文展开。内存不足导致internal compiler error: Killed用-j控制并发度或者给编译机加 swap。遇到编译错误最有效的排查法是打开日志文件定位到第一个 error 出现的位置看上下文。很多错误是连锁反应第一个错误解决了后面几百个错误全消失。5.3 目标板运行时典型问题程序编好了部署到板子上又是新战场。常见问题运行时提示Cannot create platform integration说明 QPA 插件加载失败。先确认你用的 platform 是否在 configure 阶段被启用了。如果用-no-eglfs编的那就不能用 eglfs 平台。最稳妥的办法是源码里加一段 fallbackqputenv(QT_QPA_PLATFORM, linuxfb);界面显示中文全是方框字体缺失参考 4.4 节配好字体目录。触摸屏没有反应linuxfb 平台本身不带触摸支持需要在代码里设置QT_QPA_GENERIC_PLUGINSevdevtouch:/dev/input/eventX或者使用 libinput 插件。程序能跑但退出时报段错误通常是静态链接时析构顺序问题常见于 Qt 和第三方 C 库的清理时机冲突。可以先试export QT_DEBUG_PLUGINS1看看插件初始化日志如果确定是插件的问题再做裁剪。可执行文件太大这是静态编译的天然属性但可以适当优化。用-O2而不是-O3strip 掉符号表aarch64-none-linux-gnu-strip mytest一个 25MB 的程序 strip 后一般能减到 20MB 左右效果有限但聊胜于无。5.4 经验总结一个稳如老狗的编译模板最后分享一个我一直在用的 configure 模板适用于绝大多数无 OpenGL 的 QWidget 应用。直接保存成脚本换工具链和 sysroot 路径就能用#!/bin/bash export CROSS_COMPILEaarch64-none-linux-gnu export CC${CROSS_COMPILE}-gcc export CXX${CROSS_COMPILE}-g export AR${CROSS_COMPILE}-ar export RANLIB${CROSS_COMPILE}-ranlib export STRIP${CROSS_COMPILE}-strip ./configure \ -static -release -opensource -confirm-license \ -prefix /opt/qt5.14.2-aarch64-static \ -xplatform linux-aarch64-gnu-g \ -sysroot /opt/aarch64-sysroot \ -qt-zlib -qt-libpng -qt-libjpeg \ -qt-freetype -qt-pcre -qt-harfbuzz \ -no-opengl -no-xcb -no-eglfs -no-glib \ -skip qtwebengine -skip qt3d -skip qtcanvas3d \ -skip qtlocation -skip qtmultimedia -skip qtserialbus \ -skip qtcharts -skip qtdatavis3d -skip qtpurchasing \ -skip qtvirtualkeyboard -skip qtgamepad -skip qtscript \ -nomake examples -nomake tests这套模板给我的最大感受就是确定性。一个嵌入式项目从开发到量产常常要跨好几个月中途可能换了机器、换了系统、换了个人接手配置文件一扔任何机器上都能复现同一套 Qt 环境比一个人把命令记在脑子里靠谱多了。交叉编译这套体系说白了难点不在于编译本身而在于你愿不愿意把环境当作一个工程来维护。只要环境搭一次、吃透一遍后面不管是做界面还是加组件都是顺水推舟的事。
返回列表