十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Qt 5.14.2 aarch64静态交叉编译实战:从工具链到部署

Qt 5.14.2 aarch64静态交叉编译实战:从工具链到部署 1. 为什么值得折腾Qt 5.14.2的aarch64静态交叉编译如果你手上有Orange Pi CM5、树莓派这类aarch64开发板又恰好想在上面跑一个不依赖系统Qt库的独立GUI程序那你大概率绕不开“静态交叉编译”这条路。我最初接触这个需求是因为一个工业现场的项目设备端是aarch64架构的板子系统里只带了最精简的运行时环境连Qt的共享库都没有更别提让现场人员去装一堆依赖。每次部署都要把几十个.so文件拷过去版本对不上就崩调试起来非常痛苦。后来下定决心做静态编译把Qt的核心库直接编进可执行文件里一个二进制丢过去就能跑这才算彻底解决了部署问题。Qt 5.14.2这个版本在aarch64静态交叉编译里算是一个比较稳的节点。它比5.12系列对C11/14的支持更完整又不像5.15后期那样对某些模块的依赖关系做了较大调整。网上关于qt-everywhere-src-5.15.10 交叉编译的讨论很多但5.15在静态链接时对OpenGL相关模块的处理更复杂踩坑成本更高。5.14.2的qt-everywhere-src包结构清晰配置脚本的兼容性也好适合作为从零搭建的起点。这篇文章面向的是有一定Linux基础、了解交叉编译概念但没完整走过Qt静态交叉编译全流程的开发者。我会从工具链选型开始一步步讲到configure参数、编译、部署验证把每个关键决策背后的原因说清楚。你不需要事先精通qmake的底层机制但至少要能看懂Makefile和shell脚本。整个流程走下来快的话半天慢的话两天取决于机器性能和踩坑数量。注意静态编译Qt意味着最终可执行文件会非常大通常几十MB起步。如果你的板子存储空间紧张或者需要频繁更新Qt版本静态方案不一定是最优解。但在部署环境不可控、依赖管理成本高的场景下静态编译的收益远大于代价。2. 工具链选型与编译环境搭建2.1 为什么选aarch64-linux-gnu而不是其他工具链交叉编译工具链的选择直接决定了后续所有环节的顺利程度。网上能搜到各种工具链名字比如env工具链、unity工具链、autosar工具链、etas工具链这些大多是特定商业项目或汽车电子领域的专用链对开源Qt的支持并不友好。我们需要的是一条通用、稳定、社区支持好的aarch64工具链。我实测下来gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu这个版本最省心。它是ARM官方维护的GNU工具链对glibc的兼容性好编译Qt时不会出现奇怪的链接错误。有人会问为什么还要用gcc-arm工具链交叉编译直接用板子上的原生编译器不行吗原生编译当然可以但aarch64板子的CPU性能和内存通常有限编译Qt这种大型项目可能要跑几个小时甚至半天而x86_64主机上交叉编译可能只要二三十分钟。时间成本差距巨大。工具链的下载地址在ARM开发者官网可以找到这里不贴具体链接你搜索“gcc-arm-10.3 aarch64-none-linux-gnu”就能找到。下载后解压到/opt/toolchain目录然后把它加到PATH里export TOOLCHAIN_PATH/opt/toolchain/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu export PATH$TOOLCHAIN_PATH/bin:$PATH export CROSS_COMPILEaarch64-none-linux-gnu- export CC${CROSS_COMPILE}gcc export CXX${CROSS_COMPILE}g验证工具链是否可用aarch64-none-linux-gnu-gcc -v如果能看到gcc版本信息说明工具链配置正确。这里有个细节工具链的sysroot路径要记下来后面配置Qt的-sysroot参数时要用。通常在$TOOLCHAIN_PATH/aarch64-none-linux-gnu/libc目录下。2.2 主机环境准备与依赖安装主机系统我推荐Ubuntu 20.04或22.04的x86_64版本。有人尝试在vmware安装ubuntu虚拟机选择arm架构这其实是走弯路——你在arm虚拟机上做aarch64交叉编译相当于用arm主机编译arm目标失去了交叉编译的性能优势。正确的做法是在x86_64主机上装x86_64的Ubuntu然后用aarch64工具链去编译。主机上需要安装的基础依赖包括sudo apt update sudo apt install -y build-essential perl python3 git \ libgl1-mesa-dev libglu1-mesa-dev freeglut3-dev \ libx11-dev libxext-dev libxrender-dev libxcb1-dev \ libxcb-keysyms1-dev libxcb-image0-dev libxcb-shm0-dev \ libxcb-icccm4-dev libxcb-sync-dev libxcb-xfixes0-dev \ libxcb-shape0-dev libxcb-randr0-dev libxcb-render-util0-dev \ libxcb-xinerama0-dev libxcb-xkb-dev libxkbcommon-dev \ libxkbcommon-x11-dev libfontconfig1-dev libfreetype6-dev \ libssl-dev libxcursor-dev libxcomposite-dev libxdamage-dev \ libxrandr-dev libxtst-dev libasound2-dev libpulse-dev \ libcups2-dev libdrm-dev libgstreamer1.0-dev \ libgstreamer-plugins-base1.0-dev这些包看着多但每一个都有用。比如libxcb-*系列是Qt的xcb平台插件依赖的libfontconfig1-dev和libfreetype6-dev是字体渲染的基础libssl-dev是网络模块需要的。少装一个configure阶段可能不报错但编译到一半就挂掉排查起来很费时间。实操心得我习惯在装完依赖后跑一次sudo apt build-dep qt5-default让apt自动补齐Qt官方源里的构建依赖。虽然我们编译的是5.14.2而不是系统源里的版本但依赖列表基本一致能省不少事。2.3 Qt源码获取与目录规划Qt 5.14.2的源码包可以从Qt官方下载站获取文件名是qt-everywhere-src-5.14.2.tar.xz。如果你在国内下载速度慢可以找qt国内镜像比如清华或中科大的镜像站速度会快很多。下载后解压到一个工作目录我习惯放在~/work/qt-build下mkdir -p ~/work/qt-build cd ~/work/qt-build tar xf qt-everywhere-src-5.14.2.tar.xz解压后得到qt-everywhere-src-5.14.2目录。接下来要规划好编译目录和安装目录。Qt官方推荐shadow build也就是在源码目录之外单独建一个编译目录避免污染源码树。我的目录结构是这样的~/work/qt-build/ ├── qt-everywhere-src-5.14.2/ # 源码 ├── build-aarch64-static/ # 编译目录 └── install-aarch64-static/ # 安装目录安装目录最终会被拷贝到开发板上或者作为主机上交叉编译其他项目时的sysroot使用。这个目录要记牢后面配置qmake和编译业务代码时都要用到。3. configure参数逐项拆解与配置脚本编写3.1 静态编译的核心configure选项Qt的configure脚本参数非常多但静态交叉编译真正关键的就那么十几个。我把它们分成四类平台相关、静态链接相关、模块裁剪相关、路径相关。平台相关的参数-xplatform linux-aarch64-gnu-g -device-option CROSS_COMPILE$TOOLCHAIN_PATH/bin/aarch64-none-linux-gnu- -sysroot $TOOLCHAIN_PATH/aarch64-none-linux-gnu/libc-xplatform指定了mkspecs里的平台配置文件。Qt源码的qtbase/mkspecs/devices/下有很多现成的配置但linux-aarch64-gnu-g这个通用配置在5.14.2里需要自己确认是否存在。如果没有可以从linux-arm-gnueabi-g复制一份改一下。-sysroot指向工具链的sysroot这样Qt编译时找头文件和库就不会跑到主机的/usr/include去。静态链接相关的参数-static -static-runtime -no-shared-static让Qt库以静态库形式编译-static-runtime让最终链接时也静态链接运行时库。-no-shared是显式关闭共享库编译和-static配合使用。这三个参数一起用才能保证最终产物不依赖任何Qt的.so文件。模块裁剪相关的参数-skip qtwebengine -skip qtwebview -skip qtquickcontrols -skip qtquickcontrols2 -skip qtgamepad -skip qtserialbus -skip qtserialport -skip qtspeech -skip qtvirtualkeyboard -skip qtwayland -skip qtlocation -skip qtsensors -skip qtconnectivity -skip qtwebsockets -skip qtwebchannel -skip qtxmlpatterns这里要重点说一下qtserialport。很多人在编译业务代码时会遇到unknown module(s) in qt: serialport或者:-1: error: unknown module(s) in qt: serialport原因就是编译Qt时把serialport模块skip掉了但业务代码里又用到了。如果你确定需要串口功能就不要skip它。我一般会保留qtserialport因为工业项目里串口通信太常见了。qtwebengine是必须skip的它依赖Chromium静态编译几乎不可能成功而且体积巨大。qtwayland在静态场景下也建议skip除非你明确需要Wayland支持。路径相关的参数-prefix /home/yourname/work/qt-build/install-aarch64-static -hostprefix /home/yourname/work/qt-build/install-aarch64-static-host-prefix是目标平台的安装路径-hostprefix是主机工具如moc、uic、rcc的安装路径。交叉编译时主机工具需要在x86_64上运行所以要和目标库分开存放。3.2 其他值得关注的配置项-opensource -confirm-license -release -nomake examples -nomake tests -no-opengl -no-xcb -no-xcb-xlib -no-gtk -no-cups -no-pulseaudio -no-alsa -no-feature-dbus -no-feature-icu -qt-zlib -qt-libpng -qt-libjpeg -qt-freetype -qt-pcre -qt-harfbuzz-no-opengl在静态编译里很关键。如果你的板子没有GPU或者不需要OpenGL渲染关掉它能省掉大量依赖和编译时间。-no-xcb也是同理静态编译的Qt程序通常直接跑在framebuffer或者eglfs上不需要X11。-qt-zlib、-qt-libpng这些表示使用Qt自带的第三方库而不是系统库这样能避免交叉编译时找不到aarch64版本的系统库。-no-feature-icu关掉ICU支持能显著减小库体积。ICU主要影响文本编码转换和国际化如果你的程序不需要处理复杂的多语言文本关掉没问题。3.3 完整的configure脚本把上面的参数整理成一个脚本configure-aarch64-static.sh#!/bin/bash set -e TOOLCHAIN_PATH/opt/toolchain/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu BUILD_DIR/home/yourname/work/qt-build/build-aarch64-static INSTALL_DIR/home/yourname/work/qt-build/install-aarch64-static HOST_INSTALL_DIR/home/yourname/work/qt-build/install-aarch64-static-host SRC_DIR/home/yourname/work/qt-build/qt-everywhere-src-5.14.2 mkdir -p $BUILD_DIR cd $BUILD_DIR $SRC_DIR/configure \ -prefix $INSTALL_DIR \ -hostprefix $HOST_INSTALL_DIR \ -xplatform linux-aarch64-gnu-g \ -device-option CROSS_COMPILE$TOOLCHAIN_PATH/bin/aarch64-none-linux-gnu- \ -sysroot $TOOLCHAIN_PATH/aarch64-none-linux-gnu/libc \ -static \ -static-runtime \ -no-shared \ -opensource \ -confirm-license \ -release \ -nomake examples \ -nomake tests \ -no-opengl \ -no-xcb \ -no-xcb-xlib \ -no-gtk \ -no-cups \ -no-pulseaudio \ -no-alsa \ -no-feature-dbus \ -no-feature-icu \ -qt-zlib \ -qt-libpng \ -qt-libjpeg \ -qt-freetype \ -qt-pcre \ -qt-harfbuzz \ -skip qtwebengine \ -skip qtwebview \ -skip qtquickcontrols \ -skip qtquickcontrols2 \ -skip qtgamepad \ -skip qtserialbus \ -skip qtspeech \ -skip qtvirtualkeyboard \ -skip qtwayland \ -skip qtlocation \ -skip qtsensors \ -skip qtconnectivity \ -skip qtwebsockets \ -skip qtwebchannel \ -skip qtxmlpatterns \ -verbose注意-skip qtserialport被我拿掉了因为业务代码可能需要。如果你确定不需要串口可以加回去。注意事项configure脚本执行时间可能比较长因为它要检测工具链、编译测试程序。如果中途报错先看config.log里面会记录详细的失败原因。常见错误包括sysroot路径不对、工具链前缀写错、缺少某个系统依赖。4. 编译、安装与产物验证4.1 并行编译与资源控制configure成功后会生成Makefile。接下来就是make -jNN一般设为主机CPU核心数。比如8核16线程的机器可以设-j16。但要注意内存消耗Qt编译时单个编译单元可能占用1-2GB内存如果内存不够-j设太大反而会触发OOM。make -j$(nproc)如果机器内存小于16GB建议-j8或者-j4。我试过在16GB内存的机器上-j16编译到qtbase后期就频繁OOM改成-j8后稳定通过。编译时间方面8核机器大概30-40分钟4核机器可能要1.5小时。期间可以去做别的事但建议偶尔看一眼输出如果出现大量warning或者某个模块卡住及时处理。4.2 安装与目录结构检查编译完成后make install安装目录下会生成lib、include、plugins、bin等子目录。重点检查lib目录下是否有libQt5Core.a、libQt5Gui.a、libQt5Widgets.a这些静态库文件。如果只有.so没有.a说明静态编译没生效要回去检查configure参数。plugins目录下会有platforms子目录里面是libqeglfs.a、libqlinuxfb.a等平台插件。静态编译时这些插件也是静态库形式链接时需要显式指定。主机工具在$HOST_INSTALL_DIR/bin下包括moc、uic、rcc、qmake。这些工具是x86_64架构的可以在主机上直接运行。4.3 用qmake编译一个测试程序写一个最简单的Qt Widgets程序来验证// main.cpp #include QApplication #include QLabel int main(int argc, char *argv[]) { QApplication app(argc, argv); QLabel label(Hello aarch64 static Qt!); label.show(); return app.exec(); }工程文件test.proQT core gui widgets TARGET test TEMPLATE app SOURCES main.cpp用交叉编译版的qmake生成Makefile$HOST_INSTALL_DIR/bin/qmake test.pro make编译出的test文件用file命令检查file test应该显示ELF 64-bit LSB executable, ARM aarch64。再用ldd检查动态依赖aarch64-none-linux-gnu-ldd test如果显示not a dynamic executable或者statically linked说明静态链接成功。把test拷贝到开发板上直接运行应该能看到一个显示文字的窗口。实操心得第一次跑起来大概率会遇到字体问题显示方框或者乱码。这是因为静态编译时字体配置没有正确打包。解决办法是在开发板的/etc/fonts下放一个fonts.conf指向实际的字体目录或者在程序里用QFontDatabase::addApplicationFont加载字体文件。5. 常见问题排查与避坑指南5.1 编译阶段典型错误错误一unknown module(s) in qt: serialport这个错误在业务代码编译时出现原因是Qt编译时skip了serialport模块。解决办法是重新configure去掉-skip qtserialport然后重新make和install。如果不想全量重编可以进到qtbase目录单独make但serialport在qtserialport子目录里需要单独编译那个模块。错误二cannot mix incompatible qt library (5.15.3) with this library (5.15.2)这个错误通常是因为主机上装了系统Qtqmake找到了系统Qt的头文件或库。解决办法是在.pro文件里显式指定QT_INSTALL_PREFIX或者用交叉编译版的qmake时加上-spec参数指定mkspec。更彻底的办法是在干净的环境里编译不依赖系统Qt。错误三链接时找不到-lGL虽然configure时加了-no-opengl但某些模块可能仍然尝试链接GL。检查config.log里是否有GL相关的检测通过。如果有加上-no-feature-opengl强制关闭。5.2 运行阶段典型问题问题一程序启动后报This application failed to start because no Qt platform plugin could be initialized静态编译时平台插件需要显式链接。在.pro里加上QTPLUGIN qeglfs qlinuxfb或者在main函数里用Q_IMPORT_PLUGIN(QEglFSIntegrationPlugin)宏导入。具体用哪个插件取决于开发板的显示环境。如果是framebuffer用qlinuxfb如果有EGL用qeglfs。问题二中文显示为方框静态编译的Qt默认不包含字体。解决办法有两种一是把字体文件如wqy-microhei.ttc放到开发板/usr/share/fonts下并配置fontconfig二是在程序里用QFontDatabase::addApplicationFont(/path/to/font.ttf)加载。我一般用第二种因为不依赖系统配置部署更简单。问题三程序体积过大静态编译的Qt程序动辄50MB以上。可以用strip命令去掉符号表aarch64-none-linux-gnu-strip test通常能减小30%-50%。还可以用-Os优化编译选项在configure时加上-optimize-size。如果还嫌大可以考虑用UPX压缩但UPX对Qt程序的效果一般而且可能影响启动速度。5.3 常见问题速查表问题现象可能原因解决办法configure报sysroot错误sysroot路径不对检查工具链目录下libc的位置make时OOM并行度太高降低-j数值如-j4链接时找不到Qt库qmake用了系统版本用交叉编译版qmake的绝对路径运行时无平台插件插件未静态链接在pro里加QTPLUGIN或代码里导入中文显示方框缺少字体加载字体文件或配置fontconfig程序体积过大未strip用aarch64-none-linux-gnu-strip处理serialport模块缺失configure时skip了重新configure去掉skip避坑技巧整个流程中最耗时的不是编译本身而是排查configure阶段的依赖缺失。我的经验是在configure之前先用工具链编译一个最简单的C程序确认工具链本身没问题。然后再逐步增加Qt的configure参数每加几个就重新configure一次看是否报错。这样能把问题定位到具体参数而不是一次性面对一堆错误。6. 静态交叉编译后的部署与扩展6.1 部署到Orange Pi CM5的实操记录Orange Pi CM5的CPU是Rockchip RK3588Saarch64架构板子上跑的是Ubuntu 22.04或者Debian 11。把编译好的test程序用scp拷过去scp test orangepi192.168.1.100:/home/orangepi/然后在板子上直接运行./test如果显示窗口说明部署成功。如果报错用export QT_DEBUG_PLUGINS1打开插件调试信息能看到具体是哪个插件加载失败。板子上可能需要设置QT_QPA_PLATFORM环境变量export QT_QPA_PLATFORMlinuxfb或者export QT_QPA_PLATFORMeglfs具体用哪个取决于板子的显示驱动。linuxfb是通用的framebuffereglfs需要GPU驱动支持。RK3588S的Mali GPU在Linux下通常有开源驱动eglfs能跑起来性能更好。6.2 后续扩展交叉编译其他库Qt静态编译成功后你可能还需要交叉编译其他库比如boost库交叉编译、openssl、libcurl等。这些库的交叉编译思路和Qt类似指定--hostaarch64-none-linux-gnu设置CC和CXX环境变量指定--prefix到安装目录。以boost为例./bootstrap.sh --prefix$INSTALL_DIR echo using gcc : aarch64 : $TOOLCHAIN_PATH/bin/aarch64-none-linux-gnu-g ; project-config.jam ./b2 install --prefix$INSTALL_DIR编译好的boost库放到和Qt同一个安装目录下业务代码里就能同时链接Qt和boost的静态库。6.3 关于Qt版本选择的补充有人会问为什么不直接用qt-everywhere-src-5.15.10 交叉编译5.15.10确实更新但对静态编译的支持在某些模块上不如5.14.2稳定。特别是qtdeclarative模块5.15后期版本在静态链接时对QML插件的处理有变化容易出现运行时找不到QML模块的问题。5.14.2的QML静态链接方案更成熟社区里的成功案例也更多。如果你不需要QML只用Widgets那5.15.10也可以。但如果项目里涉及QML我建议还是用5.14.2能省掉很多调试时间。另外qt5.12.10交叉编译也是一个选择但5.12对C17的支持不完整某些现代C特性用不了。5.14.2在语言特性和稳定性之间取得了比较好的平衡。6.4 静态编译与动态编译的取舍静态编译不是银弹。它的优势是部署简单、依赖少、版本可控。劣势是体积大、更新麻烦、无法共享库内存。如果你的项目需要频繁更新Qt版本或者多个程序共用Qt库动态编译更合适。但如果你的场景是嵌入式设备、工业现场、一次性部署静态编译的收益非常明显。我个人的经验是对于交付给客户的设备端程序静态编译能减少80%的现场支持工作量。客户不需要懂Linux不需要装依赖拿到二进制就能跑。这一点在商业项目里价值很大。最后分享一个小技巧如果静态编译后的程序启动速度慢可以在configure时加上-no-feature-accessibility和-no-feature-action等裁剪选项减少初始化时的插件加载数量。具体裁剪哪些可以用configure -list-features查看所有特性然后根据项目需求逐个关闭。
返回列表