Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
259 changes: 259 additions & 0 deletions .agents/docs/2026-08-30-gbm-cross-repo-closed-loop-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ Date: 2026-08-30 · 起因:`compat.libgbm`(mcpp-index PR #281)· 状态:待 revi

| 想知道 | 看哪节 | 注意 |
|---|---|---|
| **最新一轮(EGL 源码化 + 模块命名)** | **§19** | 最权威;§19.6 记了一处自己造成的破坏 |
| **沙箱干净房间验证** | **§19.4** | 宿主库在场且可达却全部落败 |
| 模块该叫什么名字 | §19.2 | 跟接口的所有者,不跟发实现的人 |
| 生成器放 build.mcpp 还是签进仓 | §19.3 | 判据是「依不依赖目标平台」 |
| xlings 该 pin 哪个版本 | §15 + §19.5 | §15 的结论仍成立,§19.5 补了它指出的真缺口 |
| **最终结论与交付** | §12(实现结果)、§13(状态)、§14(要不要做 mesa 包) | 权威 |
| 为什么 `compat.libgbm` 该独立存在 | §3、§10.1 | 理由换过三次,结论没变 |
| 任务拆分与依赖 | §11 | |
Expand Down Expand Up @@ -1113,3 +1118,257 @@ backends dir: <registry>/subos/default/usr/lib/gbm ← subos 给的,不是
`index.toml` 的 `min_mcpp = 2026.8.3.3` 是**假的**,而且是本包证明的 —— 详见提交
`feat(libgbm): the package sheds its workaround; index floor corrected`。
已上调到 `2026.8.27.2`,同时把「floor 与 CI pin 一起动」这条早已漂移的不变量恢复了。

---

## 19. 第四轮:EGL 从绑定改为源码构建,并统一模块命名(2026-08-30 下午)

§18 之后栈里还剩一处不自洽:`compat.egl` 是绑定,而它**自己的注释**写着这是错的 ——
「libglvnd **is** a separable project,按判据本该源码构建,现在仍是绑定纯粹是工作量问题」。
判据里没有「工作量」这一项。这一节把这笔账还上,并顺带解决了模块命名的归属问题。

### 19.1 交付物

| 仓 | 变更 | 状态 |
|---|---|---|
| **mcpplibs/libglvnd**(新建) | libglvnd v1.7.0 + mcpp 构建支持,出 `libEGL.so.1` 与 `libGLdispatch.so.0` | `v1.7.0`,CI 四 job 全绿 |
| **mcpplibs/wayland** | 三个模块重命名;CI 钉 xlings | `v1.26.0` 重切,CI 全绿 |
| **mcpp-index** | `compat.egl` → `freedesktop.egl`;5 个描述符 sha256;测试成员;文档 | PR #293 |
| **mcpp-community/mcpp** | 示例 09 改用 `freedesktop.egl`,模块改名,打印两个发现变量,闭包实测重新测量 | PR #532,已推 |
| mcpp-res(gitcode) | wayland / libglvnd 两个 CN 镜像 | 已发布,与 GLOBAL 字节一致 |

### 19.2 模块命名:跟**接口的所有者**,不跟发实现的人

索引既有的惯例是「namespace 不进模块名」,这一点有反例可证:

| 包 | 模块 |
|---|---|
| `chriskohlhoff.asio` | `import asio` |
| `fmtlib.fmt` | `import fmt` |
| `boost-ext.ut` | `import boost.ut` |
| `opencv.opencv` | `import opencv.cv` |

这一轮把它推进一步:模块名不是随便挑的,而是**拥有那份接口的组织**。于是

```
wayland.client → freedesktop.wayland.client freedesktop 确实拥有 wayland 协议
wayland.server → freedesktop.wayland.server
wayland.util → freedesktop.wayland.util
egl → khronos.egl EGL 是 Khronos 规范
```

**注意 EGL 的包名与模块名故意不一致**:包是 `freedesktop.egl`(freedesktop 确实在发
这份代码),模块是 `khronos.egl`(Khronos 拥有这份规范,libglvnd 只是实现之一)。

依据不是审美,是 `mcpplibs.openkal` 已经付过的学费 —— 它的 0.1.0 是**被撤回**而不是保留的:

> It placed the module a consumer imports **under the control of the
> implementation**, which contradicts what the specification is for

模块名是全局且长期的,包名不是。换实现不该逼消费者改 `import`,否则一个「除了 include
那一行什么都不改」的包装层就失去了意义。副作用是好的:两个 EGL 提供方现在会在模块名上
**硬冲突**,而不是静默共存 —— 这正是 GLVND「一个进程一个 dispatch 点」想要的。

### 19.3 build.mcpp 与「签进仓的生成物」的分界

这一轮两次遇到「生成器该放哪」,答案不一样,规则是同一条:

> **build.mcpp 管的是「依赖目标平台、算不出来」的决定;能预先算定的生成物就签进仓 + CI diff。**

| 生成物 | 依赖目标? | 放哪 |
|---|---|---|
| libglvnd 的 dispatch 表(~1000 行 Python × 2.7MB gl.xml) | 否 | `mcpp/generated/`,CI 重生成并 diff |
| wayland 的协议代码 + 模块包装(`genmod.py`) | 否 | `mcpp/generated/`,CI 重生成并 diff |
| GLdispatch 的 **entry stub 选择** | **是**(架构 × 线程存储模型) | `build.mcpp`,用 `mcpp::target_arch()` |

两个 fork 的**构建路径上都没有 Python**(`mcpp.toml` / `build.mcpp` / 根清单都不提它),
消费者只需要 mcpp。把 `genmod.py` 改写成 build.mcpp 反而会**把生成器塞进每个消费者的
构建**,并且会撞上 **mcpp#534**(build.mcpp 的 action 产物与本包编译之间没有 order-only 边)
—— wayland 的协议代码当初签进仓正是因为这个。

GLdispatch 那条则相反:entry stub 分架构**且**分线程存储模型,而且和 libffi 的不同,
**它们自身没有任何门控**。在 `sources` 里写死 x86_64 会让包只能在 x86_64 上用、而且哪儿
都不写明;`build.mcpp` 用 `mcpp::target_arch()` 做的正是上游 `gl_dispatch_type` 的选择。

### 19.4 生态真实验证:沙箱干净房间(`--sandbox --gpu`)—— 已完成并记录

§12.3 验证过的是 **GBM**。这一节验证的是**整条链**,包含这一轮新增的源码构建 EGL 与
重命名后的模块,而且是**从零安装 + 从零构建**。

**脚本已签进仓**:[`tests/verify_graphics_closed_loop_sandbox.sh`](../../tests/verify_graphics_closed_loop_sandbox.sh)

复现:

```bash
# 一个装了图形栈的 subos,例如 xlings install xim:mesa@25.0.7.2
cp tests/verify_graphics_closed_loop_sandbox.sh ~/.xlings/subos/<subos>/verify.sh
xlings subos use <subos> --sandbox --gpu \
--cmd "BRANCH=<ref> sh /home/speak/.xlings/subos/<subos>/verify.sh"
```

拷贝那一步不是多余的:**沙箱不挂当前工作目录**,放在别处的脚本在里面根本看不到。

#### 为什么这个沙箱有说服力

关键不是「隔离得干净」,而是**沙箱里 `/usr` 仍然是宿主的**。所以要证的**不是**「宿主
不在」—— 一个把 `/usr` 藏起来的沙箱对用户的机器什么也证明不了 —— 而是「宿主在、可达、
且依然全部落败」。后者才是真实机器上会发生的情形。

#### 完整输出(2026-08-30,`eco-gbm-20260830`,分支 `feat/freedesktop-egl` @ 2f4458d)

```
===== 0. where we are =====
home contents: . .. .bashrc closed-loop .config .mcpp mcpp-index .profile .xlings .zshrc
host has /usr/lib/x86_64-linux-gnu/libEGL.so.1
host has /usr/lib/x86_64-linux-gnu/libgbm.so.1
host has /usr/lib/x86_64-linux-gnu/libdrm.so.2
host has /usr/lib/x86_64-linux-gnu/libwayland-client.so.0
GBM_BACKENDS_PATH = <subos>/usr/lib/gbm
__EGL_VENDOR_LIBRARY_DIRS = <subos>/share/glvnd/egl_vendor.d

===== 1. install mcpp through xlings =====
mcpp: <subos>/bin/mcpp
mcpp 2026.8.29.1

===== 2. clone the index under test =====
branch feat/freedesktop-egl @ 2f4458d

===== 3. a project that names the whole stack =====

===== 4. build from scratch =====
build.mcpp compiling
build.mcpp running
Target x86_64-linux-gnu → x86_64-unknown-linux-gnu
Inferred sources [src/**/*.{cppm,cpp,cc,c,S,s,asm}]
Inferred target closed-loop (bin from src/main.cpp)
Compiling closed-loop v0.1.0 (.)
Cached compat.libdrm v2.4.134 (5 units)
Cached compat.libgbm v25.0.7 (1 unit)
Compiling freedesktop.egl v1.7.0
Cached freedesktop.wayland v1.26.0 (1 unit)
Cached freedesktop.wayland-server v1.26.0 (1 unit)
Finished dev [unoptimized + debuginfo] in 0.31s

===== 5. what the loader actually resolved =====
libgbm.so.1 => <registry>/xpkgs/compat-x-libgbm/25.0.7/mcpp_generated/libgbm/lib/libgbm.so.1
libdrm.so.2 => <project>/target/x86_64-linux-gnu/78e15402517d6f84/bin/libdrm.so.2
libEGL.so.1 => <project>/target/x86_64-linux-gnu/78e15402517d6f84/bin/libEGL.so.1
libwayland-client.so.0 => <project>/target/x86_64-linux-gnu/78e15402517d6f84/bin/libwayland-client.so.0
libwayland-server.so.0 => <project>/target/x86_64-linux-gnu/78e15402517d6f84/bin/libwayland-server.so.0
libexpat.so.1 => <registry>/xpkgs/xim-x-expat/2.6.2/lib/libexpat.so.1
libGLdispatch.so.0 => <project>/target/x86_64-linux-gnu/78e15402517d6f84/bin/libGLdispatch.so.0
libffi.so.8 => <project>/target/x86_64-linux-gnu/78e15402517d6f84/bin/libffi.so.8

===== 6. did anything come from the host? =====
PASS: the host's copies were present and reachable, and none of them won

===== 7. run it =====
KMS: DRM_IOCTL_MODE_CREATE_DUMB failed: Permission denied
kmsro: driver missing
-- the modules carry the API --
EGL_VERSION 1.5 libglvnd
wl_display_create 0x2bd0dbf0
-- DRM node -> GBM device -> EGL display --
/dev/dri/renderD128
drm driver nvidia-drm
gbm_create_device 0x2bd65e60
gbm_bo_create (driver declined)
eglInitialize EGL 1.5, vendor Mesa Project
/dev/dri/card0
drm driver simpledrm
gbm_create_device 0x2bd65e60
gbm_bo_create 256x256 stride=1024
eglInitialize EGL 1.5, vendor Mesa Project

reached EGL on a real device: yes

===== RESULT =====
PASS
```

#### 这份输出证明了什么

| 断言 | 证据 |
|---|---|
| 模块层在干净构建里成立 | `import khronos.egl;` + 两个 `import freedesktop.wayland.*;` 编译链接通过 |
| 加载的是**自建**的 EGL 而非 payload | `libEGL.so.1 => <project>/target/…`,且 `EGL_VERSION` 由它答出 |
| 自建 dispatch 真能加载 vendor | `eglInitialize → EGL 1.5, vendor Mesa Project` |
| GBM 真分配了显存 | `gbm_bo_create 256x256 stride=1024`(card0) |
| 宿主一条都没赢 | 第 6 步:四个宿主库在场且可达,闭包里零条来自 `/usr/lib` |
| 发现路径来自生态而非包 | 两个变量都由 `xim:mesa` 声明,包自己什么都不设 |

两条**不是**缺陷的输出:`renderD128` 上 `gbm_bo_create` 被驱动拒绝,是 NVIDIA 后端不接受
该 format/usage 组合;`DRM_IOCTL_MODE_CREATE_DUMB failed: Permission denied` 是 render node
的权限边界(dumb buffer 需 KMS 权限)。`card0` 上两者都成功。

#### 三个坑(留给下一个人)

1. **`[indices]` 同一个 path 不能挂两个键**。写 `compat` 和 `freedesktop` 都指向同一个
checkout,会注册成两个独立 project repo,之后每次查找都报
`package 'compat:libdrm@2.4.134' is ambiguous, candidates: … 'compat' … 'freedesktop'`。
只重定向被测的那个 namespace,其余走已发布索引。
2. **沙箱里 `find ~/.xlings -name mcpp` 会抓到别的 subos 的二进制**,它们的 interpreter
不在,失败信息是干巴巴的 `not found`,读起来像「mcpp 没装上」。要先试本 subos 的 `bin/`。
3. **沙箱不挂 cwd**,落点是合成的 `/home/speak`(只有 dotfile)。脚本必须拷进 subos 目录。

### 19.5 xlings pin:§15 的结论仍然成立,但它指出的缺口是真的

任务里再次出现「pin 内部依赖的 xlings 到 `2026.8.27.4`」。**§15 已经查过并否掉了**:
`.4` 比 `.5` **旧**三小时,三仓的 `kXlingsVersion` 已经都在 `.5`,而 `.5` 有 `.4` 没有的
行为(声明在解析时压过索引)。下调等于让生态退回一个能力更弱的版本。

但这条要求指出的**缺口是真的,只是位置不对**:两个 fork 的 CI **根本没钉**,
`curl … | bash` 拿的是当天最新。已改为钉 `XLINGS_VERSION: "2026.8.27.5"`,与
`kXlingsVersion` 一致。

**mcpp 本身仍不钉**,这是刻意的:这些包依赖的正是**生态**(`xim:mesa` 声明
`GBM_BACKENDS_PATH` / `__EGL_VENDOR_LIBRARY_DIRS`),而钉死的 mcpp tarball 带的是生态的
冻结快照。**钉住装的工具,放开被测的生态** —— 这个切分才让失败可归因。

### 19.6 一个自己造成的破坏,记下来

为了承载模块重命名,`mcpplibs/wayland` 的 `v1.26.0` 被**原地重切**(上游没有更新版本
可跟,而版本号必须与上游对齐)。后果是立即的:

```
main 上 freedesktop.wayland.lua 的 sha256 0a5dd54a…
tag 上 tarball 的实际 sha256 961a900d… ← 不匹配
```

在 PR #293 合并之前,`freedesktop.wayland@1.26.0` 从 main 装不下来。**正确的次序应当是
先改描述符、合并,再重切 tag**,而不是反过来。

还有一个更隐蔽的:store 只按 `(name, version)` 索引,所以**已经装过 1.26.0 的机器不会
重新下载**,会继续用旧模块名而毫无提示 —— 这正是
`stale-global-index-masks-descriptor-bugs` 那条。开发机上需要手动清:

```bash
rm -rf ~/.mcpp/registry/data/xpkgs/freedesktop-x-wayland*/1.26.0 \
~/.mcpp/registry/data/xpkgs/freedesktop-x-egl/1.7.0 \
~/.mcpp/build-cache/v1/pkg/freedesktop ~/.mcpp/build-cache/v1/tool/freedesktop
```

### 19.7 多角度审视

| 角度 | 这一轮的结果 |
|---|---|
| **架构** | 五个包里只剩 GBM 一个绑定,而它是唯一真正过不了「可否独立分发」的。判据终于和实现一致 |
| **稳定性** | CI 新增「符号内容 + obj 计数」——SONAME 检查对**空库**也会绿。两个 fork 都钉了 xlings |
| **优雅/简洁** | 一个索引条目出两个库(`libGLdispatch` 走 path 依赖),消费者只写一行 |
| **用户体验** | `import khronos.egl;` 换实现不用改代码;`EGL_*` 宏仍需 include,这一点写进了描述符 |
| **兼容性** | 模块重命名是**破坏性**的。选在 wayland 合并当天做,消费者只有示例 09 |
| **跨平台** | 按架构选 stub 移进 `build.mcpp`,aarch64/ppc64 由构造成立而非靠改代码 |
| **一致性** | 模块命名规则统一为「接口所有者」,与 openkal 的教训对齐 |
| **无感升级** | ⚠ **没做到**,见 §19.6:同版本重切 tag 会被 store 掩盖。这是本轮唯一的真缺陷 |

### 19.8 仍未闭合

- **PR #293 必须合并**才能修好 main 上 wayland 的 sha256(§19.6)。这是唯一真正阻塞的一条。
- 示例 09 的更新已推到 mcpp PR #532。曾以为「推上去会把已绿的 CI 弄红」而扣着不推,
**那是假设、没查**:mcpp 的 CI 根本不构建 `examples/09-graphics-stack`(只有
`openkal-cross.yml` 碰一个无关示例)。扣着不推让工作停在本地,比推上去更糟。
- `libGL` / `libGLX` / `libGLESv{1,2}` 未构建。各自是 fork 里一个成员加一张
`mcpp/generated/` 里的 dispatch 表;索引里目前没有消费者。
- `freedesktop.egl` 的 `libEGL.so.1` 比 payload 的多三条 `DT_NEEDED`
(libstdc++/libm/libgcc_s),因为模块接口单元被编译进库里。`freedesktop.wayland` 完全
同形,是「模块层随库一起发」的既定结果,已写进描述符。
Loading
Loading