Qt5.14.2-aarch64静态交叉编译-从零搭建完整手册
Qt 5.14.2 aarch64 静态交叉编译 · 从零搭建完整手册
本文档自包含:在一台全新的麒麟 V10 x86_64 服务器上,从零跟着做,
最终得到一个 Qt 静态交叉编译环境,编出的程序是单文件,拷到 ARM
目标机(麒麟/UOS 桌面版)直接运行,目标机不装任何东西、不改任何配置。
所有脚本全文内嵌,无需从别处获取。
Qt5.14.2源码分享:
链接:https://pan.quark.cn/s/ce2fffc14cdf
提取码:6rZE
0. 开始之前:先看懂我们要做什么
0.1 名词解释(新手必读,3 分钟)
| 名词 | 通俗解释 |
|---|---|
| 交叉编译 | 在 A 架构电脑(x86_64)上,编出给 B 架构电脑(ARM/aarch64)用的程序。因为目标机性能弱或不方便装编译器,所以在强机器上编 |
| 工具链 | 交叉版 gcc/g++/ld 等一套工具,专门生成 ARM 代码(本方案用 Bootlin 预编译工具链,gcc 7.3.0) |
| sysroot | “目标机的替身目录”。交叉编译器找头文件、找库都往这里找,相当于把 ARM 目标机的 /usr 搬了一份到本机 |
| 静态链接 | 把 Qt 库直接"焊"进程序里。好处:程序是单文件,目标机不需要装 Qt;代价:每个程序约 17~31MB |
| 动态依赖(NEEDED) | 程序运行时还需要目标机自带的系统库(如 libX11、libGL)。这些是所有桌面 Linux 必带的,所以不用管 |
0.2 成果预览(跟着做完你会得到什么)
- 安装目录
/opt/qt5.14.2-aarch64-static-xcb:以后所有 ARM Qt 项目都用它编译 - 编出的程序:单文件、19 个动态依赖全部是 ARM 桌面必带系统库、直接
./xxx运行 - 包含模块:Widgets、Quick/QML、Charts、SVG、Network、SerialPort、Multimedia、VirtualKeyboard(虚拟键盘) 等;没有 dbus 和 webengine(裁剪原因见 7.1)
- 耗时:全程约 2~4 小时(其中 Qt 编译占 40~90 分钟,可挂机)
0.3 环境要求
| 项目 | 要求 | 检查命令 |
|---|---|---|
| 交叉编译主机 | 麒麟 V10 x86_64(其他 yum 系 x86_64 Linux 亦可) | uname -m 输出 x86_64 |
| 权限 | root | whoami |
| 磁盘 | /opt 所在分区剩余 ≥ 30G | df -h /opt |
| 内存 | ≥ 4G(6.5G 实测够用;OOM 处理见 7.3) | free -h |
| 网络 | 能访问 toolchains.bootlin.com、download.qt.io 和清华镜像(openEuler 源) | 见步骤 1/2/3/4 |
# 确认系统与工具
cat /etc/os-release | head -2
uname -m
ldd --version | head -1
df -h / | tail -1
free -h | head -2
# 预期结果:
# PRETTY_NAME="Kylin Linux Advanced Server V10 (Halberd)"
# x86_64
# ldd (GNU libc) 2.28
# /dev/mapper/klas-root 62G 8.5G 53G 14% / ← 可用空间 > 30G
# Mem: 6.5Gi ...
离线应对:工具链和 Qt 源码都可以先在别的机器下载再用 U 盘/scp 拷进来;
sysroot 的 rpm 包可用目标系统的 aarch64 安装 ISO 挂载代替(步骤 4 有说明)。
0.4 流程总览(一共 8 个步骤)
步骤1 装 Bootlin 交叉工具链(含别名 + 环境变量)
步骤2 下载解压 Qt 5.14.2 源码
步骤3 扩充 sysroot(下载 openEuler aarch64 开发包合入)→ 7 组冒烟全 OK
步骤4 治本清理 .pc 文件(防止后面一个必踩的编译坑)
步骤5 configure + make + install(Qt 主体编译,核心步骤)
步骤6 demo 编译验收(readelf 依赖白名单检查)
步骤7 目标机部署运行(零配置,单文件直接跑)
步骤8 日常使用(以后每来一个项目怎么编:qmake 版 / CMake 版)
步骤 1:安装 Bootlin 交叉工具链
做什么:装一套"能在 x86 机器上生成 ARM 代码"的 gcc/g++。
为什么用它:官方预编译、自带 sysroot(glibc 2.27)、2026 年实测可下载
(注意:Linaro 的旧下载源已下线,网上老教程的 Linaro 链接会下到 HTML 错误页)。
mkdir -p /opt/qtwork && cd /opt/qtwork
# 1.1 安装主机构建依赖(编译 qmake/moc 等主机工具用)
yum install -y gcc gcc-c++ make perl python3 bison flex pkg-config \
wget tar xz bzip2 rsync binutils which file cpio
# 说明:rpm2cpio 由 rpm 包自带,"yum install rpm2cpio"会报"没有任何匹配",不用装
# 1.2 下载工具链(约 70MB)
wget https://toolchains.bootlin.com/downloads/releases/toolchains/aarch64/tarballs/aarch64--glibc--stable-2018.11-1.tar.bz2
# 1.3 验证下载(防下到 HTML 错误页)
file aarch64--glibc--stable-2018.11-1.tar.bz2
# 预期含 "bzip2 compressed data";若是 HTML document,删掉重下或换网络
# 1.4 解压安装
tar -xjf aarch64--glibc--stable-2018.11-1.tar.bz2
mv aarch64--glibc--stable-2018.11-1 /opt/bootlin-aarch64-glibc-2018.11
# 1.5 建立 aarch64-linux-gnu-* 别名(必须做!)
# 原因:Qt 的交叉 mkspec 叫 linux-aarch64-gnu-g++,找的编译器名是
# aarch64-linux-gnu-g++,而 Bootlin 的原名是 aarch64-buildroot-linux-gnu-g++。
# 且 Bootlin 的编译器是"脚本包装器",按被调用的名字定位真身(gcc.br_real),
# 所以必须对【所有】aarch64-buildroot-linux-gnu-* 逐一建别名:
cd /opt/bootlin-aarch64-glibc-2018.11/bin
rm -f aarch64-linux-gnu-*
for f in aarch64-buildroot-linux-gnu-*; do
ln -sf "$f" "aarch64-linux-gnu-${f#aarch64-buildroot-linux-gnu-}"
done
# 1.6 写环境变量(以后每次登录自动生效)
cat > /etc/profile.d/cross-aarch64.sh <<'EOF'
export PATH=/opt/bootlin-aarch64-glibc-2018.11/bin:/opt/qt5.14.2-aarch64-static-xcb/bin:$PATH
EOF
source /etc/profile.d/cross-aarch64.sh
验证(三条全过才算成功):
aarch64-linux-gnu-gcc --version | head -1
# 预期: aarch64-linux-gnu-gcc.br_real (Buildroot ...) 7.3.0
echo 'int main(){return 0;}' > /tmp/t.c
aarch64-linux-gnu-gcc /tmp/t.c -o /tmp/t && file /tmp/t | grep -q "ARM aarch64" && echo "OK: 交叉编译通过"
aarch64-linux-gnu-gcc -print-sysroot
# 预期: /opt/bootlin-aarch64-glibc-2018.11/aarch64-buildroot-linux-gnu/sysroot
常见失败:
| 现象 | 处理 |
|---|---|
gcc.br_real: No such file or directory | 1.5 别名没做全,重跑那个 for 循环 |
| 下载到 HTML | 换网络重下;或别的机器下载后拷入 /opt/qtwork/ 从 1.3 继续 |
| wget 太慢 | wget 换 curl -fLO,或用浏览器/下载工具 |
步骤 2:下载 Qt 5.14.2 源码
cd /opt/qtwork
wget https://download.qt.io/archive/qt/5.14/5.14.2/single/qt-everywhere-src-5.14.2.tar.xz
# 校验完整性(必须一致再继续)
md5sum qt-everywhere-src-5.14.2.tar.xz
# 预期输出:b3d2b6d00e6ca8a8ede6d1c9bdc74daf qt-everywhere-src-5.14.2.tar.xz
tar -xJf qt-everywhere-src-5.14.2.tar.xz
ls qt-everywhere-src-5.14.2/configure
# 预期输出:qt-everywhere-src-5.14.2/configure
步骤 3:扩充 sysroot(下载 ARM 系统库并合入)
做什么:Qt 需要目标机架构(aarch64)的 X11、OpenSSL、字体、OpenGL 等
开发库才能编译。我们从 openEuler 20.03 LTS 的 aarch64 仓库下载 rpm 包
(只解包提取文件,不在本机安装),合入工具链的 sysroot。
为什么用 openEuler:与麒麟/UOS 同属国内系(库按 glibc 2.28 构建,和
目标机一致)、rpm 格式干净、清华镜像国内快。
3.1 创建脚本
cat > /opt/qtwork/06-sysroot-full.sh <<'SCRIPT_EOF'
#!/bin/bash
# 06-sysroot-full.sh —— 下载 aarch64 开发包合入 Bootlin 工具链 sysroot
# 注意:会把 sysroot 的 glibc 从 2.27 升级为 openEuler 的 2.28(必须,
# openEuler 的库按 2.28 构建,2.27 链接不过;产物基线即 glibc>=2.28)
set -e
if [ -z "${BASE}" ]; then
for c in \
"https://mirrors.tuna.tsinghua.edu.cn/openeuler/openEuler-20.03-LTS/everything/aarch64/Packages" \
"https://dl-cdn.openeuler.openatom.cn/openEuler-20.03-LTS/everything/aarch64/Packages" \
"https://repo.openeuler.org/openEuler-20.03-LTS/everything/aarch64/Packages"; do
if curl -sfL --max-time 20 "${c}/" -o /dev/null; then BASE="${c}"; break; fi
done
fi
[ -n "${BASE}" ] || { echo "FAIL: 所有候选源均不可达(离线?请用 ISO 方式)"; exit 1; }
echo " 使用源: ${BASE}"
RPMDIR=/opt/qtwork/sysroot-rpms
STAGING=/opt/qtwork/sysroot-staging
# ---- 分组定义(包名已按 openEuler 20.03 实际仓库核对)----
# glibc 2.28:openEuler 库按 2.28 构建,sysroot 原生 2.27 缺 fcntl64@GLIBC_2.28
G_LIBC="glibc glibc-devel"
G_X11="libxcb libxcb-devel libXau libXau-devel libXdmcp libXdmcp-devel \
xorg-x11-proto-devel \
libX11 libX11-devel libXext libXext-devel libXrender libXrender-devel \
libXfixes libXfixes-devel libXdamage libXdamage-devel libXi libXi-devel \
libXcursor libXcursor-devel libICE libICE-devel libSM libSM-devel \
xcb-util xcb-util-devel xcb-util-image xcb-util-image-devel \
xcb-util-keysyms xcb-util-keysyms-devel xcb-util-wm xcb-util-wm-devel \
xcb-util-renderutil xcb-util-renderutil-devel \
libxkbcommon libxkbcommon-devel libxkbcommon-x11 libxkbcommon-x11-devel"
# 注:X11/X.h 等协议头在 xorg-x11-proto-devel(noarch 包);
# libxcb 依赖 libXau+libXdmcp;libX11-xcb.so 在 libX11 包内
G_FONT="fontconfig fontconfig-devel freetype freetype-devel expat expat-devel \
libpng bzip2 bzip2-devel harfbuzz graphite2 zlib zlib-devel"
# 关键:openssl 主包只有命令行工具,openssl-devel 只有头文件+.a,
# 真正的动态库 libcrypto.so.1.1.1d / libssl.so.1.1.1d 在 openssl-libs 包内!
G_SSL="openssl-libs openssl openssl-devel"
G_ALSA="alsa-lib alsa-lib-devel"
G_GL="libglvnd libglvnd-devel \
mesa-libGL mesa-libGL-devel mesa-libEGL mesa-libEGL-devel \
mesa-libgbm mesa-libgbm-devel libdrm libdrm-devel"
G_DBUS="dbus-libs dbus dbus-devel systemd-libs libcap libgcrypt libgpg-error \
lz4 xz-libs zstd libuuid"
G_GLIB="glib2 glib2-devel libffi libffi-devel pcre pcre-devel \
libuuid libmount libselinux libsepol"
PKGS="${G_LIBC} ${G_X11} ${G_FONT} ${G_SSL} ${G_ALSA} ${G_GL} ${G_DBUS} ${G_GLIB}"
echo ">>> [1/6] 安装解包工具..."
command -v rpm2cpio >/dev/null || yum install -y rpm-build
command -v cpio >/dev/null || yum install -y cpio
command -v rsync >/dev/null || yum install -y rsync
echo ">>> [2/6] 解析仓库索引并下载 rpm..."
mkdir -p ${RPMDIR} && cd ${RPMDIR}
INDEX=$(mktemp)
curl -sfL --max-time 120 "${BASE}/" -o ${INDEX}
[ -s ${INDEX} ] || { echo "FAIL: 索引下载失败"; exit 1; }
MISSING=""
for p in ${PKGS}; do
# 版本号允许含字母(如 openssl-1.1.1d);aarch64 优先,noarch 兜底
rpm=$(grep -oE "\"${p}-[0-9][0-9a-zA-Z.]*-[^\"]*\.aarch64\.rpm\"" ${INDEX} \
| tr -d '"' | sort -V | tail -1)
[ -z "${rpm}" ] && rpm=$(grep -oE "\"${p}-[0-9][0-9a-zA-Z.]*-[^\"]*\.noarch\.rpm\"" ${INDEX} \
| tr -d '"' | sort -V | tail -1)
if [ -z "${rpm}" ]; then
echo " [警告] 未找到 ${p}"
MISSING="${MISSING} ${p}"
continue
fi
if [ ! -f "${rpm}" ]; then
echo " 下载: ${rpm}"
curl -sfL --max-time 300 -O "${BASE}/${rpm}" || echo " [警告] 下载失败: ${rpm}"
fi
done
rm -f ${INDEX}
NRPM=$(ls *.rpm 2>/dev/null | wc -l)
echo " 共下载 rpm: ${NRPM} 个"
[ "${NRPM}" -gt 10 ] || { echo "FAIL: 下载数量异常,检查网络或更换 BASE 源"; exit 1; }
echo ">>> [3/6] rpm2cpio 解包到暂存目录..."
rm -rf ${STAGING} && mkdir -p ${STAGING}
cd ${STAGING}
for r in ${RPMDIR}/*.rpm; do
rpm2cpio "${r}" | cpio -idm --quiet 2>/dev/null || echo " [警告] 解包失败: $(basename ${r})"
done
echo ">>> [4/6] 合入 Bootlin sysroot..."
source /etc/profile.d/cross-aarch64.sh
SYSROOT=$(aarch64-linux-gnu-gcc -print-sysroot)
echo " sysroot: ${SYSROOT}"
[ -d ${STAGING}/usr/include ] && rsync -a ${STAGING}/usr/include/ ${SYSROOT}/usr/include/
[ -d ${STAGING}/usr/lib64 ] && rsync -a ${STAGING}/usr/lib64/ ${SYSROOT}/usr/lib/
[ -d ${STAGING}/usr/lib ] && rsync -a ${STAGING}/usr/lib/ ${SYSROOT}/usr/lib/
[ -d ${STAGING}/usr/share ] && rsync -a ${STAGING}/usr/share/ ${SYSROOT}/usr/share/
# glibc 的 libc.so.6/ld-linux 等在 /lib64(openEuler usrmerge 下 rpm 里仍是 /lib64 路径)
[ -d ${STAGING}/lib64 ] && rsync -a ${STAGING}/lib64/ ${SYSROOT}/lib/
[ -d ${STAGING}/lib ] && rsync -a ${STAGING}/lib/ ${SYSROOT}/lib/
# openEuler 的 .so 软链在 sysroot 内大量断链(绝对 /usr/lib64/*、/lib64/*、
# 以及 ../../lib64/* 相对形态)。通用修复:扫描所有断链,按 basename
# 在 sysroot 各库目录中查找真身,用完整路径重链。
# 注意:Bootlin sysroot 的 usr/lib、lib 可能是指向 lib64 的软链,
# find 必须加 -L 才会进入真实目录。
for d in ${SYSROOT}/usr/lib ${SYSROOT}/usr/lib64 ${SYSROOT}/lib ${SYSROOT}/lib64; do
[ -d "${d}" ] || continue
find -L "${d}" -maxdepth 1 -type l 2>/dev/null | while read -r l; do
[ -e "${l}" ] && continue # 只处理断链
rel=$(basename "$(readlink "${l}")")
[ -n "${rel}" ] || continue
for cand in "${d}/${rel}" \
${SYSROOT}/usr/lib/${rel} ${SYSROOT}/usr/lib64/${rel} \
${SYSROOT}/lib/${rel} ${SYSROOT}/lib64/${rel}; do
if [ -e "${cand}" ]; then
ln -sf "${cand}" "${l}"
break
fi
done
done
done
# 强制修复 openssl:显式全 sysroot 找真身,在 usr/lib 与 usr/lib64 都放绝对路径软链
for lib in libcrypto libssl; do
real=$(find ${SYSROOT} -maxdepth 4 -type f -name "${lib}.so.1.1*" 2>/dev/null | head -1)
[ -n "${real}" ] || { echo " [警告] 全 sysroot 找不到 ${lib} 真身"; continue; }
echo " ${lib} 真身: ${real}"
for d in ${SYSROOT}/usr/lib64 ${SYSROOT}/usr/lib; do
[ -d "${d}" ] || mkdir -p "${d}"
ln -sf "${real}" "${d}/${lib}.so"
ln -sf "${real}" "${d}/${lib}.so.1.1"
done
done
rm -f ${SYSROOT}/usr/lib64/libcrypto.a ${SYSROOT}/usr/lib64/libssl.a
for so in libcrypto.so libssl.so libfontconfig.so libdbus-1.so libglib-2.0.so; do
for d in ${SYSROOT}/usr/lib64 ${SYSROOT}/usr/lib; do
if [ -L "${d}/${so}" ]; then
if [ -e "${d}/${so}" ]; then echo " 软链 OK: ${d}/${so}"
else echo " [警告] 软链仍断: ${d}/${so} -> $(readlink ${d}/${so})"; fi
break
fi
done
done
echo " sysroot glibc: $(strings ${SYSROOT}/lib/libc.so.6 2>/dev/null | grep 'GNU C Library' | head -1)"
echo ">>> [5/6] 分组冒烟验证(编译+链接)..."
check() {
local name=$1 src=$2; shift 2
echo "${src}" > /tmp/ck.cpp
if aarch64-linux-gnu-g++ --sysroot=${SYSROOT} /tmp/ck.cpp -o /tmp/ck "$@" 2>/tmp/ck.err; then
echo " [OK] ${name}"
else
echo " [失败] ${name}"
sed 's/^/ /' /tmp/ck.err | head -6
fi
}
check "X11/xcb 组" '#include <xcb/xcb.h>
#include <X11/Xlib.h>
#include <xkbcommon/xkbcommon-x11.h>
int main(){return 0;}' -lxcb -lX11 -lxkbcommon-x11
check "fontconfig 组" '#include <fontconfig/fontconfig.h>
int main(){FcInit();return 0;}' -lfontconfig
SSLFLAGS=$(PKG_CONFIG_SYSROOT_DIR=${SYSROOT} \
PKG_CONFIG_LIBDIR=${SYSROOT}/usr/lib/pkgconfig:${SYSROOT}/usr/share/pkgconfig \
pkg-config --cflags --libs openssl 2>/dev/null)
check "OpenSSL 组" '#include <openssl/ssl.h>
int main(){SSL_free(SSL_new(0));return 0;}' ${SSLFLAGS:--lssl -lcrypto -lz -ldl -lpthread}
check "ALSA 组" '#include <alsa/asoundlib.h>
int main(){return 0;}' -lasound
check "OpenGL 组" '#include <GL/gl.h>
int main(){return 0;}' -lGL
# 注意:-I 路径不会被 --sysroot 自动加前缀,必须显式写 ${SYSROOT}
check "DBus 组" '#include <dbus/dbus.h>
int main(){return 0;}' -I${SYSROOT}/usr/include/dbus-1.0 -I${SYSROOT}/usr/lib/dbus-1.0/include -ldbus-1
check "GLib 组" '#include <glib.h>
int main(){return 0;}' -I${SYSROOT}/usr/include/glib-2.0 -I${SYSROOT}/usr/lib/glib-2.0/include -lglib-2.0
echo ">>> [6/6] 结果汇总"
echo " 未找到的包:${MISSING:-无}"
SCRIPT_EOF
3.2 执行
cd /opt/qtwork && bash 06-sysroot-full.sh
预期结果:7 个组全部 [OK],末尾输出 sysroot glibc: ... 2.28:
[OK] X11/xcb 组
[OK] fontconfig 组
[OK] OpenSSL 组
[OK] ALSA 组
[OK] OpenGL 组
[OK] DBus 组
[OK] GLib 组
失败处理:
| 现象 | 处理 |
|---|---|
某组 [失败] | 看错误输出:undefined reference → 对应库没合入,重跑脚本;No such file → 对应 rpm 没下载到,看 [警告] 未找到 提示 |
OpenSSL 组报 cannot find -lssl | libcrypto/libssl 软链断。脚本已内置强制修复;仍失败就把 [警告] 行和冒烟错误贴出来排查 |
| 全部源不可达 | 离线环境:把 openEuler 20.03 aarch64 ISO 拷入挂载后 BASE="file:///mnt/Packages" bash 06-sysroot-full.sh |
脚本是幂等的,重复执行安全(已下载的 rpm 不重下)。
步骤 4:治本清理 .pc 文件(必做,5 分钟,防后面必踩的坑)
背景(可跳过):openEuler 的部分 .pc 文件(xorg proto 系列)的 Cflags
里带了多余的 -I${includedir}(= 指向头文件根目录 /usr/include)。Qt 编译
qtvirtualkeyboard 模块时,这个 flag 会变成 -isystem <sysroot>/usr/include,
与交叉编译器的 --sysroot 冲突,导致 PCH 编译报
cstdlib:75: fatal error: stdlib.h(这是本方案实测踩过的最大的坑,根因
排查了很久)。提前清掉,后面 make 一次过。
source /etc/profile.d/cross-aarch64.sh
SYSROOT=$(aarch64-linux-gnu-gcc -print-sysroot)
# 两种"根目录"形态都清;-I${includedir}/xxx 子目录形式(无害)保留不动
find "${SYSROOT}" -name '*.pc' -type f -exec \
sed -i -E 's#^(Cflags:.*) -I\$\{includedir\}( |$)#\1\2#; s#^(Cflags:.*) -I/usr/include( |$)#\1\2#' {} +
# 验证(预期输出空 [] —— flag 已消失)
export PKG_CONFIG_SYSROOT_DIR=${SYSROOT}
export PKG_CONFIG_LIBDIR=${SYSROOT}/usr/lib/pkgconfig:${SYSROOT}/usr/share/pkgconfig
echo "=> [$(pkg-config --cflags xcb xcb-xfixes 2>/dev/null)]"
步骤 5:configure + make + install(核心,40~90 分钟)
5.1 本次构建的策略(为什么是这些参数)
| 策略 | 参数 | 效果 |
|---|---|---|
| Qt 全静态 | -static | 程序单文件,目标机不需要 Qt |
| 内置 xcb 全家桶 | -qt-xcb | libxcb 本体+14 个 xcb 子库全部编进 Qt,消除 14 个动态依赖 |
| 静态 xkbcommon | 藏 .so 入口选 .a | 消除 libxkbcommon-x11 依赖(目标机缺这个会跑不起来) |
| 不用 glib | -no-glib | 省 libglib/gthread/pcre/ffi 4 个依赖(Qt 自带事件分发器,glib 只是可选集成) |
| 不用 eglfs | -no-eglfs | xcb 桌面用不到,省 libdrm/libgbm |
| 不用 dbus | -no-dbus | 动态 dbus 会拖 libsystemd→gcrypt/lz4/xz/selinux 一串传递依赖。代价:没有 QDBus 模块(项目用到再说) |
| 中文支持 | -fontconfig -system-freetype | 直接用目标机系统字体(Qt 5.14 硬性要求 fontconfig 必须配 system freetype) |
5.2 创建构建脚本
cat > /opt/qtwork/11-build-qt-qtxcb.sh <<'SCRIPT_EOF'
#!/bin/bash
# 11-build-qt-qtxcb.sh —— 最大静态化编译 Qt 5.14.2 aarch64 桌面版
# 前置:06-sysroot-full.sh 成功 + 步骤4 的 .pc 清理已做
# 用法:cd /opt/qtwork && bash 11-build-qt-qtxcb.sh 耗时约 40~90 分钟
set -e -o pipefail
source /etc/profile.d/cross-aarch64.sh
SYSROOT=$(aarch64-linux-gnu-gcc -print-sysroot)
export PKG_CONFIG_SYSROOT_DIR=${SYSROOT}
export PKG_CONFIG_LIBDIR=${SYSROOT}/usr/lib/pkgconfig:${SYSROOT}/usr/share/pkgconfig
HIDE=/opt/qtwork/sysroot-so-hidden
echo ">>> [1/4] 静态化准备..."
# 1a) 藏 glib/gthread/pcre 的 .so 入口(-no-glib 下本不会链 glib,藏是双保险)
mkdir -p ${HIDE}/glib
for so in libglib-2.0.so libgthread-2.0.so libpcre.so; do
for d in ${SYSROOT}/usr/lib64 ${SYSROOT}/usr/lib; do
if [ -L "${d}/${so}" ] || [ -e "${d}/${so}" ]; then
mv "${d}/${so}" ${HIDE}/glib/ 2>/dev/null || true
echo " 已藏: ${d}/${so}"
fi
done
done
# 1b) 藏 xkbcommon 的 .so 入口(ld 找库顺序 libXXX.so→libXXX.a,
# 藏起 .so 入口就会自动选 .a 静态链入)
mkdir -p ${HIDE}/xkbcommon
for so in libxkbcommon.so libxkbcommon-x11.so; do
for d in ${SYSROOT}/usr/lib64 ${SYSROOT}/usr/lib; do
if [ -L "${d}/${so}" ] || [ -e "${d}/${so}" ]; then
mv "${d}/${so}" ${HIDE}/xkbcommon/ 2>/dev/null || true
echo " 已藏: ${d}/${so}"
fi
done
done
for a in libxkbcommon.a libxkbcommon-x11.a; do
found=$(find -L ${SYSROOT}/usr/lib ${SYSROOT}/usr/lib64 -maxdepth 1 -name "${a}" 2>/dev/null | head -1)
[ -n "${found}" ] || { echo "FAIL: sysroot 缺 ${a},先重跑 06-sysroot-full.sh"; exit 1; }
echo " OK: ${found}"
done
# 1c) 修补 xkbcommon-x11.pc:静态闭包必须显式补 -lxcb-xkb
# (libxkbcommon-x11.a 引用 xcb_xkb_* 符号,由 libxcb-xkb 提供)
f=$(find -L ${SYSROOT}/usr/lib/pkgconfig -maxdepth 1 -name "xkbcommon-x11.pc" 2>/dev/null | head -1)
[ -z "${f}" ] && { echo "FAIL: 找不到 xkbcommon-x11.pc"; exit 1; }
sed -i 's/^Libs:.*/Libs: -L${libdir} -lxkbcommon-x11 -lxkbcommon -lxcb-xkb -lxcb/' "${f}"
echo " 已修补: ${f}"
grep '^Libs' "${f}" | sed 's/^/ /'
# 1d) 与 Qt configure 同口径的预检(必须真实调用函数,空函数冒烟是假阳性)
cat > /tmp/xkb11.cpp <<'EOF'
#include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbcommon-x11.h>
int main() { return xkb_x11_get_core_keyboard_device_id(nullptr); }
EOF
if aarch64-linux-gnu-g++ --sysroot=${SYSROOT} /tmp/xkb11.cpp \
$(pkg-config --cflags --libs xkbcommon-x11) -o /tmp/xkb11 2>/tmp/xkb11.err; then
echo " [OK] xkbcommon-x11 静态闭包链接通过(含 -lxcb-xkb)"
else
echo "FAIL: xkbcommon-x11 预检失败,错误如下:"
sed 's/^/ /' /tmp/xkb11.err | head -15
exit 1
fi
echo ">>> [2/4] configure(-qt-xcb -no-glib -no-eglfs -no-dbus)..."
cd /opt/qtwork
rm -rf build-desktop-full && mkdir -p build-desktop-full && cd build-desktop-full
/opt/qtwork/qt-everywhere-src-5.14.2/configure \
-opensource -confirm-license -release -static \
-prefix /opt/qt5.14.2-aarch64-static-xcb -extprefix /opt/qt5.14.2-aarch64-static-xcb \
-xplatform linux-aarch64-gnu-g++ -sysroot ${SYSROOT} \
-nomake tests -nomake examples \
-skip qtwebengine -skip qtwebview -skip qtwebchannel -skip qtwayland -skip qtscript -skip qtgamepad \
-skip qtspeech -skip qtremoteobjects -skip qtscxml -skip qtlottie -skip qtdatavis3d \
-skip qtsensors -skip qtlocation -skip qtserialbus -skip qtconnectivity -skip qtpurchasing -skip qtquick3d \
-xcb -qt-xcb -xcb-xlib -fontconfig -system-freetype -openssl-linked \
-no-glib -no-eglfs -no-dbus \
-opengl desktop -linuxfb -evdev \
-qt-libpng -qt-libjpeg -qt-harfbuzz -qt-pcre -qt-zlib -sql-sqlite -no-warnings-are-errors \
2>&1 | tee ../configure-qtxcb.log
echo "--- 关键特性核对(XCB XKB 必须 yes,否则键盘不可用)---"
grep -E "XCB|Xkb|xkb|Fontconfig|GLib|OpenGL|installed into" config.summary 2>/dev/null | head -12 || true
grep -q "XCB XKB .............................. yes" config.summary 2>/dev/null \
|| { echo "FAIL: XCB XKB 不是 yes,键盘支持缺失,把 config.summary 贴出来排查"; exit 1; }
echo ">>> [3/4] make(约 40~90 分钟)..."
make -j$(nproc) 2>&1 | tee ../make-qtxcb.log
echo ">>> [4/4] make install..."
make install 2>&1 | tee ../install-qtxcb.log
/opt/qt5.14.2-aarch64-static-xcb/bin/qmake -v
echo ""
echo "完成。下一步:编译 demo 验收(见手册步骤 6)。"
SCRIPT_EOF
5.3 执行
cd /opt/qtwork && bash 11-build-qt-qtxcb.sh
configure 通过的判定——summary 必须满足:
| 关键行 | 期望值 |
|---|---|
Building for: | linux-aarch64-gnu-g++ (aarch64...) |
XCB / XCB Xlib / XCB XKB | yes(XKB 是 yes 才有键盘支持) |
Fontconfig | yes |
OpenSSL | yes (linked) |
GLib | no(预期,故意的) |
D-Bus | no(预期,故意的) |
Desktop OpenGL | yes |
| 末尾 | Qt is now configured for building. Just run 'make'. |
make 失败处理(按现象对号入座):
| 现象 | 处理 |
|---|---|
编译器被 Killed(内存不足) | make -j2 续编(make 支持断点续编,已编完的模块不重编) |
qtvirtualkeyboard 报 cstdlib:75 fatal error: stdlib.h | 步骤 4 没做或没做干净。执行下面的修复脚本 14 后重跑 make(见 5.4) |
| 其他模块失败 | 把 grep -nE "Error [0-9]" /opt/qtwork/make-qtxcb.log 附近 30 行贴出来排查 |
5.4 修复脚本 14(PCH 坑的兜底,一般用不到)
如果步骤 4 已做,make 不会遇到这个问题,可跳过本节。
万一在 qtvirtualkeyboard 阶段报 stdlib.h 错误,创建此脚本执行即可。
cat > /opt/qtwork/14-fix-vkb-pch.sh <<'SCRIPT_EOF'
#!/bin/bash
# 14-fix-vkb-pch.sh —— 修复 qtvirtualkeyboard PCH 编译失败
# 根因:pkg-config 输出混入 -I<sysroot>/usr/include,qmake 写进 Makefile
# 成 -isystem,破坏 gcc 的 #include_next <stdlib.h>
set -e -o pipefail
source /etc/profile.d/cross-aarch64.sh
SYSROOT=$(aarch64-linux-gnu-gcc -print-sysroot)
export PKG_CONFIG_SYSROOT_DIR=${SYSROOT}
export PKG_CONFIG_LIBDIR=${SYSROOT}/usr/lib/pkgconfig:${SYSROOT}/usr/share/pkgconfig
BUILD=/opt/qtwork/build-desktop-full
echo ">>> [1/3] 治本:清掉 sysroot 内所有 .pc 的根目录 -I ..."
find "${SYSROOT}" -name '*.pc' -type f -exec \
sed -i -E 's#^(Cflags:.*) -I\$\{includedir\}( |$)#\1\2#; s#^(Cflags:.*) -I/usr/include( |$)#\1\2#' {} +
echo " 再验一次 pkg-config 输出(预期:空):"
echo " => [$(pkg-config --cflags xcb xcb-xfixes 2>/dev/null || true)]"
echo ">>> [2/3] 清理构建树 Makefile 中的 -isystem <sysroot>/usr/include ..."
CNT=0
while IFS= read -r mf; do
sed -i "s# -isystem ${SYSROOT}/usr/include # #g; s# -isystem ${SYSROOT}/usr/include\$##" "${mf}"
CNT=$((CNT+1))
done < <(grep -rlE -- "-isystem ${SYSROOT}/usr/include( |$)" ${BUILD} --include=Makefile 2>/dev/null || true)
echo " 本次清理 Makefile: ${CNT} 个"
LEFT=$(grep -rlE -- "-isystem ${SYSROOT}/usr/include( |$)" ${BUILD} --include=Makefile 2>/dev/null | wc -l || true)
[ "${LEFT}" -eq 0 ] || { echo "FAIL: 仍有 ${LEFT} 个 Makefile 残留"; exit 1; }
echo ">>> [3/3] 续编 make(增量)+ 清空安装目录重装 ..."
cd ${BUILD}
make -j$(nproc) 2>&1 | tee -a ../make-qtxcb.log
rm -rf /opt/qt5.14.2-aarch64-static-xcb
make install 2>&1 | tee ../install-qtxcb.log
/opt/qt5.14.2-aarch64-static-xcb/bin/qmake -v
echo "完成。"
SCRIPT_EOF
# 执行:cd /opt/qtwork && bash 14-fix-vkb-pch.sh
5.5 安装验收
ls /opt/qt5.14.2-aarch64-static-xcb/bin/qmake # 存在
ls /opt/qt5.14.2-aarch64-static-xcb/lib/*.so 2>/dev/null # 预期:无输出(纯静态)
ls /opt/qt5.14.2-aarch64-static-xcb/lib/libQt5VirtualKeyboard.a # 存在(虚拟键盘)
ls /opt/qt5.14.2-aarch64-static-xcb/lib/libQt5Quick.a \
/opt/qt5.14.2-aarch64-static-xcb/lib/libQt5Charts.a # 存在(Quick/Charts 已保留)
步骤 6:demo 编译验收
6.1 建 demo
mkdir -p /opt/qtwork/demo-xcb && cd /opt/qtwork/demo-xcb
cat > main.cpp <<'EOF'
#include <QApplication>
#include <QLabel>
#include <QVBoxLayout>
#include <QWidget>
#include <QSysInfo>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QWidget w;
w.setWindowTitle(QStringLiteral("Qt aarch64 静态版"));
QLabel *label = new QLabel(
QStringLiteral("Qt version: %1\nArch: %2\nKernel: %3")
.arg(qVersion(), QSysInfo::currentCpuArchitecture(), QSysInfo::kernelVersion()));
label->setAlignment(Qt::AlignCenter);
QVBoxLayout *lay = new QVBoxLayout(&w);
lay->addWidget(label);
w.resize(420, 200);
w.show();
return app.exec();
}
EOF
cat > demo.pro <<'EOF'
QT += widgets
CONFIG += release
TARGET = demo
SOURCES += main.cpp
EOF
6.2 编译 + 验收
/opt/qt5.14.2-aarch64-static-xcb/bin/qmake demo.pro
make -j$(nproc)
aarch64-linux-gnu-readelf -d demo | grep NEEDED
验收标准——NEEDED 清单(19 个,一个都不能多):
| 类别 | 允许的动态库 |
|---|---|
| 显示/图形 | libEGL.so.1、libGL.so.1 |
| 字体 | libfontconfig.so.1、libfreetype.so.6 |
| X11 | libX11.so.6、libX11-xcb.so.1、libXext.so.6、libSM.so.6、libICE.so.6 |
| xcb | libxcb.so.1、libxcb-glx.so.0、libxcb-xkb.so.1 |
| C 运行时 | libc/libm/libdl/libpthread/libstdc++/libgcc_s/ld-linux-aarch64 |
红线(出现即失败,回步骤 5 检查):
- 任何
libQt5*.so libxkbcommon*、libglib*、libdbus*、libxcb-randr/render/image/...碎片库
瘦身(可选):
# gcc7 的 note bug:必须先移除该 section 再 strip
aarch64-linux-gnu-objcopy --remove-section .gnu.build.attributes demo
aarch64-linux-gnu-strip demo
步骤 7:目标机部署(零配置)
# 交叉机上
scp demo root@<目标机IP>:/root/
# 目标机上(图形桌面会话中)
chmod +x /root/demo && /root/demo
预期:直接弹窗显示 Qt version: 5.14.2 / Arch: "arm64" / Kernel: ...,中文正常。
验证依赖(可选):目标机上 ldd /root/demo——19 个直接依赖全部 found;
多出来的 libxml2/libbz2/libpng16 等是二级依赖(loader 自动解析),无需处理。
常见问题:
| 现象 | 处理 |
|---|---|
libXXX.so not found | 不应发生(19 个白名单库所有 ARM 桌面必带);若真出现,把 ldd 输出发来排查 |
| 无窗口/起不来 | 目标机没启动图形桌面;在桌面会话里跑,或 QT_QPA_PLATFORM=offscreen ./demo 冒烟 |
GLIBC_2.2x not found | 目标机 glibc < 2.28(太老),该目标机不适用本构建 |
步骤 8:日常使用(环境搭好后,每个新项目 5 分钟)
8.1 每次开机后环境自检(1 分钟)
root 登录自动加载 /etc/profile.d/cross-aarch64.sh,确认三条:
which aarch64-linux-gnu-g++ # Bootlin 路径
which qmake # /opt/qt5.14.2-aarch64-static-xcb/bin/qmake
qmake -v # Qt 5.14.2 ... -xcb 前缀
永远用
/opt/qt5.14.2-aarch64-static-xcb/bin/qmake,防止误用宿主机 x86 的 qmake。
8.2 qmake 项目
cd /opt/qtwork/myapp
/opt/qt5.14.2-aarch64-static-xcb/bin/qmake myapp.pro
make -j$(nproc)
# 瘦身 + readelf 白名单验收 + scp,同步骤 6/7
qmake 自动链入全部静态插件(平台/图片/GL),.pro 无需额外配置。
8.3 CMake 项目(比 qmake 多补一个文件)
① 新建 plugin_import.cpp(qmake 会自动生成,CMake 不会;不补则目标机报
no Qt platform plugin could be initialized):
#include <QtPlugin>
Q_IMPORT_PLUGIN(QXcbIntegrationPlugin) // 平台(必须)
Q_IMPORT_PLUGIN(QXcbEglIntegrationPlugin) // GL 集成(必须)
Q_IMPORT_PLUGIN(QXcbGlxIntegrationPlugin) // GL 集成(必须)
Q_IMPORT_PLUGIN(QGifPlugin) Q_IMPORT_PLUGIN(QICNSPlugin) // 图片×8
Q_IMPORT_PLUGIN(QICOPlugin) Q_IMPORT_PLUGIN(QJpegPlugin) // (全加零风险,
Q_IMPORT_PLUGIN(QTgaPlugin) Q_IMPORT_PLUGIN(QTiffPlugin) // 都是静态库,
Q_IMPORT_PLUGIN(QWbmpPlugin) Q_IMPORT_PLUGIN(QWebpPlugin) // 不增动态依赖)
② 工具链文件(一次性,存 /opt/qtwork/aarch64-toolchain.cmake):
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
set(CMAKE_SYSROOT /opt/bootlin-aarch64-glibc-2018.11/aarch64-buildroot-linux-gnu/sysroot)
set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++)
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT} /opt/qt5.14.2-aarch64-static-xcb)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
③ CMakeLists.txt 关键行:
set(CMAKE_AUTOMOC ON); set(CMAKE_AUTORCC ON); set(CMAKE_AUTOUIC ON)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
find_package(Qt5 REQUIRED COMPONENTS Widgets)
add_executable(myapp main.cpp mainwindow.cpp plugin_import.cpp)
target_link_libraries(myapp PRIVATE
Qt5::Widgets
Qt5::QXcbIntegrationPlugin Qt5::QXcbEglIntegrationPlugin Qt5::QXcbGlxIntegrationPlugin
Qt5::QGifPlugin Qt5::QICNSPlugin Qt5::QICOPlugin Qt5::QJpegPlugin
Qt5::QTgaPlugin Qt5::QTiffPlugin Qt5::QWbmpPlugin Qt5::QWebpPlugin)
类名大小写有坑(源码核实):ICO 是
QICO、ICNS 是QICNS全大写,Tga 是QTga。
项目若用 QtSql+SQLite,再加QSQLiteDriverPlugin;其余模块不需要插件。
④ 编译命令:
source /etc/profile.d/cross-aarch64.sh
mkdir -p build-arm && cd build-arm
cmake .. -DCMAKE_TOOLCHAIN_FILE=/opt/qtwork/aarch64-toolchain.cmake \
-DCMAKE_PREFIX_PATH=/opt/qt5.14.2-aarch64-static-xcb \
-DCMAKE_BUILD_TYPE=Release
make -j$(nproc)
附录 A:踩坑全记录(按步骤排序)
| # | 坑 | 根因 | 解法(已固化进文档步骤) |
|---|---|---|---|
| 1 | Linaro 工具链链接下载到 HTML | releases.linaro.org 2026 年下线 | 用 Bootlin 源(步骤 1) |
| 2 | gcc.br_real: No such file | Bootlin 编译器按"被调用名字"定位真身 | 全量建 aarch64-linux-gnu-* 别名(步骤 1.5) |
| 3 | yum install rpm2cpio 报无匹配 | rpm2cpio 由 rpm 自带 | 不用装;缺 cpio 才装(步骤 3) |
| 4 | rpm 版本号含字母匹配不到(openssl-1.1.1d) | 正则写死 [0-9.]* | 用 [0-9][0-9a-zA-Z.]*(06 脚本已内置) |
| 5 | noarch 包漏下(xorg-x11-proto-devel) | 只匹配 aarch64 | aarch64 优先 + noarch 兜底(06 已内置) |
| 6 | sysroot 里 .so 大量断链 | openEuler 软链是绝对路径/相对路径混形态 | 断链扫描 + 按 basename 跨目录重链(06 已内置) |
| 7 | find 扫不到软链 | Bootlin sysroot 的 usr/lib 本身是软链 | find 加 -L(06 已内置) |
| 8 | OpenSSL 链接 cannot find -lssl | libcrypto.so.1.1 真身在 openssl-libs 包(不在 openssl/devel) | G_SSL 含 openssl-libs + 强制重链(06 已内置) |
| 9 | 链接报 fcntl64@GLIBC_2.28 未定义 | openEuler 库按 glibc 2.28 构建,Bootlin sysroot 只有 2.27 | 06 把 glibc 2.28 合入 sysroot |
| 10 | 静态库冒烟"永远通过" | 测试代码不调用库函数,链接器没从 .a 拉成员 | 冒烟必须真实调用函数(11 脚本 1d) |
| 11 | configure 找到 x86 的库 | pkg-config 没指向 sysroot | export PKG_CONFIG_SYSROOT_DIR + LIBDIR(11 已内置) |
| 12 | configure 报 Feature 'glib' failed | 静态 glib 闭包不完整(pkg-config 不含 Libs.private) | 改 -no-glib(11 已内置) |
| 13 | xkbcommon-x11... no → 键盘失灵 | libxkbcommon-x11.a 需 xcb_xkb_*(libxcb-xkb)而 .pc 没写 | xkbcommon-x11.pc 补 -lxcb-xkb(11 已内置) |
| 14 | vkb 编译 cstdlib:75 stdlib.h | proto 系列 .pc 的 -I${includedir} → -isystem <sysroot>/usr/include 破坏 include_next | 步骤 4 提前治本;兜底脚本 14 |
| 15 | demo 依赖 37 个、目标机缺 libxkbcommon-x11 跑不起来 | 默认全动态 | -qt-xcb + 藏 .so 选 .a(11 已内置) |
| 16 | 旧安装前缀残留被链入(dbus/eglfs/glib 复活) | make install 不清理旧产物 | 重装前 rm -rf 安装目录(14 已内置) |
| 17 | strip 报 corrupt GNU build attribute note | gcc7 的 note 与 Bootlin binutils 冲突 | 先 objcopy --remove-section 再 strip(步骤 6) |
| 18 | grep 误报"-isystem 残留" | 无锚定匹配把 include/glib-2.0 子目录也算进去 | 匹配必须锚定 `(空格 |
附录 B:目录清单与回滚
| 路径 | 内容 | 可否删除 |
|---|---|---|
/opt/bootlin-aarch64-glibc-2018.11/ | 工具链+sysroot | 不可 |
/opt/qtwork/qt-everywhere-src-5.14.2/ | Qt 源码 | 不可(增量重编要用) |
/opt/qtwork/build-desktop-full/ | Qt 构建树(约 10G) | 可删(需要重编时全量重来) |
/opt/qt5.14.2-aarch64-static-xcb/ | 最终产物(qmake、静态库、mkspecs) | 不可 |
/opt/qtwork/sysroot-rpms/、sysroot-staging/ | rpm 缓存/暂存 | 可删(省空间,重跑 06 需重下) |
/opt/qtwork/sysroot-so-hidden/ | 被藏起的 .so 入口(可恢复动态链接用) | 不可 |
恢复动态 xkbcommon(如需):cp -a /opt/qtwork/sysroot-so-hidden/xkbcommon/* <sysroot>/usr/lib64/
附录 C:模块保留/裁剪清单
保留:Widgets、Gui、Quick/QML、QuickControls2、GraphicalEffects、Charts、Svg、
Network、WebSockets、NetworkAuth、SerialPort、Multimedia、X11Extras、Concurrent、
Tools、Translations、VirtualKeyboard、sqlite 驱动、png/jpeg/gif/ico/tiff/webp 图片插件、
linuxfb/offscreen 平台兜底。
裁剪:webengine/webview/webchannel(嵌入式 Chromium 交叉编译不可行)、wayland
(目标桌面是 X11)、script(废弃)、speech/gamepad/remoteobjects/scxml/lottie/
datavis3d/sensors/location/serialbus/connectivity/purchasing/quick3d(罕用)、
dbus(传递依赖拖太多,需要时单独开带 dbus 的前缀)。
ARM 项目日常编译手册(交叉编译机日常使用)
适用场景:交叉编译机(Kylin x86_64,
/opt/qtwork)已装好静态 Qt 后,
以后每来一个 Qt 项目,怎么编出能在 ARM 目标机直接运行的程序。
重启机器后照本文从头走一遍即可,全程 5 分钟。
一、重启后环境自检(每次开机后做一次,1 分钟)
环境由 /etc/profile.d/cross-aarch64.sh 提供,root 登录即自动生效。
登录后跑 3 条命令确认:
which aarch64-linux-gnu-g++ # 预期:/opt/bootlin-aarch64-glibc-2018.11/.../bin/...(不是 command not found)
which qmake # 预期:/opt/qt5.14.2-aarch64-static-xcb/bin/qmake
qmake -v # 预期:Using Qt version 5.14.2 in /opt/qt5.14.2-aarch64-static-xcb/lib
⚠️ 绝对不要用宿主机自己的 qmake
如果机器上装过 x86 的 Qt,/usr/bin/qmake 会编出 x86 程序。永远用
/opt/qt5.14.2-aarch64-static-xcb/bin/qmake(按下面步骤操作就不会踩)。
二、qmake 项目:编译流程(每次都这样)
步骤 1:把项目拷到交叉机
# 在你自己的电脑上执行(示例:项目名 myapp)
scp -r myapp root@<交叉机IP>:/opt/qtwork/myapp
项目只需源码 + .pro 文件,不用拷 build 产物(build-/、.o、Makefile 都不用)。
步骤 2:在交叉机上生成 Makefile 并编译
cd /opt/qtwork/myapp
/opt/qt5.14.2-aarch64-static-xcb/bin/qmake myapp.pro
make -j$(nproc)
- 静态 Qt 会自动把 Qt 库、xcb 平台插件、图片插件全部链进可执行文件,.pro 不用写任何额外配置。
Project WARNING: Redundant entries in QTPLUGIN: qxcb只是警告,可无视(也可把 .pro 里QTPLUGIN += qxcb删掉)。
步骤 3:(可选)瘦身
aarch64-linux-gnu-objcopy --remove-section .gnu.build.attributes myapp # gcc7 note bug,必须先做
aarch64-linux-gnu-strip myapp
步骤 4:验证依赖(重要,30 秒)
aarch64-linux-gnu-readelf -d myapp | grep NEEDED
白名单——输出只能包含下面这些(demo 实测基线):
| 类别 | 允许的动态库 |
|---|---|
| 显示/图形 | libEGL.so.1、libGL.so.1 |
| 字体 | libfontconfig.so.1、libfreetype.so.6 |
| X11 | libX11.so.6、libX11-xcb.so.1、libXext.so.6、libSM.so.6、libICE.so.6 |
| xcb | libxcb.so.1、libxcb-glx.so.0、libxcb-xkb.so.1 |
| C 运行时 | libc.so.6、libm.so.6、libdl.so.2、libpthread.so.0、libstdc++.so.6、libgcc_s.so.1、ld-linux-aarch64.so.1 |
(ldd 里多出来的 libxml2/libbz2/libpng16/libz/libXau/libuuid/libGLX 等是二级依赖,
由 loader 自动解析,不用管。)
红线——出现以下任何一个都说明编错了,回到步骤 2 检查 qmake 路径:
- 任何
libQt5*.so(Qt 必须是静态的) libxkbcommon*、libglib*、libdbus*、libxcb-randr/render/image/...(xcb 碎片库)
步骤 5:拷到目标机直接运行
scp myapp root@<目标机IP>:/root/
# 目标机上:
chmod +x /root/myapp && /root/myapp
单文件部署,目标机零配置,不依赖任何 Qt 库。
三、qmake 常见问题速查
| 现象 | 原因与解决 |
|---|---|
aarch64-linux-gnu-g++: command not found | 没登录 root 或没 source:source /etc/profile.d/cross-aarch64.sh |
qmake: 未找到 或指向 /usr/bin | PATH 修正,见第一章的一次性修正 |
目标机运行报 libQt5Core.so.5 not found | 用错 qmake(编的是动态 Qt),重跑 which qmake 检查 |
| 目标机运行无窗口/起不来 | 目标机没桌面(X 未启动)或无 DISPLAY;在有桌面的会话里跑 |
链接报 xcb_xxx 未定义 | qmake 用错前缀(旧的动态链不存在),检查 qmake -v |
strip 报 corrupt GNU build attribute note | 先执行步骤 3 的 objcopy 再 strip |
| 想用 WebEngine/Speech 等模块 | 这些编译时被 skip 了,没有。可用:Widgets、Quick/QML、Charts、SVG、Network、SerialPort、Multimedia、VirtualKeyboard 等 |
四、可用模块速览(本次静态 Qt 包含)
Widgets、Gui、Quick/QML、QuickControls2、Charts、Svg、Network、WebSockets、
NetworkAuth、SerialPort、Multimedia、X11Extras、Concurrent、DBus 没有(-no-dbus)、
VirtualKeyboard(有,1.4MB 静态库)。
若项目写了
QT += dbus,会报找不到模块——删掉或找我重新开一个带 dbus 的前缀。
五、CMake 项目:编译流程
5.1 先搞懂:CMake 比 qmake 多做一件事
qmake 编译时会自动生成一个插件导入文件(把 xcb 平台插件、8 个图片插件
"焊"进程序);CMake 不会——不补的话编译能过,但目标机运行报
no Qt platform plugin could be initialized。
所以 CMake 项目 = 你原来的 CMakeLists.txt 加一个新文件、加几行配置,见 5.3。
5.2 一次性准备:工具链文件(做一次,所有 CMake 项目共用)
新建 /opt/qtwork/aarch64-toolchain.cmake,内容原样复制:
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
set(CMAKE_SYSROOT /opt/bootlin-aarch64-glibc-2018.11/aarch64-buildroot-linux-gnu/sysroot)
set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++)
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT} /opt/qt5.14.2-aarch64-static-xcb)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) # moc 等 x86 宿主工具不在 sysroot 里找
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
5.3 项目里补两样东西(内容全部给全,直接抄)
① 新建 plugin_import.cpp(与 main.cpp 同级,内容原样复制,
与 qmake 自动生成的 demo_plugin_import.cpp 逐行一致):
#include <QtPlugin>
/* 平台插件(必须)——窗口能在 X11 桌面显示全靠它 */
Q_IMPORT_PLUGIN(QXcbIntegrationPlugin)
/* GL 集成插件(2 个,必须)——OpenGL 渲染路径(GLX/EGL)的桥接,
对应链接行里的 libqxcb-egl-integration.a / libqxcb-glx-integration.a */
Q_IMPORT_PLUGIN(QXcbEglIntegrationPlugin)
Q_IMPORT_PLUGIN(QXcbGlxIntegrationPlugin)
/* 图片格式插件:8 个全上(与 demo 实测配置相同)。
全加零风险——它们都是静态库,不增加任何动态依赖,只多 1~2MB 体积。
想省体积可删掉用不到的行,互不影响。 */
Q_IMPORT_PLUGIN(QGifPlugin) // gif
Q_IMPORT_PLUGIN(QICNSPlugin) // icns(macOS 图标格式)
Q_IMPORT_PLUGIN(QICOPlugin) // ico/cur(程序图标)
Q_IMPORT_PLUGIN(QJpegPlugin) // jpg/jpeg
Q_IMPORT_PLUGIN(QTgaPlugin) // tga
Q_IMPORT_PLUGIN(QTiffPlugin) // tiff/tif
Q_IMPORT_PLUGIN(QWbmpPlugin) // wbmp
Q_IMPORT_PLUGIN(QWebpPlugin) // webp
类名已逐一对照 Qt 5.14.2 源码核实。注意大小写有坑:
ICO 是QICO、ICNS 是QICNS(全大写),Tga 是QTga(T 大写、ga 小写)。
② CMakeLists.txt——完整最小示例,对照你的项目改(主要是把你项目的
源文件列表填进去):
cmake_minimum_required(VERSION 3.10)
project(myapp LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_AUTOMOC ON) # 自动跑 moc(头文件里有 Q_OBJECT 就需要)
set(CMAKE_AUTORCC ON) # 自动跑 rcc(用了 .qrc 资源文件就需要)
set(CMAKE_AUTOUIC ON) # 自动跑 uic(用了 .ui 界面文件就需要)
set(CMAKE_POSITION_INDEPENDENT_CODE ON) # 与 Qt 静态库的 -fPIC 编译选项保持一致
find_package(Qt5 REQUIRED COMPONENTS Widgets)
add_executable(myapp
main.cpp
mainwindow.cpp
plugin_import.cpp # ← ①新建的那个文件,必须加进来
)
target_link_libraries(myapp PRIVATE
Qt5::Widgets
# ↓ 插件目标,与 plugin_import.cpp 里的 Q_IMPORT_PLUGIN 一一对应(11 个)
Qt5::QXcbIntegrationPlugin
Qt5::QXcbEglIntegrationPlugin
Qt5::QXcbGlxIntegrationPlugin
Qt5::QGifPlugin
Qt5::QICNSPlugin
Qt5::QICOPlugin
Qt5::QJpegPlugin
Qt5::QTgaPlugin
Qt5::QTiffPlugin
Qt5::QWbmpPlugin
Qt5::QWebpPlugin
)
还需要其他插件吗? 就三类场景:显示窗口 + GL 渲染 + 显示图片,上面 11 个已全覆盖。
唯一的例外:项目若用了 QtSql + SQLite 数据库,再加两行——
plugin_import.cpp 里加Q_IMPORT_PLUGIN(QSQLiteDriverPlugin),
target_link_libraries 里加Qt5::QSQLiteDriverPlugin。
Network/SerialPort/Charts 等模块不需要任何插件。
5.4 编译命令(每次都这样)
source /etc/profile.d/cross-aarch64.sh # 第一步必须有:解析 aarch64 编译器别名
cd /opt/qtwork/myapp
mkdir -p build-arm && cd build-arm
cmake .. \
-DCMAKE_TOOLCHAIN_FILE=/opt/qtwork/aarch64-toolchain.cmake \
-DCMAKE_PREFIX_PATH=/opt/qt5.14.2-aarch64-static-xcb \
-DCMAKE_BUILD_TYPE=Release
make -j$(nproc)
产物在 build-arm/myapp。之后的瘦身、readelf 白名单验收、scp 部署与
qmake 流程完全相同(见第二章步骤 3~5)。
5.5 CMake 项目常见问题
| 现象 | 原因与解决 |
|---|---|
cmake 报找不到 aarch64-linux-gnu-g++ | 没 source profile(5.4 第一步) |
cmake 报 Could NOT find Qt5 | 漏了 -DCMAKE_PREFIX_PATH=/opt/qt5.14.2-aarch64-static-xcb |
| cmake 提示编译器变了/结果诡异 | 环境改过后 rm -rf build-arm 重来(CMake 缓存了旧编译器路径) |
| 编出的程序在宿主机能跑(x86) | 编译器没解析到交叉别名,重做 5.4 第一步并删 build-arm 重来 |
编译过、目标机报 no Qt platform plugin could be initialized | plugin_import.cpp 没加进 add_executable,或没链 Qt5::QXcbIntegrationPlugin |
链接报 undefined reference to qt_plugin_instance... | Q_IMPORT_PLUGIN 写的类名和 target_link_libraries 的目标名不一致/拼写错(注意 QICO/QICNS/QTga 大小写) |
| AUTOMOC 报 moc 执行失败 | moc 是宿主机工具,在 /opt/qt5.14.2-aarch64-static-xcb/bin/moc,确认存在且可执行 |
| CMakeLists 里写了 dbus 组件 | 这套 Qt 没有 dbus 模块(见第四章) |
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)