简介:面向嵌入式Linux与Qt开发者的交叉编译环境配置指南,聚焦在Ubuntu 20.04上搭建Qt5.12.12搭配aarch64-linux-gnu工具链的完整流程。资源为1个PDF文档,约4MB,内容涵盖从petalinux2018.3中提取交叉编译器、配置环境变量与SYSROOT,到修改qmake.conf、设置QPA平台抽象插件、安装音视频与图形依赖库、执行configure与make编译安装的全部环节,并标注了Linaro GCC 7.3版本验证方式与无报错编译细节。文档以图文结合方式给出关键配置的终端回显,还整理了qmake模板参考、OpenGL支持失败、make与g++缺失等典型问题的排查思路,以及Qt在线安装工具的版本选择建议,强调交叉编译库与主机工具链的一致性。目前已有12360人学习,适合需要在ARM架构设备上运行Qt应用的开发者参考,能帮助读者快速理解交叉编译平台的配置逻辑,减少重复踩坑,并可作为搭建类似ARM端Qt编译环境的对照手册。
1. 从 x86 桌面到 aarch64 目标板:Qt5.12.12 交叉编译要解决的三个错位
在 Ubuntu 20.04 上敲一句apt install qt5-default,qmake 是有了,编出来的程序拷到 OrangePi CM5、树莓派 4B 这类 aarch64 板子上,第一反应通常是Exec format error——因为那份 qmake 是给 x86_64 用的,产出的是 x86 ELF,指令集那一层就对不上。第二个错位在库路径:即便你手工用交叉 g++ 把程序编出来了,链接阶段找的是宿主机/usr/lib/x86_64-linux-gnu里的libQt5Core.so,ABI 不匹配直接报 incompatible。第三个错位在运行时:板子上没有/opt/Qt5.12.12/lib,字体目录为空、libQt5Gui.so加载不到,程序一启动就死在平台插件初始化。
这套环境要做的就是一次性把这三处对齐:宿主机装aarch64-linux-gnu工具链,用一份和目标板 rootfs 同源的 sysroot 顶掉宿主机头文件与库,再把 Qt 5.12.12 源码按 aarch64 重新 configure 一遍,产出一套能整体 rsync 到板子上的 Qt。做完之后,宿主机上一次 qmake、一次 make,产物直接上板能跑。适合做嵌入式 HMI、工控屏、机器人上位机这类需要在 ARM 板子上跑 Qt 界面的场景。
2. aarch64-linux-gnu 交叉编译工具链在 Ubuntu 20.04 上的安装与自检
2.1 apt 源里的工具链和厂商 SDK 自带那份怎么选
Ubuntu 20.04 官方源里就有gcc-aarch64-linux-gnu、g++-aarch64-linux-gnu,装完前缀是aarch64-linux-gnu-,落地在/usr/bin。它带来的好处是版本稳定、头文件齐全、和宿主机的 glibc 同代,编 Qt 这种体量的工程不容易在头文件上卡住。缺点是它面向的是通用aarch64-linux-gnu目标,不带任何厂商的启动流程和特殊浮点选项——但对编 Qt 用户态库这件事,这一点无所谓。
另一条路是用板卡厂商 SDK 里那份工具链,或者芯片原厂发布的预编译包。它的优势是 ABI、浮点 ABI(hard/soft)、glibc 版本这些细节和官方 BSP 完全一致,最不容易出玄学问题。代价是要手工加 PATH,而且不同厂商的目录嵌套风格差别很大,脚本不好写通用。
我的建议是:先用 apt 那份把整条链路跑通,确认 Qt 能在板子上起来;等真的要交付,再换成厂商 SDK 那份重编一次。两条路除了QMAKE_CC那几行的路径不同,其余流程完全一致。
# 宿主机 Ubuntu 20.04,安装 aarch64 交叉工具链 sudo apt update sudo apt install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu \ binutils-aarch64-linux-gnu \ qemu-user-static rsync cmake ninja-build # 如果是多架构,先确认自己没把 arm64 当外架构源误加进去 dpkg --print-foreign-architectures这几条命令里,qemu-user-static是给后续本地跑 aarch64 二进制做验证用的,binutils单独列出来是因为有时候 apt 只装了 gcc 没把aarch64-linux-gnu-ld带上。最后那条dpkg --print-foreign-architectures用来确认当前系统有没有被误加过arm64外架构——如果有,后面装依赖包时 apt 会尝试去拉 arm64 的 dev 包覆盖 amd64 的,很容易把宿主机环境搞坏,看到非空输出要先dpkg --remove-architecture arm64清掉。
2.2 交叉工具链的三条自检命令
装完不要直接进 Qt 源码目录,先用三条命令确认工具链是活的。
aarch64-linux-gnu-gcc -dumpmachine aarch64-linux-gnu-gcc -v 2>&1 | tail -n 3 aarch64-linux-gnu-g++ -print-sysroot第一条应该输出aarch64-linux-gnu,出现x86_64说明 PATH 里被别的同名工具抢了;第二条看它报的 target 和线程模型,确认没有把arm-linux-gnueabihf的配置混进来;第三条打印默认 sysroot,apt 装的这套通常输出为空或者/,说明它直接用宿主机的/usr/include——这也正是后面必须显式加--sysroot的原因。三条都符合预期,再往下走。
2.3 用一段最小 C++ 程序验证链接与运行
真正跑一次编译链接,比看版本号靠谱。
// hello.cpp:验证工具链能否编出 aarch64 可执行文件 #include <cstdio> #include <string> int main() { std::string s = "hello aarch64"; std::printf("%s\n", s.c_str()); return 0; }# 静态编译,避免本地 qemu 跑的时候缺 aarch64 版 libstdc++ aarch64-linux-gnu-g++ -static hello.cpp -o hello_arm64 file hello_arm64 qemu-aarch64-static ./hello_arm64-static是刻意加的:本机没装 aarch64 版的 libstdc++,动态链接时 qemu 会报找不到.so.6,静态链接把这层绕过去,只验证「编得出、链接得过、能执行」三件事。file输出里出现ARM aarch64才算过。这一步用不了 qemu 也没关系,把hello_arm64scp 到板子上跑一遍同样能确认。若file报的是x86-64,说明用了错误的编译器,回去检查qmake.conf或命令里的前缀。
| 检查项 | 期望输出 | 异常时的常见原因 |
|---|---|---|
-dumpmachine | aarch64-linux-gnu | PATH 被同名 x86 工具覆盖 |
file产物 | ELF 64-bit LSB, ARM aarch64 | 用了宿主 g++ 而非交叉 g++ |
qemu-aarch64-static运行 | 正常打印 | 动态链接缺 aarch64 libc |
-print-sysroot | 为空或/ | 正常,需命令行显式指定 |
3. 目标板 sysroot 的抓取与 Qt5.12.12 依赖库的交叉编译
3.1 从板子 rsync 一份 sysroot 到宿主机
Qt 编 aarch64 版本时,会去找一堆系统库的头和.so:libdrm、libpng、libpthread、libfontconfig等。这些包在宿主机的/usr/include里全是 x86_64 的版本,直接拿过去编会撞 ABI。最省力的做法是把目标板上的运行环境整体抓一份下来当 sysroot。
# 在宿主机执行,从板子拉取运行环境 sudo mkdir -p /opt/sysroot sudo rsync -avz --delete \ --exclude=/proc --exclude=/sys --exclude=/dev \ --exclude=/run --exclude=/tmp \ --exclude=/etc/ssh \ root@192.168.1.100:/lib /opt/sysroot/ sudo rsync -avz root@192.168.1.100:/usr/lib /opt/sysroot/usr/ sudo rsync -avz root@192.168.1.100:/usr/include /opt/sysroot/usr/-a保留符号链接这一点很关键:很多发行版里/lib是指向/usr/lib的软链,如果 rsync 把链接解开成实体目录,链接期会因为路径重复报警告,甚至-rpath-link找不到库。--exclude掉/proc、/sys、/dev是避免抓到挂载点和字符设备节点,/etc/ssh只是个人信息卫生习惯,跟编译无关。抓完检查一下ls /opt/sysroot/lib/ld-linux-aarch64.so.1是否存在,这是交叉链接器最后的依赖,缺了链接一定失败。
3.2 哪些依赖能从 sysroot 拿,哪些必须自己编
从板子抓下来的 sysroot 通常只有运行时.so和少量.h,没有.pc文件,也没有-devpackage 里的静态库和头。判断某个依赖要不要自己编,看三件事:头文件在不在、.so符号链接在不在、.pc文件在不在。
# 快速体检某个库是否可直接用于 Qt 编译 LIB=libpng16 ls /opt/sysroot/usr/include/libpng16/png.h # 头 ls /opt/sysroot/usr/lib/aarch64-linux-gnu/libpng* # 动态库与 .so 软链 find /opt/sysroot -name "${LIB}.pc" # pkg-config 文件三样齐了就可以直接指向 sysroot 让 Qt configure 去用;缺了.h或.pc,就得自己交叉编译一份装进 sysroot。这个判断特别重要,因为一旦漏了某个库,Qt configure 会静默地把对应 feature 关掉,你会在编到一半时才发现缺功能。
注意:不要试图用
dpkg --add-architecture arm64加 ports 源来 apt 装 arm64 的 dev 包。Ubuntu 20.04 的 ports 源和 amd64 源混在一起之后,很容易在下次apt upgrade里卸载掉宿主机的库,风险远大于收益。sysroot + 源码编译更可控。
3.3 libpng、fontconfig、libxkbcommon 的交叉编译模板
Qt 5.12 的 eglfs/linuxfb 平台里,字体、图像、输入是三个最常缺的环节。下面这套模板改改--host就能套用到大部分 autotools 库。
# 以 libpng 为例,交叉编译并安装到 sysroot ./configure --host=aarch64-linux-gnu \ --prefix=/opt/sysroot/usr \ --enable-shared --disable-static \ CFLAGS="--sysroot=/opt/sysroot" \ LDFLAGS="--sysroot=/opt/sysroot -Wl,-rpath-link,/opt/sysroot/usr/lib/aarch64-linux-gnu" make -j$(nproc) && sudo make install--host触发 configure 的交叉编译模式,它会去找aarch64-linux-gnu-gcc;--prefix直接指到 sysroot 的/usr,这样装完不用再 copy;CFLAGS/LDFLAGS里的--sysroot是为了让编译器不去翻宿主机的头;-rpath-link告诉链接器在--sysroot之外再额外找一层运行库路径,避免只链接不运行的场景下报 undefined。freetype、fontconfig、libxkbcommon都是同一套,fontconfig编之前要先装libexpat(同样方式交叉编进去),否则它的 configure 会跳过 dev 分支。
| 依赖 | 用途 | 是否可从 sysroot 直接拿 | 编译注意 |
|---|---|---|---|
| libpng16 | QtGui 图像解码 | 头 + .so 常在 sysroot | 版本要和目标板运行库一致 |
| freetype2 | 字体光栅化 | 通常需自编 | 依赖 zlib,先编 zlib |
| fontconfig | 字体查找 | 通常需自编 | 依赖 expat,注意 xml 路径 |
| libxkbcommon | eglfs 键盘映射 | 需自编 | 用 meson 时加 cross file |
| libdrm | eglfs/KMS | 通常可从 sysroot | 版本和内核 DRM 头要匹配 |
4. Qt 5.12.12 源码 configure 参数拆解与 mkspec 定制
4.1 复制 linux-aarch64-gnu-g++ 并改写 qmake.conf
Qt 5.12 自带qtbase/mkspecs/linux-aarch64-gnu-g++这个规格,但它默认走宿主机头文件,也没有 sysroot,需要基于它改一份。
# qtbase/mkspecs/linux-aarch64-gnu-g++/qmake.conf 关键段落 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 SYSROOT = /opt/sysroot QMAKE_CFLAGS += --sysroot=$$SYSROOT QMAKE_CXXFLAGS += --sysroot=$$SYSROOT QMAKE_LFLAGS += --sysroot=$$SYSROOT \ -Wl,-rpath-link,$$SYSROOT/usr/lib/aarch64-linux-gnu load(qt_config)QMAKE_LINK_SHLIB必须和QMAKE_LINK一致,否则 QtWebEngine、QtMultimedia 这类会编共享库的模块在链接阶段会用回宿主 g++,报一堆 ABI 不匹配。QT_QPA_DEFAULT_PLATFORM = linuxfb决定程序不指定-platform时的默认后端,板上只有 framebuffer 就写linuxfb,接了 GPU 走 KMS 就写eglfs。-rpath-link那行是最后兜底的:链接共享库时-sysroot只能保证编译期找得到.so,符号解析不到时还得靠-rpath-link指定目录。
4.2 configure 参数逐条说明
# 在 qt-everywhere-src-5.12.12 根目录执行 ./configure -prefix /opt/Qt5.12.12-aarch64 \ -xplatform linux-aarch64-gnu-g++ \ -sysroot /opt/sysroot \ -opensource -confirm-license \ -release -shared \ -nomake examples -nomake tests \ -no-opengl -no-xcb -no-gtk \ -linuxfb -eglfs \ -qt-libpng -qt-libjpeg -qt-zlib -qt-freetype -qt-harfbuzz \ -no-feature-cups -no-iconv -no-glib \ -skip qtwebengine -skip qt3d -skip qtcharts \ -v 2>&1 | tee configure.log-prefix是安装目录,编完后所有.so、头文件、qmake 都在这里;-xplatform指定用刚才改的 mkspec;-sysroot是 Qt 5.12 特有的开关,会把这个路径塞进所有编译单元。-no-opengl -no-xcb是嵌入式最常见的选择:板上没 X11、没桌面 GL,这两个开了只会浪费编译时间还会引入一堆依赖;需要 GPU 时把-no-opengl换成-opengl es2并保证libEGL、libGLESv2在 sysroot 里。
-qt-libpng -qt-zlib这类前缀是让 Qt 用自带的第三方源码而不是去 sysroot 里找,好处是零依赖风险,缺点是体积略大;如果你已经在 sysroot 装了 libpng,把-qt-libpng换成-system-libpng能让产物更小,但要确保版本一致。最后那几个-skip是把不需要的模块整个跳过,QtWebEngine 依赖 Chromium,交叉编译耗时巨大,除非业务需要,一律 skip。
| 参数 | 作用 | 嵌入式场景推荐值 |
|---|---|---|
-xplatform | 指定 mkspec | 自定义的 linux-aarch64-gnu-g++ |
-sysroot | 全局系统根目录 | 从板子 rsync 的目录 |
-no-xcb | 关闭 X11 后端 | 板上无 X 时必加 |
-linuxfb | framebuffer 平台插件 | 无 GPU 时的默认 |
-eglfs | 无窗口系统 GL 后端 | 走 KMS/GPU 时启用 |
-qt-freetype | 用内置 freetype | 不想折腾系统库时用 |
-opengl es2 | GL ES 2.0 | HDMI + GPU 时启用 |
4.3 make 阶段的报错定位方法
configure 通过不代表能编过去,Qt 5.12 全量编译在 8 核机器上大概 40 分钟到 1 小时,报错通常出现在qtbase/src/plugins/platforms或qtdeclarative。
make -j$(nproc) 2>&1 | tee build.log grep -n -E "error:|Error [0-9]+|No such file" build.log | head -n 30-j$(nproc)把核数用满,tee保证日志可回查。一旦停下,先 greperror:,最常见的是cannot find -lXXX:这表示链接器在-L路径里找不到某个.so,要么是依赖库没编进 sysroot,要么是库名版本不对(需要libfoo.so软链而不是libfoo.so.1)。第二常见的是头文件找不到,几乎都是忘了--sysroot,回头看 build.log 里那条命令是不是缺了这个参数。第三种是feature冲突,比如开了-eglfs但 sysroot 里没有libdrm,编译到一半才暴露——这类问题 configure 阶段日志里会有WARNING: Feature eglfs ...,回去翻configure.log比翻build.log快。
本文还有配套的精品资源,点击获取