资讯详情

资讯详情

Qt 5.14.2 aarch64静态交叉编译实战:从环境搭建到ARM64部署

手头正好在折腾一块aarch64架构的开发板要把Qt界面程序跑起来结果被动态库依赖折腾得够呛。后来干脆花了一周时间把Qt 5.14.2的aarch64静态交叉编译环境从零到一完整搭了一遍踩了不少坑也积累了一些经验。这篇就把整个搭建过程、关键参数、依赖处理思路和常见问题排查都整理出来给需要在ARM64嵌入式设备上部署Qt应用的朋友做个参考。先说结论Qt 5.14.2配合aarch64静态交叉编译做出来的程序可以拷贝到目标板上直接运行不需要在板子上装一大堆Qt运行库非常适合嵌入式产品发布和现场部署。下面进入正题按我实际操作顺序来写。1. 内容整体设计与思路拆解1.1 为什么选Qt 5.14.2、aarch64和静态编译先理清几个核心概念。aarch64就是ARM的64位指令集架构也常被叫做ARM64树莓派4B以上的板子、瑞芯微RK3568/RK3588系列、飞腾、鲲鹏这些处理器基本都是这个架构。Qt 5.14.2是Qt公司发布的LTS版本长期支持意味着bug修复周期长工业级项目选它更稳妥而且5.15虽然也是LTS但当时下载入口收紧了5.14.2在开源仓库里还好找编译资料也相对丰富。很多人在PC上开发Qt程序用的是动态编译程序跑起来时会去系统目录找Qt的so动态库。放到x86的Ubuntu上还好系统里装一下Qt运行环境就行。但换到嵌入式aarch64板子上情况完全不一样板子上可能没有桌面系统或者文件系统裁剪得很小你没法像PC那样轻易安装Qt运行库。就算能装上版本不一致、依赖缺失、库路径不对任何一个环节出问题程序就跑不起来。静态编译就是把Qt核心库和你的业务代码全部打包进同一个可执行文件里目标板子上只需要一个Linux内核和基础C库其他什么都不用装。缺点是生成的文件体积大不少一个简单的Qt程序可能从几百KB变成10MB甚至更大但换来的是部署极度简单拷过去就能跑。1.2 静态交叉编译的整套链路交叉编译的意思是在一台x86_64架构的电脑上编译出aarch64架构能运行的程序。所以整套链路包含这几层主机环境我用的Ubuntu 20.04 x86_64工具链从Linaro官方仓库下载。交叉编译工具链使用GCC的aarch64-linux-gnu-前缀版本这套工具链负责把源码编译成aarch64的机器码。目标根文件系统sysroot交叉编译时头文件和库文件不能直接去主机系统找会找到一堆x86的库导致编译失败所以需要准备一个aarch64的sysroot。Qt源码这里用Qt 5.14.2的完整源码包而不是Linux下的二进制安装包因为官方没有提供aarch64的预编译二进制。configure配置这是整个流程里最关键的一步要指定平台、编译器、静态模式、功能裁剪等参数。这四个环节缺一不可。很多人编译失败就是因为sysroot没准备好或者configure参数不对工具链找不到目标库文件。1.3 方案选型对比在动手之前在几个方案之间犹豫过这里也给大家对比一下方案优点缺点适用场景板载源码编译在aarch64板子直接编译不需要交叉编译工具链配置简单板子性能弱编译要数小时依赖板端环境完整开发板性能强、内存大只做单板部署交叉编译动态Qt编译快体积小部署时要同步一堆so文件版本冲突麻烦产品需频繁升级且文件系统空间紧张交叉编译静态Qt本方案部署最简单单文件拷贝即运行编译耗时较长文件体积大个别库需处理嵌入式产品发布、现场交付、需要快速部署我实际测试下来静态编译的Qt程序在aarch64板子上运行稳定没有遇到动态库兼容问题对于产品化交付来说省掉的部署麻烦远比牺牲的那点磁盘空间值得。2. 环境准备与交叉编译工具链搭建2.1 主机环境要求主机环境是Ubuntu 20.04磁盘剩余空间至少需要20GB因为Qt源码加编译产物加上sysroot体积不小。内存建议8GB以上并行编译时内存占用很明显我一次开4线程编译时峰值内存到了4GB左右。需要先安装一批基础软件包sudo apt update sudo apt install -y build-essential gcc g gperf bison flex \ libxkbcommon-dev libxcb-cursor0 libxcb-icccm4-dev \ libxcb-keysyms1-dev libxcb-shape0-dev libxcb-xfixes0-dev \ libxcb-xinerama0-dev libxcb-randr0-dev libxcb-render-util0-dev \ libxcb-image0-dev libxcb-util-dev libgl1-mesa-dev \ libsqlite3-dev libmysqlclient-dev libssl-dev libfontconfig1-dev \ libfreetype6-dev libicu-dev libpng-dev libjpeg-dev \ libts-dev zlib1g-dev libc6-dev-arm64-cross这里面需要特别说明两个细节。第一交叉编译前提下很多x86的dev包其实派不上用场但安装它们能保证configure阶段检查依赖时不至于报缺少工具的错误。第二libts-dev和libc6-dev-arm64-cross比较重要触摸屏库在后面配置触摸支持时要用arm64的C库是确保工具链能找到目标架构的基础文件。2.2 交叉编译工具链选型工具链我选的是gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu下载解压后配置环境变量即可使用。下载地址一般从Linaro官方Release页面获取选择x86_64 host、aarch64 target的版本。解压到/opt目录cd /opt tar xf gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar.xz export PATH/opt/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin:$PATH验证工具链是否可用aarch64-linux-gnu-gcc --version aarch64-linux-gnu-g --version这里为什么要选7.5.0而不是更新的10.3或者11.x主要原因是Qt 5.14.2是2020年发布的官方测试时用的编译器和当年的工具链匹配度最高。用太新的GCC 11编译Qt 5.14可能会有警告升级成错误的隐患反过来处理起来麻烦。如果非要用新工具链可能要加额外的编译选项不建议新手一上来就挑战高版本。2.3 准备目标板sysrootsysroot是编译时编译器查找头文件和库文件的根目录。由于交叉编译环境下主机系统的头文件和库是x86架构直接使用会导致“找不到头文件”或“链错库”的问题所以需要做一个干净的aarch64 sysroot。一种方式是直接从开发板的文件系统打包把板子根目录导出后清理一下临时目录、内核模块等保留lib、usr/include、usr/lib这些关键目录然后放到主机上。另一种方式是手动把工具链自带的aarch64库和各依赖库拷贝到一个目录里。我建议用第一种板子文件系统里的库和头文件齐全且版本匹配。整理后的sysroot结构大致如下/opt/sysroot/ ├── lib ├── usr │ ├── include │ ├── lib │ └── bin └── etc注意在sysroot里的符号链接要正确很多库的so文件是指向带版本号的文件的这个拷贝过程中容易断链。比如libc.so.6 - libc-2.31.so如果只拷贝了libc.so.6而没有带源文件链接时就会出错。一个可靠的办法是在板子上用tar打包时保留符号链接或者拷贝完用file命令抽查几个库文件确认是aarch64架构且文件完整。3. 依赖库准备与交叉编译细节3.1 需要重点准备的第三方库Qt 5.14.2的交叉编译尤其是静态编译第三方库的准备工作决定成败。有些库Qt源码自带比如libpng、libjpeg这些qtimageformats会用到的可以依赖Qt源码内部编译但有些库必须外部提供。我这里整理了一个对照表库用途处理方式tslib触摸屏校准与输入必须交叉编译放到sysrootlibmysqlclientQt SQL的MySQL插件可选需要数据库连接则必须交叉编译opensslQt Network的SSL支持必须交叉编译否则https无法使用zlib压缩支持用系统自带或交叉编译into sysrootfreetype字体渲染Qt自带的qtbase里可选但建议外部交叉编译保证版本可控sqlite3本地数据库可选Qt自带sqlite插件但系统库版本更新libinput输入设备可选Qt 5.14可以走libinput或直接用tslib这里有个容易踩坑的点是openssl。Qt的Network模块编译时会去查找openssl头文件和库如果找不着或者架构不对编译出来的QtNetwork虽然还能用但没有SSL支持所有https请求都会失败。嵌入式设备上现在很多数据上报都是https接口没有SSL基本没法用。3.2 tslib的交叉编译示例以tslib为例演示一下标准的三方库交叉编译流程。tslib是嵌入式Linux上几乎绕不开的触摸屏库很多板子的触摸屏驱动都依赖它来校准和上报坐标。先下载源码git clone https://github.com/libts/tslib.git cd tslib git checkout 1.21然后设置交叉编译参数export CROSSaarch64-linux-gnu- export CC${CROSS}gcc export CXX${CROSS}g export SYSROOT/opt/sysroot export PKG_CONFIG_PATH${SYSROOT}/usr/lib/aarch64-linux-gnu/pkgconfig配置和编译./autogen.sh ./configure --hostaarch64-linux-gnu \ --prefix${SYSROOT}/usr \ --sysconfdir/etc \ CC${CROSS}gcc CXX${CROSS}g make -j4 make install编译完检查一下生成的库架构file ${SYSROOT}/usr/lib/libts.so.0.10.3 # 输出包含 AArch64 字样说明正确这个检查步骤特别重要我建议每交叉编译完一个库都用file命令确认架构是AArch64而不是x86-64否则后面Qt链接时会蹦出各种莫名奇妙的undefined reference。3.3 处理sysroot里的pkgconfig另一个隐藏问题是pkgconfig路径。Qt configure会通过pkg-config来查找依赖库的信息而pkg-config默认查找的是本机的/usr/lib/pkgconfig出来的路径和架构全是x86的导致Qt找到错误的库路径。解决办法是设置环境变量export PKG_CONFIG_LIBDIR${SYSROOT}/usr/lib/aarch64-linux-gnu/pkgconfig:${SYSROOT}/usr/lib/pkgconfig export PKG_CONFIG_SYSROOT_DIR${SYSROOT}PKG_CONFIG_LIBDIR不能只设成/usr/lib/pkgconfig因为Ubuntu上aarch64的库文件实际放在/usr/lib/aarch64-linux-gnu下面。有些库的.pc文件也在这个子目录漏掉的话相关库的检测就会失败。PKG_CONFIG_SYSROOT_DIR这个变量会让pkg-config在给出的路径前自动加上sysroot前缀从而找到sysroot里的头文件和库。不改这两个环境变量直接跑configure的话最典型的现象是openssl检测失败、libsqlite3检测失败然后你一脸懵地反复编译openssl却又没用根源就在这。4. Qt源码获取与configure配置详解4.1 获取Qt 5.14.2源码Qt源码建议直接下载tarball完整包包含了qtbase、qtdeclarative、qtmultimedia、qtquickcontrols2、qtserialport、qtsvg等所有常用模块比用qt5.git拉分支要省事很多。下载方式wget https://download.qt.io/archive/qt/5.14/5.14.2/single/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如果网络不稳定也可以只下载qtbase部分后面按需添加其他模块。但完整源码包编译时可以统一配置之后想加模块再单独进子目录编译灵活度更高我建议用完整包。4.2 configure核心参数逐个拆解这是整个搭建过程中最重要的一步configure参数错了后面全白费。我先给一份可用的完整配置再逐个解释关键参数./configure \ -prefix /opt/Qt-5.14.2-static-aarch64 \ -xplatform linux-aarch64-gnu-g \ -sysroot /opt/sysroot \ -static \ -release \ -opensource \ -confirm-license \ -no-opengl \ -skip qt3d \ -skip qtwebengine \ -nomake examples \ -nomake tests \ -no-compile-examples \ -qt-zlib \ -qt-libpng \ -qt-libjpeg \ -qt-freetype \ -qt-pcre \ -tslib \ -qt-sqlite \ -no-icu \ -openssl-linked \ -I${SYSROOT}/usr/include \ -L${SYSROOT}/usr/lib逐项说明一下关键参数-static指定静态编译这是本方案的基石。-xplatform linux-aarch64-gnu-g指定目标平台配置文件。Qt源码的qtbase/mkspecs目录下默认有linux-aarch64-gnu-g这个规格文件它会告诉configure和make使用aarch64-linux-gnu-g编译器。-sysroot /opt/sysroot指定目标根文件系统路径。-prefix这个看似可有可无其实很重要它决定了make install后Qt文件安装到哪个目录。静态编译场景里prefix可以设成一个PC端临时目录或者直接设成工具链的sysroot路径但为了后续查找方便单独设一个目录更清晰。-no-opengl嵌入式板子如果没有GPU加速的OpenGL驱动强行开OpenGL编译会失败或运行时崩。如果板子支持OpenGL ES2可以改成-qt-opengl es2但默认不建议开。-tslib启用触摸屏支持注意这是在sysroot里准备好了tslib库的前提下才能成功。-openssl-linked链接OpenSSL库注意要和后面指定的头文件和库路径配合如果找不到openssl会导致配置失败。-qt-sqlite用Qt自带的SQLite而不是去链接系统的libsqlite3。好处是静态编译时少一个依赖缺点是如果之后想用更高版本SQLite特性就得换。-no-icuICU负责Unicode支持但ICU库非常大静态编译会显著增大产物体积嵌入式环境如果对国际化要求不高可以关掉。4.3 configure的检测输出怎么看运行configure后日志会打印每个模块的检测结果我建议保存一份完整日志./configure ... 21 | tee configure.log然后重点关注几个关键项的检测结果Qt Sql: sqlite yes, mysql no如果你需要mysql就说明没配置好 Qt Network: OpenSSL yes没有显示yes就得回头检查openssl Qt Gui: LinuxFB yes, XCB no静态嵌入式不需要xcb没问题 Touch: tslib yes这里显示yes说明tslib检测成功另外configure结束后会给出一个总结类似Qt is now configured for building... Then run make (or ninja).看到这个说明配置阶段成功。如果配置失败直接打开configure.log搜索error关键词多半会指向某个库找不到或者参数不合法。5. 编译过程与核心环节实现5.1 make编译与耗时configure成功后进入实际编译。这一步按模块并行编译make -j4 21 | tee build.log这里要解释一下为什么建议-j4而不是无脑-j16。交叉编译非常吃内存Qt的编译单元又大编译时每个G进程能吃掉1GB多内存-j16很容易把内存打满导致OOM然后编译就卡死或者进程被杀。实测在8GB内存的机器上-j4最稳16GB内存再考虑-j8。编译时间上完整Qt源码包如果只编译常用模块大概需要一个半小时到两个小时我实际用时1小时40分钟左右。如果只编译qtbase大概40分钟就够了。所以如果不需要qtmultimedia、qtdeclarative这些模块可以在configure的-skip参数里列出来节省不少时间。5.2 make install安装静态Qt编译完成后make install安装完成后到prefix目录确认文件结构cd /opt/Qt-5.14.2-static-aarch64 ls bin/ ls lib/重点检查lib目录下是否有libQt5Core.a、libQt5Gui.a、libQt5Widgets.a这些静态库文件。有就说明静态库生成成功。需要注意的一点是make install不会帮你自动设置环境变量。后面每次想用这个交叉编译的qmake时都需要指定完整路径或者把路径加到PATH里。5.3 验证静态库架构和验证tslib一样用file命令检查Qt静态库的架构file /opt/Qt-5.14.2-static-aarch64/lib/libQt5Core.a如果输出显示ARM aarch64则说明生成的目标架构正确。如果是x86-64说明configure阶段-xplatform参数实际没有生效回去查mkspecs配置。6. 编译一个aarch64静态Qt程序6.1 用qmake构建项目工具链和Qt都准备好后现在用实际项目来验证。假设有一个简单的Qt Widgets程序工程文件hello.pro如下QT core gui widgets TARGET hello TEMPLATE app SOURCES main.cpp编译时需要用交叉编译的qmake来生成Makefileexport PATH/opt/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin:$PATH export QT_PREFIX/opt/Qt-5.14.2-static-aarch64 export PATH$QT_PREFIX/bin:$PATH mkdir build-arm64 cd build-arm64 qmake ../hello.pro make -j4qmake如果找到了正确路径会自动使用交叉编译器进行编译。编译完成后用file命令检查产物file hello # 输出: ELF 64-bit LSB executable, ARM aarch64, dynamically linked, with debug_info, not stripped注意这里有个容易犯迷糊的地方就算Qt是静态编译的可执行文件本身也可能显示dynamically linked这是因为它还需要链接系统的C库静态链接只是把Qt库打进去了。真正要验证Qt库是否静态链接用readelf看动态依赖readelf -d hello | grep NEEDED正常来说只会看到libc.so.6、libstdc.so.6、libm.so.6这些系统基础库不会出现libQt5Core.so.5、libQt5Widgets.so.5这些Qt库。能输出这样的结果说明Qt核心已经被静态打进可执行文件了。6.2 处理动态依赖的libstdc实际部署时还会遇到一个小坑目标板子可能缺libstdc.so.6。虽然Qt库静态了但C标准库本身是动态链接的如果板子文件系统精简到连libstdc都没了程序还是会拒绝运行。解决方案有两种。一种是把工具链里的libstdc.so.6拷贝到板子的/lib或/usr/lib目录另一种更省事编译时也用-static-libstdc把C标准库静态链进去QMAKE_LFLAGS -static-libgcc -static-libstdc在pro文件里加上这行重新编译。这样最终的hello就不依赖libstdc.so.6了部署时只需要基础C库。如果还想彻底一点连C库也静态编译可以试-static-libgcc但强烈建议不要静态链接libc因为这会导致域名解析、用户信息等系统调用行为异常嵌入式中除非非常特殊不推荐。一般到-static-libstdc这层已经够用了。6.3 部署到目标板实测把hello拷到开发板上先给执行权限再运行chmod x hello ./hello正常情况下窗口直接弹出来。如果运行提示./hello: No such file or directory多半不是真的找不到文件而是缺了某个动态库或者解释器路径不对。嵌入式Linux有时/lib/ld-linux-aarch64.so.1缺失需要检查/lib和/lib64目录。实测用file命令看动态链接器如果是/lib/ld-linux-aarch64.so.1而板子上的ld-linux-aarch64.so.1不在这个路径就会出现这种诡异的问题。解决办法就是把工具链里的ld-linux-aarch64.so.1拷贝到板子对应路径或者编译时用-sysroot让链接器自动处理。7. 常见问题与排查技巧实录7.1 Qt Creator编译时报kit异常如果你在Qt Creator里配置了交叉编译kit结果出现“No suitable kits”或者编译器报错先检查两件事。第一Qt Creator的Qt Versions里是否添加了/opt/Qt-5.14.2-static-aarch64/bin/qmake注意这里要选交叉编译的qmake不是主机上的qmake。第二Compilers里是否添加了aarch64-linux-gnu-g和aarch64-linux-gnu-gcc设置时不能选自动生成要手动指定路径。我遇到的一个典型坑是Qt Creator自动检测到千辛万苦配置好的交叉编译器但qmake识别成了“Desktop Qt 5.14.2”因为qmake是同一个版本号。解决办法是在Kits里手动指定Sysroot为/opt/sysroot同时把CMake或qmake参数加上否则会去主机上找系统头文件。7.2 undefined reference to iconv_open编译Qt时如果出现iconv相关的undefined reference一般是glibc的iconv没有正确链接。这通常是因为configure没有找到sysroot里的iconv实现或者头文件路径冲突。解决方法是configure参数里显式加上-I${SYSROOT}/usr/include \ -L${SYSROOT}/usr/lib如果还不行试试在Makefile里加上-liconv。但真正原因多半是configure时检测到系统iconv可用但链错版本用sysroot后问题自然消失。7.3 编译一个Qt程序时找不到OpenGL的GL/gl.h即使configure时用了-no-opengl有些Qt子模块比如QtGui在某些平台宏下还是可能去include GL/gl.h导致编译报错“GL/gl.h: No such file or directory”。这个一般是因为头文件搜索路径里没有包含mesa的EGL头文件。手机上的解决方案是安装libegl1-mesa-devsudo apt install libegl1-mesa-dev并且configure时加上-QT_QPA_DEFAULT_PLATFORMlinuxfbLinuxFB是纯粹基于framebuffer的显示插件最省事不依赖EGL和OpenGL适合简单界面。如果板子支持GBM或者EGLFS可以研究一下各自的配置但基础功能LinuxFB就够了。7.4 程序运行时提示could not find or load the Qt platform plugin这个报错是Qt嵌入式开发里的经典问题出现的原因是Qt程序启动时要找平台插件比如libqlinuxfb.so或libqeglfs.so。静态编译后这个插件理论上会被编进可执行文件里但如果configure时没有把对应插件模块编进去还是会报错。检查方法ls /opt/Qt-5.14.2-static-aarch64/plugins/platforms/如果没有libqlinuxfb.so说明configure时可能把它skip掉了需要重新编译对应插件模块。Qt的静态插件机制要求在main函数里显式添加插件导入#include QtPlugin Q_IMPORT_PLUGIN(QLinuxFbIntegrationPlugin)如果用了Q_IMPORT_PLUGIN还是找不到检查pro文件里有没有加QTPLUGIN qlinuxfb这个坑我在首次编译时踩过加了QTPLUGIN后才解决。7.5 编译qmake时提示找不到Qt5Config.cmake用CMake写项目的朋友会遇到这个问题qmake生成后想用CMake配置Qt提示找不到Qt5Config.cmake。静态编译的Qt默认不生成CMake配置文件如果一定要支持CMakeconfigure时可以加-no-framework \ -no-dbus \ -opensource \ -confirm-license但更稳妥的策略是直接用qmake管理静态Qt项目省时省力。Qt官方对CMake的重视是Qt6开始才强化的5.14时代qmake才是主流没必要非跟CMake较劲。8. 对整个流程做一个实操总结回头梳理一下Qt 5.14.2的aarch64静态交叉编译核心就四件事准备可用的aarch64工具链、搭建匹配的sysroot、把第三方依赖库交叉编译进sysroot、然后configure Qt时把所有路径和功能都指对。这四个环节环环相扣任何一个地方架构不对或路径错误后面都是灾难。我实际搭建过程中最大的体会就是一定要养成用file命令时刻检查产物架构的习惯。交叉编译最痛苦的不是编译报错而是编译成功了但生成的是x86的程序白忙活半天。每次编译完静态库、动态库、可执行文件都花几秒钟跑一下file发现ELF 64-bit LSB executable, ARM aarch64就放心了。还有一个值得保留的经验是保存好configure参数和编译日志。Qt交叉编译不是一次性能做完美的事情很可能要反复调参。把configure时用的参数存到脚本里下次直接改一两个路径就能复用省得每次手工输一大堆。最后再分享一个小技巧configure完成后建议顺手把configure.log里的检测摘要拍个照或者存成文本文件之后排查问题会非常有用。比如Qt Network OpenSSL到底有没有成功检测到看一眼日志就清楚不需要重新跑一遍配置再猜。这套环境搭建完成后后续编译新模块、新项目就是水磨工夫了框架搭对了剩下的都是道路问题。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →