# xlings 2026.8.8.1 — 发布说明

**日期**: 2026-08-08
**类型**: 发布记录 (release notes)
**关联**: `2026-08-08-host-loader-migration-and-ecosystem-guards.md`、`xim-pkgindex#546`

---

## 一句话

**`remove` 不再默默切断别人的依赖,`doctor` 会真的去解析一次用户名 —— 以及一个把
"切 loader"整套判据推翻的测量。**

---

## 这一轮真正的发现

计划里写着:两个 JDK 今天就能切到我们的 loader,因为满足不了的六个库都是 AWT、
声音、启动画面 `dlopen` 的,"降级程度和今天一样"。

**错的。**我们 glibc 的 `ld.so` 里编进去的 cache 路径是构建机的
`/home/xlings/.xlings_data/.../etc/ld.so.cache`,**任何机器上都不存在**。
multiarch 发行版上宿主库几乎全在 `/usr/lib/<triple>`,**只能**通过这个 cache 找到。
所以 `PT_INTERP` 一旦指向我们,**宿主库整体不可达**。

带对照组实测(同机,`DISPLAY=:1`):

| jdk-temurin 25.0.4+7 | `Toolkit.getDefaultToolkit()` |
|---|---|
| 未打补丁 | ok, 2560x1440 |
| 切到我们的 loader | `UnsatisfiedLinkError: libawt_xawt.so: libX11.so.6` |

无头侧完美:`java`、`javac`、libzip、libnet、`InetAddress.getByName`、NSS、UTF-8
全过 —— **正因为如此,冒烟测试会放它过去**。

所以判据要改成:**切 loader 之前,闭包必须覆盖它可能加载的每一个库,`dlopen` 的也算。**
两个 JDK 因此**故意不切**,卡在 `libXtst` 和 `libasound` 两个包上。

---

## 本体改动

### `remove` 的反向依赖拒绝

`xlings remove libxml2` 在 llvm 依赖它时会成功,然后 `ld.lld` 起不来。错误信息只报
soname,读起来像 llvm 坏了 —— 重装 llvm 还修不好,因为 llvm 从来就不是变化的那个。

现在拒绝并列出依赖方,退出码 **2**;`--force` 覆盖。

`--force` 和 `-y` **是两个开关**:`-y` 说的是"别问我",那是关于**提示**的;
脚本里的 `remove -y` 仍然应该被这条检查拦住。install hook 调用
`pkgmanager.remove` 时传 `force=true` —— recipe 换 provider 时已经自己想过了。

只看**直接**依赖。这不是近似:依赖方的 libdirs 只有被直接声明才会进入闭包
(`closure_lib_paths` 读的是直接列表),隔一跳的包根本没把这个载荷放进任何搜索路径。

> **第一版有个 bug,值得记下来。**它把整个检查挂在"catalog 解析成功"上。
> 而 `glibc` 在任何注册过 `local:` recipe 的 home 里都是 ambiguous —— 于是检查
> **恰好在最需要它的 home 里跳过**,而且是**静默跳过**,输出和干净通过一模一样。
> 是跑出来的,不是看出来的:第一次测试打印的是普通确认提示。

### `doctor` 的 NSS 格

glibc 的名字服务后端是 `dlopen` 出来的,且必须与调用方同源。所以切到我们 loader 的
载荷会改用**我们**的后端(compat/db/dns/files/hesiod);宿主若配了 systemd、Avahi
或 NIS 的后端,那些模块我们没有。失败是静默的:NSS 回答"查无此人",和真的没这个人
一模一样。

这一格**真的去跑** glibc 载荷自带的 `bin/getent`,用它自己的 loader。
在进程内做是显然的捷径,而且是错的 —— 本进程有自己的 libc(静态 musl 构建根本没有
NSS),那样回答的是关于 xlings 的问题,不是关于其它包即将依赖的那个载荷的问题。

通过是 Notice,失败是 Error,**只有 Error 计入退出码**:一个在健康 home 上喊狼来了
的格子,第二周就会被当成噪音。

---

## 索引侧(xim-pkgindex#546)

`llvm-tools` 切到我们的 loader —— 但需要的是 `glibc + gcc-runtime + zlib` 三个,
不是计划里写的 glibc 一个。原来的"0 个不满足"是拿"home 里有"算的,不是"已声明"。

四道守卫:声明 vs 真实 `DT_NEEDED` 差分、裸依赖名、构建**输入**侧宿主泄漏、
三态退出码规范。构建输入检查当场抓出第三个实例:`--deps` 的 .pc 改写只补了
`prefix=`,`libdir=/usr/lib` 留在 40 个文件里。

---

## 验证

全新隔离 home 真装:python 3.13.12 INTERP 已切、ssl 3.5.5、NSS 通;
llvm-tools 的 RPATH 恰好等于声明的闭包;沙箱内 clangd/clang-format/clang-tidy 全跑通。

> 沙箱那一步**差点变成假通过**:第一次输出 `py ok speak OpenSSL 3.0.13`,看着是绿的,
> 跑的却是宿主的 python。断言"跑起来了"会过,断言"跑的是谁"才不会 —— 靠 OpenSSL
> 版本对不上认出来的。根因是 `xlings#500`。

## 顺带提的两个 issue

- `xlings#500` —— 在没有 subos 的 home 里 install,会副作用地造出半个 subos:
  `list` 读目录看得见,`use` 读注册表说不存在。
  (原文还报了"`use` 报成功但不写 shim",**那条是错的,已更正**:真实 home 里
  `use` 写得好好的,我撞上的是嵌套 home 的 shim 归属拒绝 —— 而 xlings 当时就
  打了警告说明这一点。沙箱那次假通过也是这个原因,不是 `use` 坏了。)
- `configure-project-installer` 的 macOS 段 `ref = "linux"` 连 deps 一起继承,
  而 `make`/`gcc` 没有 macosx 段。裸名字解析不到被容忍,所以这条声明一直是**空的**
  而不是对的;加前缀让它第一次可见。**不是回归,是守卫起作用了。**
