实测结论:
- 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 的结论与后续路线
211 lines
9.7 KiB
Markdown
211 lines
9.7 KiB
Markdown
# 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 文件**。
|