Files
Unlucky-Launcher/packaging/docs/termux-glibc-build.md
T
AskaEth 16c61ca8b7 docs(packaging): 记录 Termux glibc 构建与 termux-x11 运行配方
新增 packaging/docs/termux-glibc-build.md:glibc 工具链(Rust gnu 组件 +
patchelf + clang/lld + Debian libgcc crt)、构建环境(LD_PRELOAD/CA/USTC 镜像)、
termux-x11 运行与截图取证、GPU 现状与已知边界。
支持矩阵四处同步:Termux 行从「GUI 未验证」改为「源码自建、CPU 渲染单机验证过」,
仍标注实验级、不在发布矩阵内。
2026-11-06 22:32:09 +08:00

7.0 KiB
Raw Blame History

Termux + termux-x11 上的 glibc 构建(试验级)

本页记录在 Termux 里构建 glibc 版 GUI,并在 termux-x11 里跑起来的可复现步骤。 2026-11-06 在一台 OnePlus 15(Android 17、Termux、KirinSU/root 环境)上实测通过: --software-renderer --screenshot 出 1180×760 PNG,中文正常,msa client_id=configured source=build。

定位:试验级、不承诺。发布矩阵里没有面向 Termux 的资产;本文只描述源码自建路径。 GUI 只在上述设备 + CPU 渲染(tiny-skia)上验证过,GPU 路径见 §5(尚未在 glibc 侧验证)。

1. 为什么不能直接 cargo build

Termux 的 rustc host 是 aarch64-linux-android(target_os = "android"):

  • crates/app-iced/Cargo.toml 的 x11 / wayland feature 只挂在 cfg(target_os = "linux") 下;
  • iced 的窗口层 winit 0.30 在 android target 上走 Android(NativeActivity)后端, 会拉 android-activity 并要求 game-activity/native-activity feature,普通进程编不过也跑不了。

所以要在 Termux 里产出一个 aarch64-unknown-linux-gnu(glibc) 二进制,靠 Termux 的 glibc 运行时 原生执行。

2. 工具链(一次性)

以下路径用变量表示,实际可放在任意本地目录(示例放在仓库内的 .gnu/,已 gitignore):

PREFIX="${PREFIX:-/data/data/com.termux/files/usr}"
G="$PWD/.gnu"

2.1 glibc 运行时

确认 $PREFIX/glibc/lib/ld-linux-aarch64.so.1 存在(pkg install glibc-runner 或装过 glibc 仓库 的包即可)。不需要 gcc-glibc:链接用 Termux 自带的 clang + lld,glibc 的 CRT/头文件由 glibc 包提供。

2.2 Rust gnu 工具链

下载 Rust 1.99.1(或与 rust-version 兼容的 stable)的 rustc / rust-std / cargo 三个 aarch64-unknown-linux-gnu 组件。官方源在国内很慢,用 USTC 镜像:

mkdir -p "$TMPDIR/ull-gnu" && cd "$TMPDIR/ull-gnu"
for c in rustc rust-std cargo; do
  curl -fLO "https://mirrors.ustc.edu.cn/rust-static/dist/$c-1.99.1-aarch64-unknown-linux-gnu.tar.xz"
done

解包后按组件布局合并到 $G/toolchain(rustc/bin、cargo/bin → toolchain/bin, 两者的 lib/ → toolchain/lib,rust-std-*/lib/rustlib/aarch64-unknown-linux-gnu → toolchain/lib/rustlib/)。

2.3 改 interpreter / rpath

工具链里的 rustc、cargo、rustdoc 是 glibc ELF,默认 interpreter 是 /lib/ld-linux-*.so.1, 在 Termux 上不存在:

LD="$PREFIX/glibc/lib/ld-linux-aarch64.so.1"
for f in "$G/toolchain/bin/rustc" "$G/toolchain/bin/cargo" "$G/toolchain/bin/rustdoc"; do
  patchelf --set-interpreter "$LD" --set-rpath "$PREFIX/glibc/lib:$G/toolchain/lib" "$f"
done

2.4 gcc 运行时片段

clang -fuse-ld=lld 仍然需要 crtbeginS.o / crtendS.o / libgcc.a。从 Debian 的 libgcc-14-dev(arm64)取即可(只解包,不安装):

curl -fLO https://deb.debian.org/debian/pool/main/g/gcc-14/libgcc-14-dev_14.2.0-19_arm64.deb
dpkg-deb -x libgcc-14-dev_14.2.0-19_arm64.deb "$G/gnu-support"
# crt/libgcc 在 $G/gnu-support/usr/lib/gcc/aarch64-linux-gnu/14/

2.5 linker 驱动

写一个 $G/bin/cc-glibc(chmod +x):

#!/data/data/com.termux/files/usr/bin/sh
unset LD_PRELOAD
P=$PREFIX
S="<仓库>/.gnu/gnu-support/usr/lib/gcc/aarch64-linux-gnu/14"
C="clang --target=aarch64-unknown-linux-gnu --sysroot=/ -isystem $P/glibc/include \
   -B$P/glibc/lib -B$S -L$S -L$P/glibc/lib -fuse-ld=lld"
for a in "$@"; do
  case "$a" in -c|-E|-S) exec $C "$@" ;; esac
done
exec $C -Wl,--dynamic-linker=$P/glibc/lib/ld-linux-aarch64.so.1 -Wl,-rpath,$P/glibc/lib "$@"

要点:--sysroot=/ 而不是 --sysroot=$PREFIX/glibc——glibc 的 libc.so 是含绝对路径的 linker script,带 sysroot 会被二次拼接;-B $P/glibc/lib 让 clang 找到 Scrt1.o/crti.o/crtn.o。

3. 构建

unset LD_PRELOAD                       # 必须:Termux 的 libtermux-exec 会被 glibc loader 当成坏 ELF
G="$PWD/.gnu"
export PATH="$G/toolchain/bin:$PATH"
export CARGO_TARGET_DIR="$G/target"
export RUSTFLAGS="-C linker=$G/bin/cc-glibc"
export CC="$G/bin/cc-glibc" CXX="$G/bin/cc-glibc" AR="$PREFIX/glibc/bin/ar"
export CARGO_HTTP_CAINFO="$PREFIX/etc/tls/cert.pem"   # glibc 版 cargo 不认 Android 的 CA 路径
export ULL_MSA_CLIENT_ID=<id>          # 可选;烘微软登录 id,值不进 git

# crates 走 USTC 稀疏索引(官方源在部分网络下只有 ~20 KB/s)
cargo --config 'source.crates-io.replace-with="ustc"' \
      --config 'source.ustc.registry="sparse+https://mirrors.ustc.edu.cn/crates.io-index/"' \
      build --release -p unlucky-app-iced --no-default-features

--no-default-features 关掉 gpu,只编 tiny-skia(CPU)。产物:.gnu/target/release/unlucky-launcher-iced。

4. 运行(termux-x11)

# 一次性:glibc 版 X 运行库(cf 源慢时可把 sources.list 里的 packages-cf 换成 packages.termux.dev)
pkg install libx11-glibc libxkbcommon-glibc fontconfig-glibc \
            libxcursor-glibc libxrandr-glibc libxi-glibc libxext-glibc \
            libxcb-glibc libwayland-glibc

termux-x11 :0 -ac &          # 配合已安装的 termux-x11 App 使用
DISPLAY=:0 .gnu/target/release/unlucky-launcher-iced --software-renderer

无头取证(CI 同款通道):

DISPLAY=:0 .gnu/target/release/unlucky-launcher-iced \
  --software-renderer --screenshot /tmp/ull-termux.png --data-dir /tmp/ull-termux-data
  • 中文:GUI 在 Termux 上会把 /system/fonts/NotoSansCJK-Regular.ttc 注册进 cosmic-text (fontdb 不扫 Android 的字体目录),无需额外环境变量。
  • 启动日志里 [auth] msa client_id=configured source=build 表示构建期 id 已烘入。

5. GPU(未在 glibc 侧验证)

termux-x11 的 X 客户端可以走硬件:本机 bionic 栈实测 glxinfo -B → zink Vulkan 1.4 (Adreno 840 (MESA_TURNIP))、Accelerated: yes, vulkaninfo 中 Adreno 设备带 VK_KHR_xcb_surface / VK_KHR_xlib_surface。

要用到 ULL 的 glibc 构建上,还需要 glibc 侧的 Vulkan 栈 (vulkan-icd-loader-glibc、mesa-vulkan-icd-freedreno-glibc,可加 swrast-glibc 兜底), 并用默认 feature(带 gpu)重编:

cargo ... build --release -p unlucky-app-iced     # 不加 --no-default-features
VK_ICD_FILENAMES="$PREFIX/glibc/share/vulkan/icd.d/freedreno_icd.aarch64.json" \
  DISPLAY=:0 .gnu/target/release/unlucky-launcher-iced --renderer=gpu

这一步尚未在本仓库验证;没配好时用 --software-renderer 回退即可。

6. 已知边界

  • 没有面向 Termux 的发布资产,也不在 CI 矩阵里;每次都要源码自建。
  • 只在 CPU 渲染 + 单台设备(Adreno 840 / Android 17)上验证过。
  • 装好 JDK ≠ 能跑 MC:MC 的 LWJGL natives 是 glibc .so,bionic 的 Termux JVM 加载不了; 这条与「启动器 GUI 能显示」是两件事,未在本文覆盖。
  • ULL_MSA_CLIENT_ID 属于仓库 Secret,构建时从环境/Secret 注入,不写进任何 git 文件。