资讯动态

Qt5.14.2 aarch64静态交叉编译实战:从环境搭建到部署避坑

发布时间:2026/9/20 2:42:18 来源:尧图企业网站定制
1. 为什么值得折腾Qt5.14.2的aarch64静态交叉编译如果你手上有Orange Pi、树莓派这类aarch64开发板又想把Qt程序直接丢上去跑不依赖板子上的Qt运行库那静态交叉编译几乎是绕不开的一条路。我第一次在Orange Pi CM5上部署Qt程序的时候图省事直接在板子上装了Qt5的运行时结果程序拷过去各种库版本对不上cannot mix incompatible qt library这种报错反复出现折腾了一整天才跑起来。后来换成静态编译一个二进制文件拷过去就能跑那种清爽感真的回不去了。Qt5.14.2这个版本在嵌入式圈子里用得特别多一方面它是LTS版本稳定性经过大量项目验证另一方面它对aarch64的支持已经相当成熟配合qt-everywhere-src-5.14.2源码包可以完整地做静态构建。所谓静态交叉编译说白了就是在你的x86_64开发机上用aarch64的交叉编译工具链把Qt的所有依赖包括Qt自身的库全部编译成静态库最终链接出一个不依赖任何动态库的可执行文件。这个文件丢到板子上只要内核和C库兼容直接就能运行。这套流程适合谁呢如果你在做嵌入式Qt开发板子存储空间有限、不想在板子上装一堆Qt运行库或者你希望发布软件时只给用户一个文件那静态交叉编译就是你的菜。当然这个过程坑不少从工具链选择到configure参数从依赖库编译到最终的链接报错每一步都可能卡住你。我踩过的坑包括但不限于unknown module(s) in qt: serialport、链接时找不到libstdc.a、板子上跑起来字体乱码等等。这篇手册就是把这些经验整理出来让你少走弯路。2. 环境准备与工具链选型2.1 开发机环境搭建开发机我建议用Ubuntu 20.04或者22.04的x86_64版本别用CentOS 7.9虽然CentOS 7.9也能做但它的glibc版本太老编译出来的东西在较新的板子上反而可能出问题。我实测下来Ubuntu 20.04最稳gcc版本是9.x配合aarch64的工具链兼容性最好。先装一堆基础依赖这些是编译Qt源码必须的sudo apt update sudo apt install -y build-essential libgl1-mesa-dev libglu1-mesa-dev \ libxkbcommon-dev libxkbcommon-x11-dev libfontconfig1-dev \ libfreetype6-dev libpng-dev libjpeg-dev libssl-dev \ python3 perl git wget flex bison gperf \ libx11-dev libxext-dev libxrender-dev libxi-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 libxcb-glx0-dev这些包看着多但每一个都有用。比如libxcb-*系列是Qt的xcb平台插件依赖的libfontconfig1-dev和libfreetype6-dev是字体渲染的基础少了它们configure阶段就会报错。我建议一次性装完别等到报错了再一个个补那样很浪费时间。2.2 aarch64交叉编译工具链选择工具链的选择直接决定了后面能不能顺利编译。市面上常见的aarch64工具链有几种Linaro的GCC、ARM官方的GNU Toolchain、以及各大板子厂商提供的工具链。我推荐用Linaro的gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu这个版本经过大量项目验证对C17的支持也够用。下载解压后放到/opt目录下sudo tar -xvf gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar.xz -C /opt/ export PATH/opt/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin:$PATH验证一下工具链是否可用aarch64-linux-gnu-gcc -v如果能看到gcc version 7.5.0说明工具链没问题。这里有个细节要注意工具链的sysroot里包含了目标板的C库和头文件Qt编译时会用到这些。Linaro的工具链sysroot在aarch64-linux-gnu/libc目录下configure的时候需要指定-sysroot参数。提示不要用Ubuntu自带的gcc-aarch64-linux-gnu包那个工具链的sysroot不完整编译Qt时会出现找不到crt1.o之类的错误。我试过一次折腾了半天最后还是换回Linaro的。2.3 目录规划与源码准备在开始之前先把目录结构规划好后面会清晰很多mkdir -p ~/qt-aarch64/{src,build,install} cd ~/qt-aarch64/src wget https://download.qt.io/archive/qt/5.14/5.14.2/single/qt-everywhere-src-5.14.2.tar.xz tar -xvf qt-everywhere-src-5.14.2.tar.xz源码包大概有500多MB解压后接近2GB。如果你下载速度慢可以用国内镜像比如清华的mirrors.tuna.tsinghua.edu.cn/qt/速度会快很多。解压完成后源码目录是qt-everywhere-src-5.14.2我们所有的编译操作都在这个目录之外进行保持源码干净。3. 核心配置与编译参数拆解3.1 configure参数逐项解析Qt的configure脚本参数非常多但做静态交叉编译核心参数就那么十几个。我先把完整的configure命令列出来然后逐项解释为什么这么设cd ~/qt-aarch64/build ../src/qt-everywhere-src-5.14.2/configure \ -prefix /home/yourname/qt-aarch64/install \ -opensource -confirm-license \ -release -static -optimize-size \ -xplatform linux-aarch64-gnu-g \ -sysroot /opt/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/aarch64-linux-gnu/libc \ -nomake examples -nomake tests \ -skip qtwebengine -skip qtwebview \ -no-opengl -no-openssl \ -qt-zlib -qt-libpng -qt-libjpeg -qt-freetype -qt-pcre \ -no-feature-accessibility \ -no-feature-cups -no-feature-printdialog \ -no-feature-sql-sqlite \ -no-icu -no-glib \ -no-dbus -no-xcb -no-wayland \ -no-compile-examples \ -v-prefix指定安装路径这个路径是开发机上的路径不是板子上的。-static是核心告诉Qt编译静态库。-optimize-size会让编译器优先优化体积对于嵌入式场景很实用编译出来的库会小不少。-xplatform linux-aarch64-gnu-g指定使用哪个平台配置文件。Qt源码里qtbase/mkspecs/目录下有很多平台配置但默认没有aarch64的我们需要自己创建一个。这个后面会详细说。-sysroot指向工具链的sysrootQt编译时会从这里找目标板的C库和头文件。-nomake examples -nomake tests跳过示例和测试能省大量编译时间。-skip qtwebengine跳过WebEngine模块这个模块依赖Chromium编译极其耗时且容易出错嵌入式场景基本用不上。-no-opengl和-no-openssl是因为板子上通常没有GPU和OpenSSL如果你确实需要OpenSSL可以自己交叉编译一个再链接进来。-qt-zlib -qt-libpng -qt-libjpeg -qt-freetype -qt-pcre表示使用Qt自带的这些第三方库而不是系统库这样能避免依赖问题。-no-feature-*系列是裁剪功能accessibility、cups、printdialog这些在嵌入式设备上基本用不到裁掉能减小体积。-no-icu -no-glib也是同理ICU是国际化库体积很大如果你的程序不需要多语言支持直接关掉。3.2 创建aarch64的mkspec配置文件Qt默认没有aarch64的mkspec我们需要在源码目录里创建一个cd ~/qt-aarch64/src/qt-everywhere-src-5.14.2/qtbase/mkspecs sudo cp -r linux-arm-hisilicon-hix5hd2-g linux-aarch64-gnu-g cd linux-aarch64-gnu-g然后编辑qmake.conf内容改成MAKEFILE_GENERATOR UNIX CONFIG incremental QMAKE_INCREMENTAL_STYLE sublib include(../common/linux.conf) include(../common/gcc-base-unix.conf) include(../common/g-unix.conf) QT_QPA_DEFAULT_PLATFORM linuxfb QMAKE_CC aarch64-linux-gnu-gcc QMAKE_CXX aarch64-linux-gnu-g QMAKE_LINK aarch64-linux-gnu-g QMAKE_LINK_SHLIB aarch64-linux-gnu-g QMAKE_AR aarch64-linux-gnu-ar cqs QMAKE_OBJCOPY aarch64-linux-gnu-objcopy QMAKE_NM aarch64-linux-gnu-nm -P QMAKE_STRIP aarch64-linux-gnu-strip load(qt_config)这里QT_QPA_DEFAULT_PLATFORM linuxfb指定默认的平台插件是linuxfb也就是直接写framebuffer不依赖X11或Wayland。如果你的板子跑的是X11可以改成xcb但嵌入式场景大多数用linuxfb或者eglfs。注意QMAKE_CC这些变量里的工具链前缀必须和你的实际工具链名字一致。如果你用的是aarch64-none-linux-gnu-前缀这里也要相应改掉。我见过有人直接复制网上的配置结果工具链名字对不上configure阶段就报command not found。3.3 静态编译的依赖处理策略静态编译最麻烦的地方在于依赖库的处理。Qt本身依赖zlib、libpng、libjpeg、freetype、pcre这些第三方库如果系统里没有aarch64版本的静态库就必须用Qt自带的。这就是为什么configure里要加-qt-zlib -qt-libpng这些参数。但有些库Qt不自带比如OpenSSL、DBus、GLib这些就需要你自己交叉编译。我的建议是能不用就不用嵌入式场景下这些库往往不是必需的。如果确实需要OpenSSL可以这样交叉编译cd ~/qt-aarch64/src wget https://www.openssl.org/source/openssl-1.1.1w.tar.gz tar -xvf openssl-1.1.1w.tar.gz cd openssl-1.1.1w ./Configure linux-aarch64 shared no-asm \ --prefix/home/yourname/qt-aarch64/install \ --cross-compile-prefixaarch64-linux-gnu- make -j$(nproc) make install编译完成后在Qt的configure里加上-openssl-linked -I /home/yourname/qt-aarch64/install/include -L /home/yourname/qt-aarch64/install/lib即可。不过要注意静态链接OpenSSL时最终的可执行文件会大不少而且OpenSSL的许可证和Qt的许可证兼容性需要你自己确认。4. 编译过程与关键环节实操4.1 执行configure与常见报错处理configure命令执行后会输出一大堆检测信息。如果一切顺利最后会看到Qt is now configured for building的提示。但实际情况往往没那么顺利我整理了几个最常见的报错和解决方法报错一ERROR: Cannot find libxcb这是因为缺少xcb相关的开发库。虽然我们最终不用xcb但configure阶段还是会检测。解决方法就是装齐前面列出的那些libxcb-*包。如果装完了还报错检查一下是不是pkg-config找不到可以用export PKG_CONFIG_PATH/usr/lib/x86_64-linux-gnu/pkgconfig指定路径。报错二ERROR: Feature fontconfig was enabled, but the pre-condition libs.fontconfig failed缺少fontconfig的开发库装libfontconfig1-dev即可。如果装完了还报错可能是sysroot里没有fontconfig这时候可以加-no-fontconfig关掉它改用Qt自带的freetype渲染字体。报错三Project ERROR: Unknown module(s) in QT: serialport这个报错通常出现在你编译完Qt后编译自己的项目时。原因是configure时没有启用serialport模块。Qt5.14.2的serialport模块默认是自动检测的如果检测不到就会跳过。你可以在configure时显式加上-qt-serialport强制启用。如果还是不行检查一下qtbase/src/plugins/目录下有没有serialport相关的源码。报错四cannot mix incompatible qt library (5.15.3) with this library (5.15.2)这个报错说明你的程序链接了不同版本的Qt库。静态编译时一般不会遇到但如果你之前装过系统Qtqmake可能会找到系统Qt的库。解决方法是确保PATH里你的交叉编译qmake在最前面或者直接用完整路径调用qmake。4.2 编译与安装configure成功后就可以开始编译了make -j$(nproc) 21 | tee build.log编译时间取决于你的机器性能一般8核机器大概需要1到2小时。-j$(nproc)表示用所有CPU核心并行编译能快不少。tee build.log把编译日志保存下来方便出错时排查。编译过程中如果报错先看日志最后几行通常是某个模块编译失败。常见的错误包括头文件找不到、链接时符号未定义、内存不足等。内存不足的话可以减少并行编译的job数比如make -j4。编译完成后安装make install安装完成后~/qt-aarch64/install目录下就是完整的Qt静态库和头文件。检查一下lib目录应该能看到libQt5Core.a、libQt5Gui.a、libQt5Widgets.a这些静态库文件。4.3 验证交叉编译工具链安装完成后用交叉编译的qmake验证一下~/qt-aarch64/install/bin/qmake -query输出里应该能看到QT_INSTALL_PREFIX指向你的安装路径QMAKE_SPEC是linux-aarch64-gnu-g。然后写一个最简单的Qt程序测试#include QApplication #include QLabel int main(int argc, char *argv[]) { QApplication app(argc, argv); QLabel label(Hello aarch64); label.show(); return app.exec(); }用交叉编译的qmake生成Makefile并编译~/qt-aarch64/install/bin/qmake test.pro make file testfile命令的输出应该显示ELF 64-bit LSB executable, ARM aarch64说明编译成功。把这个文件拷到板子上加上-platform linuxfb参数运行./test -platform linuxfb如果板子上有屏幕应该能看到一个显示Hello aarch64的窗口。5. 常见问题与排查技巧实录5.1 编译阶段问题速查表问题现象可能原因解决方法Cannot find -lstdc工具链的静态库路径没配好在qmake.conf里加QMAKE_LFLAGS -static-libstdc或检查sysroot路径undefined reference to pthread_create链接时缺少pthread库在pro文件里加LIBS -lpthreaderror: numeric_limits is not a member of std缺少#include limits在源码里补上头文件这是gcc版本差异导致的Unknown module(s) in QT: serialportserialport模块没编译configure时加-qt-serialport或单独编译该模块qrc: file not found资源文件路径不对检查pro文件里的RESOURCES路径用相对路径relocation R_AARCH64_ADR_PREL_PG_HI21静态链接时的重定位问题加-fPIC重新编译或在链接时加-Wl,-z,notext程序在板子上跑起来字体乱码没有嵌入字体用QFontDatabase::addApplicationFont加载字体文件或编译时加-qt-freetype5.2 静态链接的坑与避坑技巧静态链接最大的坑是符号冲突和重复定义。比如你的程序同时链接了Qt的静态库和系统里的某个静态库两者都定义了同一个符号链接器就会报错。我遇到过一次程序里用了libpngQt也自带了libpng结果链接时符号冲突。解决方法是确保只用一方的库要么全用Qt自带的要么全用系统的。另一个坑是静态库的链接顺序。GNU链接器对静态库的顺序很敏感被依赖的库要放在后面。比如libQt5Widgets.a依赖libQt5Gui.alibQt5Gui.a依赖libQt5Core.a链接顺序就应该是-lQt5Widgets -lQt5Gui -lQt5Core。顺序反了就会报undefined reference。用qmake的话它会自动处理这个顺序但如果你手动写Makefile就要特别注意。还有一个经验静态编译时尽量加-Wl,--gc-sections这个选项会让链接器丢弃未使用的代码段能显著减小最终可执行文件的体积。配合编译时的-ffunction-sections -fdata-sections一起用效果更好。我实测过一个简单的Qt程序不加这些选项是15MB加了之后降到8MB左右。5.3 板子上的部署与运行调试程序拷到板子上后如果运行报错可以用ldd检查动态依赖ldd ./test静态编译的程序应该显示not a dynamic executable。如果显示了一堆依赖说明静态编译没成功检查一下是不是链接了动态库。如果程序运行起来但界面显示不正常比如花屏、颜色不对通常是framebuffer的像素格式问题。可以在程序启动时指定./test -platform linuxfb:fb/dev/fb0:size1024x600:offset0x0如果板子支持OpenGL ES可以用eglfs平台插件性能会好很多./test -platform eglfs但eglfs需要板子的GPU驱动支持不是所有板子都能用。Orange Pi CM5的GPU驱动在官方镜像里是有的但需要确认/dev/dri/card0设备节点存在。提示静态编译的程序在板子上运行时如果报QFontDatabase: Cannot find font directory说明字体路径不对。可以在程序里用QFontDatabase::addApplicationFont(/path/to/font.ttf)手动加载字体或者把字体文件放到/usr/share/fonts/目录下。6. 进阶优化与模块裁剪6.1 按需裁剪Qt模块减小体积静态编译出来的程序体积往往比较大因为Qt把很多用不到的模块也链接进来了。可以通过configure时的-skip参数裁剪掉不需要的模块。比如你的程序只用Widgets那可以跳过qtdeclarative、qtquickcontrols、qtmultimedia这些-skip qtdeclarative -skip qtquickcontrols -skip qtquickcontrols2 \ -skip qtmultimedia -skip qtwebsockets -skip qtsensors \ -skip qtlocation -skip qtconnectivity -skip qtwayland裁剪之后编译时间会大幅缩短最终的可执行文件也能小不少。我做过对比完整编译的Qt静态库大概有500MB裁剪后能降到200MB左右。当然具体裁剪哪些模块要根据你的项目需求来定别裁掉了正在用的模块。6.2 编译选项优化与体积控制除了裁剪模块编译选项也能影响体积。-optimize-size会让gcc用-Os而不是-O2优先优化体积。如果对性能要求不高这个选项很实用。另外可以在qmake.conf里加QMAKE_CFLAGS_RELEASE -ffunction-sections -fdata-sections QMAKE_CXXFLAGS_RELEASE -ffunction-sections -fdata-sections QMAKE_LFLAGS_RELEASE -Wl,--gc-sections这些选项配合使用能让链接器丢弃未使用的函数和数据进一步减小体积。还有一个技巧是编译时加-fno-exceptions和-fno-rtti但这要求你的代码不使用异常和RTTIQt本身是支持的但如果你用了第三方库可能就不行了。6.3 静态编译与动态编译的取舍静态编译虽然部署方便但也不是没有缺点。首先是体积大一个简单的Qt程序静态编译后可能十几MB动态编译只有几百KB。其次是内存占用静态链接的程序在运行时会把整个库加载到内存而动态链接是多个程序共享同一份库。最后是更新麻烦如果Qt出了安全补丁静态编译的程序需要重新编译整个程序而动态链接只需要更新库文件。所以我的建议是如果你的板子存储空间充足而且上面跑多个Qt程序那动态编译更合适。如果板子空间紧张或者你只想发布一个独立的可执行文件那静态编译是更好的选择。我自己的项目里Orange Pi CM5的eMMC有32GB空间不是问题但我还是选了静态编译因为部署简单一个文件拷过去就能跑不用管板子上的Qt版本。7. 我踩过的那些坑与个人经验最后分享几个我在实际项目中踩过的坑希望能帮你省点时间。第一个坑是工具链的sysroot路径。Linaro的工具链解压后sysroot在aarch64-linux-gnu/libc目录下但有些版本的路径是aarch64-linux-gnu/sysroot。我一开始没注意configure时-sysroot指错了路径结果编译时找不到stdio.h。后来用find /opt/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu -name stdio.h找了一下才发现路径不对。第二个坑是Qt的版本和qmake的版本不匹配。我开发机上装了系统Qt5.15.3交叉编译的是5.14.2结果用交叉编译的qmake生成Makefile时qmake却调用了系统Qt的库。原因是PATH里系统Qt的qmake在前面。解决方法是用完整路径调用交叉编译的qmake或者在PATH里把交叉编译的qmake放到最前面。第三个坑是静态链接时的-lpthread。Qt的静态库依赖pthread但qmake生成的Makefile里有时候不会自动加上-lpthread导致链接时报undefined reference to pthread_create。解决方法是在pro文件里手动加LIBS -lpthread或者在qmake.conf里加QMAKE_LIBS -lpthread。第四个坑是板子上的字体。静态编译的程序在板子上跑起来后界面上的文字全是方块。原因是板子的文件系统里没有字体文件而Qt默认会去/usr/share/fonts/找。解决方法是在程序里用QFontDatabase::addApplicationFont加载一个字体文件或者把字体文件打包到程序资源里。我一般用后者把字体文件放到qrc里程序启动时加载这样就不依赖板子的文件系统了。第五个坑是编译时的内存不足。Qt的某些模块编译时非常吃内存比如qtdeclarative单个编译单元可能占用2GB以上内存。如果你的开发机内存小于8GB建议减少并行编译的job数用make -j2甚至make -j1。我一开始用make -j8结果编译到一半被OOM Killer杀掉了日志里只看到Killed排查了半天才发现是内存不够。这套流程走下来从零到跑通大概需要半天到一天的时间主要时间花在编译上。但一旦编译成功后面就轻松了程序改完直接交叉编译拷到板子上就能跑。希望这篇手册能帮你顺利搞定Qt5.14.2的aarch64静态交叉编译少踩几个坑。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价