Files
Unlucky-Launcher/packaging/docs/termux-glibc-build.md
T
AskaEth bbb316ca08 docs(termux): GPU 结论落地——glibc 侧被驱动版本挡住
实测结论:
- glibc Vulkan loader + 自写 ICD 能加载
- Termux glibc Mesa 24.2.6 不认识 Adreno 840(chip 0x44050A31 unsupported)
- Arch vulkan-freedreno 26.2.6 能加载但 Linux/DRM 构建找不到设备(缺 KGSL,
  /dev/dri 对 untrusted_app 被 SELinux 拒绝)
- GPU 变体会打印具名错误并自动降级 tiny-skia

README.termux 同步:GPU 标 ⛔、MC 走 glibc JVM 的结论与后续路线
2026-11-07 09:33:21 +08:00

211 lines
9.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 运行时](https://packages.termux.dev/apt/termux-glibc/) 原生执行。
## 2. 工具链(一次性)
以下路径用变量表示,实际可放在任意本地目录(示例放在仓库内的 `.gnu/`,已 gitignore):
```sh
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`,
以及跑门禁需要的 `clippy` / `rustfmt` 五个 `aarch64-unknown-linux-gnu` 组件。官方源在国内很慢,
用 USTC 镜像:
```sh
mkdir -p "$TMPDIR/ull-gnu" && cd "$TMPDIR/ull-gnu"
for c in rustc rust-std cargo clippy rustfmt; 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 上不存在:
```sh
LD="$PREFIX/glibc/lib/ld-linux-aarch64.so.1"
for f in rustc cargo rustdoc clippy-driver cargo-clippy rustfmt cargo-fmt; do
patchelf --set-interpreter "$LD" --set-rpath "$PREFIX/glibc/lib:$G/toolchain/lib" \
"$G/toolchain/bin/$f"
done
```
> **坑(实测)**:`cargo clippy` 会在 PATH 里找 `cargo-clippy`。如果只装了 Termux 的 Rust,
> 找到的是 android 版 `cargo-clippy`,`aws-lc-sys` 等构建脚本会被当成
> `--target=aarch64-linux-android` 编译并失败。把 gnu 工具链的 `bin` 放在 PATH 最前面即可;
> `rustc`/`rustfmt` 同理。
### 2.4 gcc 运行时片段
`clang -fuse-ld=lld` 仍然需要 `crtbeginS.o` / `crtendS.o` / `libgcc.a`。从 Debian 的
`libgcc-14-dev`(arm64)取即可(只解包,不安装):
```sh
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`):
```sh
#!/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. 构建
```sh
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`。
门禁(与 CI 同一组命令,环境同上):
```sh
cargo fmt --all --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace --all-targets --no-fail-fast
```
Termux 上有三条**环境差异**失败(不是回归):`launch` 测试读
`/proc/<pid>/task/<pid>/children`(Android 内核没有)、natives 事件的长路径被脱敏成
`<opaque>`、`settings_bridge` 的 mtime 热重载偶发(flaky)。PC 的 Linux CI 不受影响。
## 4. 运行(termux-x11)
```sh
# 一次性: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 同款通道):
```sh
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 已烘入。
- **横屏优先**:Termux 上默认窗口尺寸会按当前 X 屏幕等比自适应(日志
`[window] ... source=termux-screen`),横屏铺满、竖屏也不溢出;验收尺寸 1180×760 仍是
默认基准,`ULL_WINDOW_SIZE=1520x900` 这类覆盖仍然优先。
### 4.1 Termux 上的 Java
`ull java install <major>` 在 Termux 上**默认先用系统包**(`openjdk-17/21/25`):
- 内置 SAU / TUNA / USTC / 官方四个 termux-main 镜像,按顺序尝试;
- `apt-get` 的 lists/cache 隔离在 `$PREFIX/tmp/ull-java-termux/`,不改用户的 apt 配置;
装完自动清理该目录(一次安装约 100 MB 临时占用);
- 装完从 `$PREFIX/lib/jvm/java-<major>-openjdk` 发现,`ull java list` 直接可见;
- `--vendor termux` 只走系统包;`temurin|oracle|microsoft|zulu` 仍走归档下载链。
- 已实测:`ull java install 17` 从 SAU 镜像装完 `openjdk-17`(含 `openjdk-17-x`),
约 20 秒;`ull java list` 随后列出 `[system]` 的 17。
## 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`。
glibc 侧已把链路搭好并实测到具体阻塞点:
1. **loader 正常**:`vulkan-icd-loader-glibc` + 自写 ICD JSON 能加载 ICD。
2. **Termux 的 glibc Mesa 太旧**:`mesa-vulkan-icd-freedreno-glibc` 是 24.2.6(2024),
`vulkaninfo` 报 `chip_id = 44050A31, gpu_id = 6860 is unsupported`
(Adreno 840 不在该版本的支持表里)。
3. **Arch 的新驱动也走不通**:`vulkan-freedreno 26.2.6`(Arch Linux ARM)能加载、
但它是 Linux/DRM 构建,找不到设备——Android 的 GPU 走 KGSL,而
`/dev/dri` 对 `untrusted_app` 被 SELinux 拒绝(`ls /dev/dri` → Permission denied,
设备节点本身是 `crw-rw-rw-`)。
结论:glibc 侧要用硬件 GPU,需要一个 **Mesa 26+ 的 glibc 构建且带 Android/KGSL 后端**;
现有包都缺一半。在那之前用 CPU(tiny-skia)。
GPU 变体仍可构建并验证降级链:
```sh
cargo ... build --release -p unlucky-app-iced # 默认 feature,带 wgpu
VK_ICD_FILENAMES="$PREFIX/glibc/share/vulkan/icd.d/freedreno_icd.aarch64.json" \
VK_DRIVER_FILES="$VK_ICD_FILENAMES" \
DISPLAY=:0 .gnu/target/release/unlucky-launcher-iced --renderer=gpu
```
没有可用驱动时它会打印具名错误并**自动降级**到 tiny-skia(同一进程不能重建事件循环,
降级走 respawn),所以 GPU 变体在只有 CPU 的环境里也能跑。
## 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 文件**。