先说结论ARM64 板子上跑 Qt5 这件事如果全靠板子自己编译尤其是带 OpenGL/WebEngine 的时候体验不是“慢”能形容的准确点说是在修仙。我自己做 RK3588 工控项目那阵子第一次在板子上跑./configure等了快一个钟头后面 WebEngine 直接内存爆掉当时整个人是麻的。后来老老实实换到 Ubuntu 22.04 主机上交叉编译把 QT5 for ARM64 这套流程彻底跑通包括最折磨人的 OpenGL ES 图形栈和 Chromium 内核的 WebEngine 模块整个项目才走上正轨。这篇内容不是官方文档翻译是我把踩过的坑、试错的过程、以及最终能稳定复现的一套方案完整记录下来。包含了工具链安装、Sysroot 搭建、OpenGL/EGL 图形栈选型、WebEngine 编译的资源和参数问题、完整的 configure 命令、部署到目标板的注意事项。适合两类人看一类是刚拿到 ARM64 开发板、准备在上面做 Qt 应用但不想在板子上硬编译的另一类是已经编译过但始终跑不起来图形加速或 WebEngine 的可以参考我这里的排查思路。1. 为什么必须做交叉编译以及整体技术路线怎么定1.1 直接在板子上编译为什么不建议很多人的第一反应是既然目标是 ARM64 的板子那直接在板子上装 Ubuntu 再装 Qt岂不省事这个思路在纯 x86 的台式机上没毛病但在嵌入式 ARM 设备上基本属于自虐。原因很直接主流的 ARM64 开发板比如 RK3588、树莓派 4B、飞腾系列CPU 性能虽然比十年前的 PC 强但和现在动辄十几个核心的 x86 工作站比起来还是有代差。你自己跑一下就知道在板子上编译 QtBase轻则四十分钟五十分钟重则一个半小时整块板卡在那边干不了别的。更致命的是 WebEngine。这个模块是 Chromium 内核编译起来需要的内存和磁盘远超一般板子的承受能力而且动不动需要十几个并行的编译任务来压时间。我试过在 8GB 内存的板子上硬编编到一半直接被 OOM Killer 干掉连错误日志都没来得及保存。所以对于嵌入式设备来说交叉编译不是可选项而是必选项在宿主机上利用多核并行编译QtBase 二十分钟内能搞定加上 WebEngine 也就多等一两个小时效率提升非常明显。1.2 核心方案Sysroot qmake 交叉链路交叉编译的核心思想很简单在 x86_64 的 Ubuntu 主机上用一个能生成 ARM64 机器码的编译器把目标设备的系统运行库和依赖库“模拟”出来。这里面最关键的两个概念是交叉工具链和 Sysroot。交叉工具链就是aarch64-linux-gnu-gcc、aarch64-linux-gnu-g这一组程序它们的任务是把 C/C 源码编译成 ARM64 指令集的二进制。但编译器只是工具编译出来的程序还要链接到目标平台上的 C 库、图形库、各种依赖库。这些库不能直接拿宿主机上的 x86_64 库来用必须有一个 ARM64 版本的根文件系统放在那里供编译器链接这个根文件系统就叫 Sysroot。在 Qt5 的交叉编译链路里qmake 负责衔接这一切。你需要在 configure 阶段告诉 Qt目标平台是 aarch64交叉编译器前缀是aarch64-linux-gnu-Sysroot 放在哪里最终安装到设备的哪个路径。qmake 会生成一套专门给交叉环境用的构建规则后续make的时候所有编译链接动作都会自动走交叉工具链。1.3 版本选型和影响范围分析版本选择是整个流程里最不该忽略的一环。Ubuntu 22.04 默认带的 GCC 是 11 或 12对 Qt 5.15 的支持比较友好所以我这里优先推荐qt-everywhere-src-5.15.10或 5.15.2。如果你翻出老项目用的 5.12.10在 GCC 12 上编译经常会出现 C 标准相关的报错处理起来很麻烦。如果你是给国产化环境做适配目标系统可能是银河麒麟 V10 ARM64底层一样是 aarch64交叉编译的流程并没有本质变化只是 Sysroot 必须从目标设备上拷贝而不能在线下载我在后面会讲到对应的离线方案。影响范围上这套方案几乎覆盖所有主流的 ARM64 Linux 场景工控触摸屏、医疗设备、边缘网关、车载终端、还有各种基于 RK3588/树莓派的交互项目。你只要把 Qt 库交叉编译好后续的应用程序都在宿主机上写、宿主机上编编完把产物往板子上一放改个环境变量就能跑整个开发体验会顺滑非常多。2. 环境准备工具链、Sysroot 一次搞定2.1 安装交叉编译工具链在 Ubuntu 22.04 上安装 ARM64 交叉工具链非常方便软件源里直接有现成的包。执行下面几条命令sudo apt update sudo apt install -y build-essential gcc-aarch64-linux-gnu g-aarch64-linux-gnu aarch64-linux-gnu-gcc --version装完之后可以用aarch64-linux-gnu-gcc的版本号确认一下。这个工具链是 11 或 12 的版本配合 Qt 5.15 没有问题。需要注意的是工具链里的ar、strip、objcopy等 binutils 组件也会跟着一起装好后续编译第三方库时会用到不需要单独安装。这里顺带提醒一下新手装完工具链不代表万事大吉aarch64-linux-gnu-gcc只是编译器本身它链接时找的头文件和库文件都在 Sysroot 里所以下一步的 Sysroot 搭建才是真正的重头戏很多人第一步就挂在这里报错信息往往是fatal error: stdio.h: No such file or directory原因就是 Sysroot 里根本没有 C 库头文件。2.2 搭建 Sysroot 的两种方式Sysroot 的搭建有两条路线我两种都试过各有适用范围。第一种是 debootstrap 在线构建适合目标设备用的也是 Ubuntu 系系统的情况。先安装依赖工具然后从 Ubuntu Ports 源拉取一套 ARM64 的基础根文件系统sudo apt install -y debootstrap qemu-user-static binfmt-support sudo debootstrap --archarm64 --variantminbase jammy /opt/arm64-rootfs http://ports.ubuntu.com/ubuntu-ports装完基础系统后再 chroot 进去补装 Qt 交叉编译需要的依赖库sudo chroot /opt/arm64-rootfs apt-get install -y \ libc6-dev libxcb1-dev libx11-dev libxkbcommon-dev \ libegl1-mesa-dev libgles2-mesa-dev libglib2.0-dev \ libfontconfig1-dev libfreetype6-dev libdbus-1-dev这套方式的优点是库之间的依赖关系由 apt 自动解决Sysroot 非常完整干净。缺点是需要联网下载几百 MB 的包如果宿主机网络环境不太行可能要等一阵。第二种方式是从目标开发板直接拷贝适合目标系统不是 Ubuntu 系的场景比如板子厂商提供的定制系统或者国产化离线环境。操作上就是把板子根目录下的/lib、/usr/lib、/usr/include整个拷贝到宿主机的一个目录里然后告诉编译器和链接器用这个目录作为根。这里有个容易漏的坑拷贝时要带上动态链接器和相关符号链接否则编译出来的程序在目标板上会报“无法找到动态链接器”之类的错误。就个人经验而言只要是能联网的场景我首选 debootstrap因为从板子上拷贝经常会遇到头文件不完整、软链接断裂的问题排查起来反而更费神。离线的国产化环境只能拷贝这时候我会用tar在板子上打包回宿主机解包尽量避免用scp按目录拷不然链接属性容易丢。2.3 环境变量与 pkg-config 的坑Sysroot 搭好之后千万不要急着直接 configure环境变量不设置对后面一定翻车。至少需要导出下面这些export SYSROOT/opt/arm64-rootfs export PKG_CONFIG_LIBDIR$SYSROOT/usr/lib/aarch64-linux-gnu/pkgconfig:$SYSROOT/usr/lib/pkgconfig:$SYSROOT/usr/share/pkgconfig export PKG_CONFIG_SYSROOT_DIR$SYSROOT export PKG_CONFIG_PATH第一行是自定义的 Sysroot 路径后面 configure 脚本会反复用到。后面两行是给 pkg-config 用的目的很明确让它在查找依赖库的.pc描述文件时只去 Sysroot 里面找而不是去宿主机/usr/lib/x86_64-linux-gnu/pkgconfig里找。PKG_CONFIG_PATH要置空是为了屏蔽宿主机的 pkgconfig 搜索路径避免 x86_64 的库信息串进来。这一步的坑特别隐蔽。如果你不设置这三个变量Qt 的 configure 在检测 xcb、EGL 这些模块时会因为找不到 ARM64 版本的库而自动禁用相关功能。最坑的是它不会报错而是默默地把-no-xcb、-no-eglfs加到配置里等你编译完发现平台插件缺失又要从头再来一遍。3. 最难啃的图形栈OpenGL/EGL 往哪找3.1 ARM64 上的 OpenGL 现实ARM64 设备上的 OpenGL 形态和 x86 桌面完全不同。绝大多数 ARM SoC包括 RK3588 的 Mali GPU、树莓派的 VideoCore以及高通的 Adreno在 Linux 下支持的都是 OpenGL ES 接口而不是桌面 x86 那种完整的 OpenGL。所以你交叉编译 Qt5 时碰到图形栈的第一个认知要转变过来别再执着于桌面 OpenGL老老实实走 OpenGL ES 2.0/3.0 路线。这个差异会直接影响 configure 参数。Qt5 里对应的是-opengl es2它告诉 qmake 去链接libGLESv2.so和libEGL.so而不是libGL.so。很多教程在这里含糊其辞用默认配置一通操作结果出来的是桌面 OpenGL 版本放到 ARM 板子上怎么可能跑得起来。3.2 三种 OpenGL 后端怎么选先明确你需要的最终呈现方式再反推后端选择这是我吃了几次教训之后的经验。第一种是 EGLFS GPU 硬件加速。Qt 直接绕过 X11通过 EGL 接口对接 GPU 驱动在 framebuffer 或者 DRM 上输出画面。这种方式性能最好适合工控屏、设备 HMI也是 ARM 嵌入式 Qt 最常用的方式。代价是你的 Sysroot 里必须有与目标 GPU 匹配的 EGL/GLES 开发库比如 Mali 的libmali或者 Mesa 的开源驱动。第二种是 Mesa 软件渲染。Sysroot 里装好libegl1-mesa-dev和libgles2-mesa-devQt 会以软件方式模拟 EGL/GLES2好处是兼容性极强几乎任何 ARM 板都能跑坏处是性能损耗明显复杂的界面会掉帧。我通常把它当兜底方案调试阶段验证程序逻辑时用。第三种是 LinuxFB就是最原始的 framebuffer 绘图不走任何图形加速接口。这种方式 Qt5 还在支持但你要清楚它的能力非常有限很多特效、半透明、复杂布局都会出问题。对比下来实际项目里最稳的组合是先用 Mesa 软渲染把整套 Qt 编译和运行链路跑通再根据目标板 GPU 驱动替换成 EGLFS 硬件加速。一步到位反而容易在 GPU 驱动和 Qt 之间反复扯皮。3.3 configure 阶段怎么配合图形栈如果你确定要 EGLFS 和 LinuxFBconfigure 里至少要加入这几个参数-opengl es2 -eglfs -linuxfb-opengl es2控制 Qt 使用 GLES2 接口-eglfs让 Qt 编译 eglfs 平台插件-linuxfb编译 framebuffer 平台插件。如果你不需要 X11 桌面环境下的 Qt 窗口可以在 configure 里显式加-no-xcb这样能省去编译一大坨 xcb 相关依赖交叉编译的负担会小很多。编译完检查产物的方式也很简单看qtbase/plugins/platforms/目录下的.so文件。只要libqeglfs.so或者liblinuxfb.so在就说明平台插件编译成功了。如果这两个文件缺失通常就是 configure 阶段检测 EGL/GLES 库失败回去检查 Sysroot 里的libegl1-mesa-dev是否装好或者 pkg-config 环境变量是否指对路径。4. 玄学重灾区WebEngine 模块如何编译4.1 先想清楚要不要 WebEngineWebEngine 是 Qt5 里最重的一个模块它本质上是把 Chromium 浏览器内核打包成 Qt 原生组件。如果你做的应用只是普通工控界面完全没有嵌入网页、调用 JavaScript 的需求那么我强烈建议你直接跳过它。configure 命令里加一行-skip qtwebengine就能省下几百分钟的时间和几十 GB 磁盘空间这笔账怎么算都划得来。但如果你确实需要 WebEngine比如做信息发布屏、Web 混合开发框架那就要做好心理准备这个东西的编译复杂度和资源需求远高于 QtBase。我第一次编的时候没看任何说明直接默认全量构建系统到一半开始疯狂 swap最后报错退出的画面至今难忘。4.2 编译 WebEngine 的系统要求与依赖我实测下来交叉编译 Qt WebEngine宿主机至少需要满足这些条件内存 16GB 以上这个是真底线8GB 内存几乎必然 OOM磁盘空闲空间至少 30GB因为 Chromium 的源码和中间产物非常占空间CPU 核心越多越好编译是强并行任务建议make -j$(nproc)跑满。另外记得把文件描述符上限调大编译过程中会有大量文件操作ulimit -n 4096Sysroot 里还需要一堆额外的依赖库主要包括 NSS、ALSA、PulseAudio、X11 扩展库等。在 chroot 环境下安装的命令大致是sudo chroot /opt/arm64-rootfs apt-get install -y \ libnss3-dev libssl-dev libasound2-dev libpulse-dev \ libxcomposite-dev libxrandr-dev libxss-dev另外要注意WebEngine 在 configure 阶段会自动下载一堆它自己依赖的组件源码比如 GN、Ninja、FFmpeg 等这个过程对网络环境比较挑剔。如果你的主机下载这些资源不稳定编译通常会死在半路上的某个 “Downloading …” 步骤这时候要么配置国内镜像要么找一台网络稳定的机器来跑。4.3 WebEngine 常见失败信号速查根据我自己的记录和身边朋友的经验WebEngine 编译失败有几类典型信号整理成一张速查表。报错特征原因处理思路No rule to make target ...nss3...Sysroot 缺少 NSS 开发头文件在 Sysroot 里安装libnss3-devpython: command not foundQt 5.15 需要 Python 3 环境但目录识别异常确认宿主机的 Python3 在PATH中gn: No such file or directoryQt 自动下载 GN 工具失败检查网络把 GN 工具链手工放进 WebEngine 工具目录编译中cc1plus: out of memory宿主机内存不足加内存或者降低并行数make -j1会非常慢GLIBCXX_3.4.x not foundSysroot 或目标板的 libstdc 版本过旧更新 Sysroot 的 libstdc或同步目标板系统库看到cc1plus: out of memory的时候不要硬扛先停掉make关掉其他吃内存的程序再重新编译。编译过程有断点续跑的能力再次make会从断的地方继续不会全部重来。4.4 时间预算与合理策略如果你决定上 WebEngine这里给你一个比较现实的时间预期在 16 核 32GB 内存的宿主机上QtBase 全量编译大约 15 到 25 分钟WebEngine 模块单独编译通常在 1.5 到 3 小时之间视网络下载速度和磁盘性能有浮动。这比很多教程里说的“几分钟”要真实得多。我的建议是先跑一个干净的、不带 WebEngine 的 Qt 完整流程把应用程序跑通、部署跑顺然后再在现有基础上重新 configure 加入 WebEngine。这样即使 WebEngine 编失败了你手上的 Qt 环境还能继续用不会影响主项目进度。5. 完整 configure 与 make 实操记录5.1 下载解压 Qt5 源码我这边用的是qt-everywhere-opensource-src-5.15.10.tar.xz可以到 Qt 官网的 archive 目录下载如果官网慢很多镜像站也有同步。下载完解压wget https://download.qt.io/archive/qt/5.15/5.15.10/qt-everywhere-opensource-src-5.15.10.tar.xz tar xf qt-everywhere-opensource-src-5.15.10.tar.xz cd qt-everywhere-opensource-src-5.15.10解压后你会看到一个能完整构建 Qt 所有模块的源码树里面qtbase、qtdeclarative、qtwebengine等子仓库都在。Workspace 目录结构清晰很适合直接构建。这里特别注意一点不要在 Windows 上解压以后传到 Linux 里换行符和权限一堆问题直接在 Linux 下解压最省心。5.2 一份能跑的 configure 命令拆解这是我最常用的一套 configure 参数组合不含 WebEngine适合先把常规 Qt 跑通./configure -release -opensource -confirm-license \ -xplatform linux-aarch64-gnu-g \ -sysroot $SYSROOT \ -prefix /usr/local/qt5-arm64 \ -extprefix $HOME/qt5-arm64-install \ -nomake examples -nomake tests \ -opengl es2 \ -eglfs -linuxfb \ -skip qtwebengine逐段解释关键参数的含义。-xplatform linux-aarch64-gnu-g让 qmake 使用 Qt 自带的这个 mkspec它会自动把编译器前缀拼成aarch64-linux-gnu-来调用。-sysroot $SYSROOT告诉 configure 链接时使用我们搭建的 ARM64 根文件系统。-prefix /usr/local/qt5-arm64这是最终安装到目标设备上的路径。交叉编译和本地编译不同Qt 配置里有两个路径概念-prefix指的是未来在目标板上运行时库的位置-extprefix指的是当前宿主机上实际安装产物的路径。我这里是安装到$HOME/qt5-arm64-install等发布到板上再拷到/usr/local/qt5-arm64。-opengl es2配合-eglfs -linuxfb走 OpenGL ES 2.0 路线并启用两个嵌入式平台插件。-skip qtwebengine暂时跳过这个大块头。如果你的机器上$SYSROOT环境变量没有设执行 configure 前先export SYSROOT/opt/arm64-rootfs把它补上。configure 执行完留意最后输出的配置总结确认OpenGL: es2、Xcb: no这些关键项符合预期再进行下一步。5.3 分模块编译与安装源码树全量构建时直接执行make -j$(nproc) make install但这里有个更稳的分步策略先只构建和安装qtbase因为这个模块包含了 QtCore、QtGui、QtWidgets 等最核心的库和平台插件是其他模块的基础。cd qtbase make -j$(nproc) make install cd ..qtbase 编译安装成功之后再决定要不要继续编其他模块。如果你需要 QML、Quick那就进qtdeclarative编译安装需要串口、网络等扩展模块可以使用-skip和-module相关参数按需选择模块范围。这种渐进式做法的好处是问题范围被限制在一个模块内排查起来不累。5.4 定制 mkspec 的进阶技巧Qt 自带的linux-aarch64-gnu-gmkspec 在绝大多数情况下够用但如果你遇到 EGL/GLES 头文件路径不对或者需要加入额外的编译宏自行定制一个 mkspec 会更方便。做法是把自己需要的 mkspec 复制一份改一个项目相关名字cp -a qtbase/mkspecs/linux-aarch64-gnu-g qtbase/mkspecs/linux-myarm-g然后编辑qmake.conf在里面加上 EGL 相关的头文件和库路径CROSS_COMPILE aarch64-linux-gnu- QMAKE_INCDIR_EGL $$[QT_SYSROOT]/usr/include QMAKE_LIBDIR_EGL $$[QT_SYSROOT]/usr/lib/aarch64-linux-gnuconfigure 时把-xplatform指向你新创建的linux-myarm-g。这种做法的好处是即使以后 Sysroot 路径变了或者目标板的 GPU 库换了位置只需要改这一个文件不需要重新梳理整个 configure 参数。5.5 验证交叉编译产物编译和安装完成之后一定要做一次基础验证用file命令检查产物是否为 ARM64 架构file $HOME/qt5-arm64-install/lib/libQt5Core.so.5.15.10正常情况下输出会显示ELF 64-bit LSB shared object, ARM aarch64。如果显示的是x86-64那说明某个环节没有启用交叉编译通常是-xplatform参数没生效或者环境变量里混入了宿主机路径。6. 部署到目标板别忘了这些坑6.1 库与环境变量的准备交叉编译出来的 Qt 库最终是要拷贝到目标开发板上去的。把$HOME/qt5-arm64-install整体拷到板子上放到你配置的-prefix路径下比如/usr/local/qt5-arm64。这一步不复杂但要注意权限和软链接最好用tar打包传输避免 scp 按目录拷贝时软链接断掉。在板子上运行 Qt 程序之前先设置环境变量export QTDIR/usr/local/qt5-arm64 export LD_LIBRARY_PATH$QTDIR/lib:$LD_LIBRARY_PATH export QT_QPA_PLATFORMeglfsQT_QPA_PLATFORM设置成eglfs是告诉 Qt 用 EGLFS 平台后端输出画面。如果你的板子有 X11 环境可以设成xcb如果没有已经编译 xcb 插件设了也没用程序会报平台插件无法加载。6.2 运行时常见错误速查部署阶段最让人抓狂的是运行时各种.so找不到我把高频问题整理成了表格方便你对着查。现象原因处理error while loading shared libraries: libQt5Core.so.5系统找不到 Qt 库检查LD_LIBRARY_PATH是否设置正确could not find or load the Qt platform plugin eglfs平台插件缺失或与库不匹配确认qtbase/plugins/platforms/已完整拷贝libGLESv2.so.2: cannot open shared object file目标板缺少 GLES 软渲染库在板子上安装 Mesa 的 GLES 库画面黑屏但有窗口GPU 初始化失败先后退到软渲染模式验证再排查 GPU 驱动应用无法拖拽文件xcb 相关扩展库没编译进去在 Sysroot 里安装libxcb-icccm4-dev等扩展包后重新编译 xcb 插件或者改用 eglfs黑屏那个问题值得单独说一句如果你用的是 Mali GPU并且自己的板卡内核里 GPU 驱动没加载好EGLFS 强行初始化 display 就是黑屏。这时候把QT_QPA_PLATFORM改成linuxfb跑一次如果画面能出说明 Qt 这边没问题问题出在 GPU 驱动层级去检查驱动加载状态和内核配置。6.3 交叉编译自己的 Qt 应用Qt 库部署好了接下来就是日常开发场景在宿主机上编译自己的 Qt 程序然后扔到板子上跑。这里有两种方式。如果你习惯用 CMake那么一个最小化的CMakeLists.txt可以这样写set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc) set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g) set(CMAKE_PREFIX_PATH /home/user/qt5-arm64-install) find_package(Qt5 COMPONENTS Widgets REQUIRED) add_executable(myapp main.cpp) target_link_libraries(myapp Qt5::Widgets)如果你习惯用 qmakeQt 交叉编译树里自带的$HOME/qt5-arm64-install/bin/qmake本身就是针对 ARM64 平台生成的用这个 qmake 去处理你的.pro文件生成的 Makefile 里所有编译指令会自动走交叉工具链$HOME/qt5-arm64-install/bin/qmake your_project.pro make编译出来的可执行文件在宿主机上是跑不了的直接用file检查一下架构确认是 ARM64 后丢到板子上跑即可。这里常见的误操作是顺手用了系统里自带的qmake那个是 x86_64 版本编出来的东西在板子上肯定跑不了注意路径别搞混。6.4 离线环境的部署方案如果你的目标环境是离线网络比如某些内网生产机器或者国产化环境Sysroot 没法用 debootstrap 在线构建整条流程需要做一些调整。我做过一次这种部署关键点在于在一台能连网的 Ubuntu 机器上用 debootstrap 构建一套和离线环境相同的 ARM64 rootfs然后把所有依赖包用apt-get download方式下载下来打包带到离线环境再从.deb文件安装到 Sysroot 里。Qt 编译产物整体打好 tar 包带到目标板部署即可。比这更原始但有效的方法就是把目标系统的整个/usr和/lib用 tar 打包拿到宿主机做 Sysroot。这种方式能保证 Sysroot 和运行时环境严格一致编译出来的 Qt 在板子上几乎不会出现 GLIBC 版本不匹配的问题。缺点是打包体积大一个最小的根文件系统也是几百 MB 起步但做离线项目完全值得。我个人在实际操作中的体会是这套流程最花时间的不是编译本身而是图形栈和 WebEngine 这些环节的取舍。前期多花十分钟想清楚目标设备到底需要哪些模块后面就能省下数小时的无效调试。先用 Mesa 软渲染跑通流程再切 GPU 硬件加速先编译没有 WebEngine 的版本确认业务逻辑没问题后再考虑集成。按照这条路走下来Ubuntu 22.04 上交叉编译 QT5 for ARM64 这件事基本不会再有让你整晚睡不着觉的坑。