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
权限rootwhoami
磁盘/opt 所在分区剩余 ≥ 30Gdf -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 directory1.5 别名没做全,重跑那个 for 循环
下载到 HTML换网络重下;或别的机器下载后拷入 /opt/qtwork/ 从 1.3 继续
wget 太慢wgetcurl -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 -lssllibcrypto/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-xcblibxcb 本体+14 个 xcb 子库全部编进 Qt,消除 14 个动态依赖
静态 xkbcommon藏 .so 入口选 .a消除 libxkbcommon-x11 依赖(目标机缺这个会跑不起来)
不用 glib-no-glib省 libglib/gthread/pcre/ffi 4 个依赖(Qt 自带事件分发器,glib 只是可选集成)
不用 eglfs-no-eglfsxcb 桌面用不到,省 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 XKByes(XKB 是 yes 才有键盘支持)
Fontconfigyes
OpenSSLyes (linked)
GLibno(预期,故意的)
D-Busno(预期,故意的)
Desktop OpenGLyes
末尾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
X11libX11.so.6、libX11-xcb.so.1、libXext.so.6、libSM.so.6、libICE.so.6
xcblibxcb.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:踩坑全记录(按步骤排序)

#根因解法(已固化进文档步骤)
1Linaro 工具链链接下载到 HTMLreleases.linaro.org 2026 年下线用 Bootlin 源(步骤 1)
2gcc.br_real: No such fileBootlin 编译器按"被调用名字"定位真身全量建 aarch64-linux-gnu-* 别名(步骤 1.5)
3yum install rpm2cpio 报无匹配rpm2cpio 由 rpm 自带不用装;缺 cpio 才装(步骤 3)
4rpm 版本号含字母匹配不到(openssl-1.1.1d)正则写死 [0-9.]*[0-9][0-9a-zA-Z.]*(06 脚本已内置)
5noarch 包漏下(xorg-x11-proto-devel)只匹配 aarch64aarch64 优先 + noarch 兜底(06 已内置)
6sysroot 里 .so 大量断链openEuler 软链是绝对路径/相对路径混形态断链扫描 + 按 basename 跨目录重链(06 已内置)
7find 扫不到软链Bootlin sysroot 的 usr/lib 本身是软链find 加 -L(06 已内置)
8OpenSSL 链接 cannot find -lssllibcrypto.so.1.1 真身在 openssl-libs 包(不在 openssl/devel)G_SSL 含 openssl-libs + 强制重链(06 已内置)
9链接报 fcntl64@GLIBC_2.28 未定义openEuler 库按 glibc 2.28 构建,Bootlin sysroot 只有 2.2706 把 glibc 2.28 合入 sysroot
10静态库冒烟"永远通过"测试代码不调用库函数,链接器没从 .a 拉成员冒烟必须真实调用函数(11 脚本 1d)
11configure 找到 x86 的库pkg-config 没指向 sysrootexport PKG_CONFIG_SYSROOT_DIR + LIBDIR(11 已内置)
12configure 报 Feature 'glib' failed静态 glib 闭包不完整(pkg-config 不含 Libs.private)-no-glib(11 已内置)
13xkbcommon-x11... no → 键盘失灵libxkbcommon-x11.a 需 xcb_xkb_*(libxcb-xkb)而 .pc 没写xkbcommon-x11.pc 补 -lxcb-xkb(11 已内置)
14vkb 编译 cstdlib:75 stdlib.hproto 系列 .pc 的 -I${includedir}-isystem <sysroot>/usr/include 破坏 include_next步骤 4 提前治本;兜底脚本 14
15demo 依赖 37 个、目标机缺 libxkbcommon-x11 跑不起来默认全动态-qt-xcb + 藏 .so 选 .a(11 已内置)
16旧安装前缀残留被链入(dbus/eglfs/glib 复活)make install 不清理旧产物重装前 rm -rf 安装目录(14 已内置)
17strip 报 corrupt GNU build attribute notegcc7 的 note 与 Bootlin binutils 冲突先 objcopy --remove-section 再 strip(步骤 6)
18grep 误报"-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
X11libX11.so.6、libX11-xcb.so.1、libXext.so.6、libSM.so.6、libICE.so.6
xcblibxcb.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/binPATH 修正,见第一章的一次性修正
目标机运行报 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 initializedplugin_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 模块(见第四章)
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐