Skip to content

Commit 90a4a84

Browse files
committed
docs(libgbm): the shipped docs still described the deleted design
Review pass 2. Three documents that ship with this PR still described the constructor, the lib/gbm farm and mcpp_gbm.h as though they existed: * docs/descriptor-examples.md and its zh counterpart -- the catalog row is the first thing a reader opens when looking for this shape, and it described the workaround as the design. Rewritten around what the package actually is: it sets nothing, and the backend path comes from xim:mesa's declaration. The historical note stays in one clause, because "it briefly did carry a constructor, and deleting it took 598 lines to 303" is the useful part to remember. * .agents/docs/2026-08-29-add-libgbm-plan.md -- the first-round design record, whose central section is titled "the part that is actual work" and is about machinery that no longer exists. Banner at the top rather than a rewrite: the shape decision, the zero-host rule, the two-directory-key finding and the test design all still hold, and the reasoning that was overturned is worth keeping next to what overturned it. Nothing in the descriptor or the tests changed here; this is the documentation catching up with the code.
1 parent 62b2419 commit 90a4a84

3 files changed

Lines changed: 24 additions & 2 deletions

File tree

.agents/docs/2026-08-29-add-libgbm-plan.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,27 @@
11
# Adding `compat.libgbm` — GBM bound to the ecosystem's Mesa
22

3+
> ## ⚠ SUPERSEDED IN PART — read this first
4+
>
5+
> This is the FIRST-ROUND design record, and its central mechanism no longer
6+
> exists. It describes a constructor in a generated TU that set
7+
> `GBM_BACKENDS_PATH`, a `lib/gbm/` backend farm, and an `mcpp_gbm.h`. **All
8+
> three are gone.**
9+
>
10+
> Setting that variable was always Mesa's own mechanism and the ENVIRONMENT's
11+
> job. `xim:mesa` now declares it through the graphics discovery layer
12+
> (openxlings/xim-pkgindex#713), so the package sets nothing, generates no TU
13+
> and ships no header of its own — 598 lines down to 303. What this document
14+
> calls "the part that is actual work" turned out to be a workaround for a
15+
> missing declaration one layer down.
16+
>
17+
> What still holds: the shape decision (a binding, not a source build) and the
18+
> evidence behind it; the zero-host rule; the two-directory-key finding; and
19+
> the test design. For the current state and the three rounds of correction
20+
> that produced it, see
21+
> [2026-08-30-gbm-cross-repo-closed-loop-plan.md](2026-08-30-gbm-cross-repo-closed-loop-plan.md)
22+
> — especially §12.1, §16 and §17.
23+
24+
325
Date: 2026-08-29 · Package: `compat.libgbm@2026.08.29` · Member: `tests/examples/libgbm`
426

527
## What GBM is, and what had to be decided

docs/descriptor-examples.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ in the [root README](../README.md#reference-examples).
3131
| C++ application framework compat (dependencies reuse packages already in the index) | [`compat.eui-neo`](../pkgs/e/compat.eui-neo.lua) (upstream's `3rd/` ships 8 vendored dependencies; none of them is compiled here — all are redirected to the same-version `compat.*` packages in this index) |
3232
| Mutually exclusive backends (one of several inside one package) | [`compat.eui-neo`](../pkgs/e/compat.eui-neo.lua): `vulkan` / `sdl2` each **replace** the default OpenGL / GLFW, and the default backend is expressed by *naming no feature at all* — there is no `opengl`/`glfw` feature. A `default` feature cannot express exclusivity: its own `defines`/`sources`/`deps` have no effect whatsoever, while its `implies` always applies and cannot be overridden by a named feature (which is, conversely, exactly the solution for the "always-on interface define" row below). The workable answer is to read the `-DMCPP_FEATURE_<NAME>` mcpp passes anyway and decide up front in a force-included header. Note also that `cflags` only reaches C TUs — C++ needs `cxxflags`, so a backend define written only into `cflags` never reaches any `.cpp` |
3333
| Host runtime adaptation (drivers are not vendored) | [`compat.glx-runtime`](../pkgs/c/compat.glx-runtime.lua) · [`compat.vulkan-runtime`](../pkgs/c/compat.vulkan-runtime.lua) (mcpp binaries run against a bundled glibc, so a bare-soname `dlopen` never reaches the host drivers; a symlink farm plus `runtime.library_dirs` bridges that. The farm holds only versioned sonames, so nothing there can shadow an index package. Note that `runtime.library_dirs` renders as `-Wl,-rpath` and **not** as `-L` — the `-L` key is `runtime.link_library_dirs`, which these two do not need because nothing links against their farms; see the row below for one that does) |
34-
| Ecosystem-stack binding (zero host) | [`compat.libgbm`](../pkgs/c/compat.libgbm.lua) (Mesa's GBM — buffer allocation out of a DRM device. The row above reaches the HOST; this one reaches the ECOSYSTEM and nothing else, and the distinction is the whole design. **Why not a source build**: libgbm is a build target *inside* Mesa, not a project — `src/gbm/meson.build` is `link_with: [libloader]`, and `libloader` wants `idep_mesautil`, the whole of Mesa's internal util library (~120 TUs plus Python-generated tables) for exactly one function, `loader_open_driver_lib`. Building it would make this index re-import libdrm + expat + xcb + a Mesa-util carve-out to duplicate what `xim:mesa` has already resolved hermetically. Contrast `compat.vulkan`, which *does* build the Khronos loader from source — Khronos releases that as a standalone project, Mesa releases no such thing for GBM. **Zero host, with no escape hatch**: unlike its two neighbours it has no `/usr/lib*` path and no `MCPP_HOST_*` override, because host libgbm is a leak the ecosystem already closed — `xim:nvidia-gl-host-link` names it directly ("the table … was missing libm, libdrm, **libgbm**, libgcc_s … all of which were therefore coming from the HOST, silently, which is the leak this package exists to close"). NVIDIA's own GBM backend, if ever needed, belongs in that host-link layer rather than here. The measured surface is 1 ecosystem package (`xim:mesa`, not `xim:graphics`'s twenty-two), zero index deps, and zero transitive burden — libgbm.so.1's own RUNPATH resolves entirely inside `xim-x-*`. **The part that is real work**: libgbm is a loader, and Mesa compiles `/usr/lib/gbm` in as its backend search path, which does not exist in the sandbox, so `gbm_create_device` finds nothing and no link check notices. `xim:mesa` declares `lib` into the view but `lib/gbm/` is a subdirectory and does not follow. So install() also harvests the backends, laid out as a **sibling** of the farm's libgbm, and a generated TU wires `GBM_BACKENDS_PATH` from a **constructor** — `dlsym(RTLD_DEFAULT)` a gbm symbol, `dladdr` it, append `/gbm` — which reports the farm path rather than the realpath and therefore pins nothing (baking an absolute path into a generated header instead would fix the package to whichever mesa payload existed on install day). **The repair has to be invisible, and that is the load-bearing design decision**: the API stays stock `#include <gbm.h>`, because libgbm is mostly called from *inside* other libraries (SDL2's KMSDRM backend, wlroots, ffmpeg's VAAPI hwcontext) which will never call a helper of ours — an opt-in fix would leave exactly those consumers broken. It works because mcpp links a dependency's objects eagerly, so the constructor cannot be dropped. This is also what every other ecosystem does: distros split `libgbm1` out of the mesa source package and rely on the compiled-in `$libdir/gbm`, while relocated stacks set the environment variable (Valve's pressure-vessel hit this exact bug in steam-runtime#797; Nix and Conda do it at activation) and Mesa offers `-Dgbm-backends-path=` for packagers who control the build — never an API. Longer term this belongs in `xim:mesa` itself. This is also the package that needs **both** directory keys: `library_dirs` for the RUNPATH and `link_library_dirs` for the `-L` that lets `-lgbm` resolve at all. It ships **two** test binaries, and the split is the point: `stock_usage.cpp` includes stock `<gbm.h>` and nothing else, so a regression back to an opt-in fix fails there while the fuller `gbm.cpp` could still pass. Both assert the backend is *present at the derived path* rather than that it loads, which is checkable on a runner with no `/dev/dri` — and stays honest on a host where the stack's own mesa/glibc skew makes the dlopen fail) |
34+
| Ecosystem-stack binding (zero host) | [`compat.libgbm`](../pkgs/c/compat.libgbm.lua) (Mesa's GBM — buffer allocation out of a DRM device. The row above reaches the HOST; this one reaches the ECOSYSTEM and nothing else, and the distinction is the whole design. **Why not a source build**: libgbm is a build target *inside* Mesa, not a project — `src/gbm/meson.build` is `link_with: [libloader]`, and `libloader` wants `idep_mesautil`, the whole of Mesa's internal util library (~120 TUs plus Python-generated tables) for exactly one function, `loader_open_driver_lib`. Building it would make this index re-import libdrm + expat + xcb + a Mesa-util carve-out to duplicate what `xim:mesa` has already resolved hermetically. Contrast `compat.vulkan`, which *does* build the Khronos loader from source — Khronos releases that as a standalone project, Mesa releases no such thing for GBM. **Zero host, with no escape hatch**: unlike its two neighbours it has no `/usr/lib*` path and no `MCPP_HOST_*` override, because host libgbm is a leak the ecosystem already closed — `xim:nvidia-gl-host-link` names it directly ("the table … was missing libm, libdrm, **libgbm**, libgcc_s … all of which were therefore coming from the HOST, silently, which is the leak this package exists to close"). NVIDIA's own GBM backend, if ever needed, belongs in that host-link layer rather than here. The measured surface is 1 ecosystem package (`xim:mesa`, not `xim:graphics`'s twenty-two), zero index deps, and zero transitive burden — libgbm.so.1's own RUNPATH resolves entirely inside `xim-x-*`. **What it deliberately does NOT do**: set the backend search path. libgbm is a loader — `gbm_create_device()` dlopens `<path>/<driver>_gbm.so`, and Mesa's compiled-in `/usr/lib/gbm` is right on a distro and wrong the moment the payload is relocated. The mechanism to fix that is Mesa's own (`GBM_BACKENDS_PATH`) and the job belongs to the ENVIRONMENT, which is where every other relocated stack puts it — Valve's pressure-vessel answers the identical breakage with `GBM_BACKENDS_PATH=/run/host/usr/lib64/gbm` (steam-runtime#797), Nix and Conda set it at activation. In this ecosystem `xim:mesa` now declares it through the graphics discovery layer (openxlings/xim-pkgindex#713), so this package sets nothing, generates no TU and ships no header of its own. It briefly did carry a constructor that set the variable itself; that was a workaround for the missing declaration, and deleting it took the descriptor from 598 lines to 303. **Two directory keys, not one**: `library_dirs` renders as `-Wl,-rpath` and `link_library_dirs` as `-L`, so a package that is *linked against* (unlike glx-runtime/vulkan-runtime, whose farms are only dlopen'd) needs both — with `library_dirs` alone the farm is complete, the rpath correct, and the build still dies at `ld: cannot find -lgbm`. It ships **two** test binaries: `stock_usage.cpp` includes stock `<gbm.h>` and nothing else, which is the minimal consumer and the tripwire on the two things outside this repo the package now depends on — xim-pkgindex's DISCOVERY row and mcpp's subos-env injection)
3535
| Always-on interface define | `CURL_STATICLIB` in [`compat.curl`](../pkgs/c/compat.curl.lua): `cflags` is always on but package-private, while a feature's `defines` reaches consumers yet has to be named — `default = { implies = … }` applies unconditionally and happens to give both |
3636
| Multiple majors in one package (shape switches with the version) | [`compat.catch2`](../pkgs/c/compat.catch2.lua) (3.x compiles `src/catch2/` into a static library; 2.x goes header-only through `single_include/`) |
3737
| External build system (`install()` builds from source) | [`compat.openblas`](../pkgs/c/compat.openblas.lua) (Make) · [`compat.openssl`](../pkgs/c/compat.openssl.lua) (Perl Configure + Make, static libssl/libcrypto) |

docs/zh/descriptor-examples.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
| C++ 应用框架 compat(依赖复用索引内既有包) | [`compat.eui-neo`](../../pkgs/e/compat.eui-neo.lua)(上游 `3rd/` 自带 8 个 vendored 依赖,此处一个不编,全部改指索引内同版本 `compat.*`) |
3030
| 互斥后端(同包多后端二选一) | [`compat.eui-neo`](../../pkgs/e/compat.eui-neo.lua):`vulkan` / `sdl2` 各自**替换**默认的 OpenGL / GLFW,默认后端由"不点名任何 feature"表达,并不存在 `opengl`/`glfw` feature。`default` feature 表达不了互斥 —— 它自带的 `defines`/`sources`/`deps` 完全不生效,而 `implies` 又恒生效、无法被点名的 feature 覆盖(后者反而正好是本表『恒开的 interface define』一行的解法)。可行解是读 mcpp 本就会传的 `-DMCPP_FEATURE_<NAME>`,在强制包含头里做前置判定。另注意 `cflags` 只作用于 C TU,C++ 需 `cxxflags` —— 只写进 `cflags` 的后端 define 到不了任何 `.cpp` |
3131
| 宿主运行时适配(不 vendor 驱动) | [`compat.glx-runtime`](../../pkgs/c/compat.glx-runtime.lua) · [`compat.vulkan-runtime`](../../pkgs/c/compat.vulkan-runtime.lua)(mcpp 产物跑在自带 glibc 下,裸 soname 的 `dlopen` 够不到宿主驱动;用符号链接农场 + `runtime.library_dirs` 打通。farm 只放带版本号的 soname,so 里面的东西遮蔽不了索引包。注意 `runtime.library_dirs` 渲染成 `-Wl,-rpath`**不是** `-L` —— `-L` 那个键叫 `runtime.link_library_dirs`;这两个包不需要它,因为没人链接它们的 farm,需要的例子见下一行) |
32-
| 生态栈绑定(零 host) | [`compat.libgbm`](../../pkgs/c/compat.libgbm.lua)(Mesa 的 GBM —— 从 DRM 设备分配 buffer。上一行伸手够的是 **host**,这一行够的是**生态**、且仅止于此,这个区别就是整个设计。**为什么不源码构建**:libgbm 是 Mesa **内部**的一个 build target,不是一个项目 —— `src/gbm/meson.build` 写着 `link_with: [libloader]`,而 `libloader` 又要 `idep_mesautil`(Mesa 整个内部 util 库,~120 个 TU 加 Python 生成的表),只为了其中一个函数 `loader_open_driver_lib`。真去建,就得让本索引把 libdrm + expat + xcb + 一块 Mesa-util 重新进口一遍,去复刻 `xim:mesa` 已经 hermetic 解析好的东西。对照 `compat.vulkan` —— 它**确实**从源码建 Khronos loader,因为 Khronos 把 loader 作为独立项目发布,而 Mesa 对 GBM 没有对应物。**零 host,且不留后门**:与相邻两个包不同,它没有 `/usr/lib*` 路径、也没有 `MCPP_HOST_*` 覆盖项 —— 因为 host libgbm 是生态**已经堵上**的泄漏,`xim:nvidia-gl-host-link` 里直接点了名("the table … was missing libm, libdrm, **libgbm**, libgcc_s … all of which were therefore coming from the HOST, silently, which is the leak this package exists to close")。NVIDIA 自家的 GBM 后端若将来需要,归那个 host-link 层管,不归这里。实测依赖面:生态 1 个包(`xim:mesa`,不是 `xim:graphics` 的 22 个)、索引依赖 0、传递负担 0 —— libgbm.so.1 自己的 RUNPATH 全落在 `xim-x-*` 内。**真正的活**:libgbm 是个 loader,而 Mesa 把 `/usr/lib/gbm` 编译进去当后端搜索路径,沙箱里没有这个目录,于是 `gbm_create_device` 找不到后端,而且任何链接检查都发现不了。`xim:mesa` 把 `lib` 声明进 view,但 `lib/gbm/` 是子目录,不会跟着进去。所以 install() 把后端也收进来,并且放成 farm 里 libgbm 的**同级**目录,再由一个生成的 TU 在**构造函数**里设好 `GBM_BACKENDS_PATH` —— `dlsym(RTLD_DEFAULT)` 取一个 gbm 符号、`dladdr`、拼上 `/gbm` —— 它报的是 farm 路径而非 realpath,因此**什么都不固定**(换成在 install 时把绝对路径烧进生成头,包就被钉死在安装当天那个 mesa payload 上)。**修复必须是隐形的,这是决定性的设计约束**:API 保持原样的 `#include <gbm.h>`,因为 libgbm 绝大多数是被**别的库内部**调用的(SDL2 的 KMSDRM 后端、wlroots、ffmpeg 的 VAAPI hwcontext),它们永远不会调用我们的 helper —— 需要消费者主动 opt-in 的方案,恰好把最重要的那批消费者留在坏掉的状态。它能成立是因为 mcpp 把依赖的对象**急切全量入链**,构造函数不可能被丢掉。这也正是其他生态的做法:发行版把 `libgbm1` 从 mesa 源码包里拆成二进制包、依赖编译进去的 `$libdir/gbm`;而被重定位的栈改设环境变量(Valve 的 pressure-vessel 正是踩到这个 bug —— steam-runtime#797,Nix/Conda 在环境激活时设),Mesa 还给能控制构建的打包者留了 `-Dgbm-backends-path=` —— **没有一个是用 API 解决的**。更长远看这件事该落在 `xim:mesa` 自己身上。这也是唯一**两个**目录键都要的包:`library_dirs` 给 RUNPATH,`link_library_dirs` 给那个让 `-lgbm` 能解析的 `-L`。它带**两个**测试二进制,拆分本身就是重点:`stock_usage.cpp` 只 include 原样的 `<gbm.h>`、别的什么都不include,一旦退化回「要消费者 opt-in」的方案,它会失败而内容更全的 `gbm.cpp` 仍可能通过。两者都断言后端**存在于推导出的路径上**,而不是断言它能加载 —— 前者在没有 `/dev/dri` 的 runner 上可验证,并且在生态自身 mesa/glibc 错位导致 dlopen 失败的机器上也不会说谎) |
32+
| 生态栈绑定(零 host) | [`compat.libgbm`](../../pkgs/c/compat.libgbm.lua)(Mesa 的 GBM —— 从 DRM 设备分配 buffer。上一行伸手够的是 **host**,这一行够的是**生态**、且仅止于此,这个区别就是整个设计。**为什么不源码构建**:libgbm 是 Mesa **内部**的一个 build target,不是一个项目 —— `src/gbm/meson.build` 写着 `link_with: [libloader]`,而 `libloader` 又要 `idep_mesautil`(Mesa 整个内部 util 库,~120 个 TU 加 Python 生成的表),只为了其中一个函数 `loader_open_driver_lib`。真去建,就得让本索引把 libdrm + expat + xcb + 一块 Mesa-util 重新进口一遍,去复刻 `xim:mesa` 已经 hermetic 解析好的东西。对照 `compat.vulkan` —— 它**确实**从源码建 Khronos loader,因为 Khronos 把 loader 作为独立项目发布,而 Mesa 对 GBM 没有对应物。**零 host,且不留后门**:与相邻两个包不同,它没有 `/usr/lib*` 路径、也没有 `MCPP_HOST_*` 覆盖项 —— 因为 host libgbm 是生态**已经堵上**的泄漏,`xim:nvidia-gl-host-link` 里直接点了名("the table … was missing libm, libdrm, **libgbm**, libgcc_s … all of which were therefore coming from the HOST, silently, which is the leak this package exists to close")。NVIDIA 自家的 GBM 后端若将来需要,归那个 host-link 层管,不归这里。实测依赖面:生态 1 个包(`xim:mesa`,不是 `xim:graphics` 的 22 个)、索引依赖 0、传递负担 0 —— libgbm.so.1 自己的 RUNPATH 全落在 `xim-x-*` 内。**它刻意不做的事**:设置后端搜索路径。libgbm 是个 loader —— `gbm_create_device()` 会 dlopen `<path>/<driver>_gbm.so`,而 Mesa 编译进去的 `/usr/lib/gbm` 在发行版上是对的、payload 一被重定位就是错的。修它的机制是 **Mesa 自己的**(`GBM_BACKENDS_PATH`),而这件事归**环境**管 —— 所有被重定位的栈都是这么做的:Valve 的 pressure-vessel 用 `GBM_BACKENDS_PATH=/run/host/usr/lib64/gbm` 解决同一个 bug(steam-runtime#797),Nix/Conda 在环境激活时设。在本生态里,`xim:mesa` 现在通过 graphics discovery 层声明它(openxlings/xim-pkgindex#713),所以这个包**什么都不设**、不生成 TU、也不带自己的头文件。它一度自带一个 constructor 去设这个变量,那是声明缺失时的权宜之计;删掉它让描述符从 598 行降到 303 行。**两个目录键都要**:`library_dirs` 渲染成 `-Wl,-rpath`、`link_library_dirs` 渲染成 `-L`,所以一个**会被链接**的包(不像 glx-runtime/vulkan-runtime 的农场只被 dlopen)两个都得写 —— 只写 `library_dirs` 的话,农场完整、rpath 正确,构建照样死在 `ld: cannot find -lgbm`。它带**两个**测试二进制:`stock_usage.cpp` 只 include 原样的 `<gbm.h>`,是最小消费者,也是本仓之外那两件依赖的绊线 —— xim-pkgindex 的 DISCOVERY 行,以及 mcpp 的 subos-env 注入)
3333
| 恒开的 interface define | [`compat.curl`](../../pkgs/c/compat.curl.lua)`CURL_STATICLIB`:`cflags` 恒开但包私有,feature `defines` 可达消费端但需点名 —— `default = { implies = … }` 无条件生效,恰好两者兼得 |
3434
| 单包多 major(形态随版本切换) | [`compat.catch2`](../../pkgs/c/compat.catch2.lua)(3.x 编 `src/catch2/` 出静态库;2.x 走 `single_include/` header-only) |
3535
| 外部构建系统(`install()` 从源码构建) | [`compat.openblas`](../../pkgs/c/compat.openblas.lua)(Make) · [`compat.openssl`](../../pkgs/c/compat.openssl.lua)(Perl Configure + Make,静态 libssl/libcrypto) |

0 commit comments

Comments
 (0)