Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
865f2c0
feat(libgbm): add compat.libgbm 2026.08.29, GBM bound to the ecosyste…
Sunrisepeak Aug 29, 2026
3e42e5a
fix(libgbm): make the backend repair invisible — stock <gbm.h> is the…
Sunrisepeak Aug 29, 2026
3c8c21f
docs(libgbm): cross-repo closed-loop plan for GBM (mcpp / xim-pkginde…
Sunrisepeak Aug 29, 2026
f5ebfb3
docs(libgbm): second-round self-review supersedes the mcpp-side proposal
Sunrisepeak Aug 29, 2026
b0b8436
docs(libgbm): R2b fix (auto-provision [xlings] deps) + third-round re…
Sunrisepeak Aug 29, 2026
713c82e
docs(libgbm): task breakdown, and the measurements that killed B3
Sunrisepeak Aug 29, 2026
bb82e0c
docs(libgbm): delivery status, one PR per repo, and where the SubOS-e…
Sunrisepeak Aug 29, 2026
fff0d75
docs(libgbm): should mesa (or its separable libraries) become mcpp-in…
Sunrisepeak Aug 29, 2026
91de938
docs(libgbm): the constructor's removal condition is now mechanical
Sunrisepeak Aug 29, 2026
989d231
docs(libgbm): reading guide — the doc records three rounds, last one …
Sunrisepeak Aug 29, 2026
ebce676
docs(libgbm): why the xlings pin stays at 2026.8.27.5
Sunrisepeak Aug 29, 2026
f4009b1
docs(libgbm): mark section 8.1 superseded at its own heading
Sunrisepeak Aug 29, 2026
a02ee32
docs(libgbm): V5 passes — the constructor is removable once #713 lands
Sunrisepeak Aug 29, 2026
f210512
docs(libgbm): correct section 14.2 — compat.libdrm does not close tha…
Sunrisepeak Aug 29, 2026
738c2fe
fix(libgbm): the xpm anchor is inert — stop naming it like the header
Sunrisepeak Aug 29, 2026
62b2419
feat(libgbm): the package sheds its workaround; index floor corrected
Sunrisepeak Aug 29, 2026
90a4a84
docs(libgbm): the shipped docs still described the deleted design
Sunrisepeak Aug 29, 2026
1c08e2f
docs(libgbm): record the final state — the follow-up in 16.4 is done
Sunrisepeak Aug 29, 2026
340b45b
feat(graphics): compat.libdrm, compat.egl and compat.wayland — the KM…
Sunrisepeak Aug 29, 2026
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
330 changes: 330 additions & 0 deletions .agents/docs/2026-08-29-add-libgbm-plan.md

Large diffs are not rendered by default.

1,115 changes: 1,115 additions & 0 deletions .agents/docs/2026-08-30-gbm-cross-repo-closed-loop-plan.md

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion docs/descriptor-examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ in the [root README](../README.md#reference-examples).
| Header package filling a gap in the index | [`compat.glx-headers`](../pkgs/c/compat.glx-headers.lua) (libglvnd's `GL/glx.h`, absent from the Khronos registry and required by SDL's X11 backend) |
| 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) |
| 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` |
| 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. Note the farm holds only versioned sonames — `library_dirs` also joins the link line) |
| 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) |
| 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 · [`compat.libdrm`](../pkgs/c/compat.libdrm.lua) (the layer under GBM — `drmModeAddFB2`/`drmModeSetCrtc` turn an allocated buffer into a scanout. Passes the separable-unit test that libgbm fails — libdrm is an independent freedesktop project and Conan carries a real recipe — but is still a binding for the second reason: `xim:libdrm` exists, Mesa's own payload has DT_NEEDED on it, and two `libdrm.so.2` in one process means two DRM handle tables. **Two include roots**, which is the thing that bites: the public headers sit at the include root and the uapi headers they include sit under `libdrm/`, and `xf86drm.h` line 40 is a bare `#include <drm.h>` — expose one root and nothing compiles at all) · [`compat.egl`](../pkgs/c/compat.egl.lua) (what makes libgbm useful for RENDERING rather than only allocation: `eglGetPlatformDisplay(EGL_PLATFORM_GBM_KHR, gbm_device, NULL)`. Provider is libglvnd, not Mesa — EGL is a spec and the thing you link is a vendor-neutral dispatch library that must be the only one in the process. Ships **only** `EGL/` out of a payload that also carries `GL/`, `GLES2/`, `KHR/`: a third provider of `GL/` would make compat.glx-headers' documented two-provider race a three-way one, and `KHR/` comes from the index's existing `compat.khrplatform` instead — load-bearing, since `eglplatform.h` opens with `#include <KHR/khrplatform.h>`. X11 is deliberately NOT a dependency: that include is `USE_X11`-gated, and forcing Xorg on headless GBM users would be exactly wrong) · [`compat.wayland`](../pkgs/c/compat.wayland.lua) (client, server, cursor and EGL shim all harvested, but only `-lwayland-client` on `ldflags`. A dependency's ldflags reach the consumer's link line with no way to opt out, so forcing the server library on every client would be unfixable downstream; a compositor author adds `-lwayland-server` themselves and it resolves out of the farm. The test member does exactly that, so the documented escape hatch has a regression guarding it. `wayland-scanner` and the protocol XML are NOT here — that is a code generator plus a data package, the `compat.protobuf` `protoc` shape, and a separate package))
| 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 |
| 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/`) |
| 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) |
Expand Down
22 changes: 18 additions & 4 deletions docs/package-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ combined as needed.
| **F. Shared-library compat** | has to be the **only** copy of that `.so` in the process (third parties `dlopen` it) | the X11 family such as `pkgs/c/compat.x11.lua`, and `compat.vulkan.lua` (linux) | `targets = { kind = "shared", soname = … }` |
| **G. Host runtime adaptation** | things that cannot be vendored, such as drivers — only a symlink farm plus metadata | `pkgs/c/compat.glx-runtime.lua`, `compat.vulkan-runtime.lua` | `runtime.library_dirs` / `capabilities` |
| **H. Host tool provider** | the upstream tarball also holds a **code generator** consumers run at build time | `pkgs/c/compat.protobuf.lua` (`protoc`) | a `targets` entry with `kind = "bin"` + `main`, plus `required_features` |
| **I. Ecosystem-stack binding** | the library is an internal build target of a project the **ecosystem already owns**, so vendoring it would fork that project | `pkgs/c/compat.libgbm.lua` (Mesa's GBM, via `xim:mesa`) | `xpm.<plat>.deps.runtime = { "xim:<pkg>" }` + a farm from `system.subos_sysrootdir()`, with `runtime.library_dirs` **and** `link_library_dirs` |

For the complete sample index, see [Descriptor examples by shape](descriptor-examples.md).

Expand Down Expand Up @@ -193,10 +194,23 @@ every ICD manifest and yet cannot open a single driver.

Two details that keep biting:

- **Put only versioned sonames in the farm** (`lib*.so.*`). `runtime.library_dirs` also joins the **link line**, so a
bare `libxcb.so` shadows this repository's own `compat.xcb` and the link fails with
`undefined reference to XauDisposeAuth` (mcpp#304). Versioned names are invisible to the linker and are exactly what
`dlopen` asks for.
- **Put only versioned sonames in the farm** (`lib*.so.*`). A bare `libxcb.so` shadowed this repository's own
`compat.xcb` and the link failed with `undefined reference to XauDisposeAuth` (mcpp#304). Versioned names are
invisible to the linker and are exactly what `dlopen` asks for, so this remains the rule.
- **Know which directory key produces which flag** — they are not interchangeable, and the split is what makes the
point above version-dependent. Read off the emitted `build.ninja` on mcpp 2026.8.27.2:

| key | renders as |
|---|---|
| `runtime.library_dirs` | `-Wl,-rpath` |
| `runtime.link_library_dirs` | `-L` |
| `runtime.transitive_needed_dirs` | `-Wl,-rpath-link` |

So on this pin `library_dirs` alone does **not** put the farm on the link line (the separate `-L` key arrived in
2026.8.10.3; mcpp#304 predates it). Shape G packages want exactly that — nothing links against their farms, they
exist so a bare-soname `dlopen` resolves at run time. A package that *is* linked against needs
`link_library_dirs` too, or the build dies at `ld: cannot find -l<name>` with a farm that is perfectly correct;
see shape I.
- **The closure has to be complete.** A farm holding `libxcb.so.1` but not the `libXau.so.6` it depends on shadows the
host copy that would otherwise have resolved, and the executable simply fails to start.

Expand Down
3 changes: 2 additions & 1 deletion docs/zh/descriptor-examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
| 补索引空缺的头文件包 | [`compat.glx-headers`](../../pkgs/c/compat.glx-headers.lua)(libglvnd 的 `GL/glx.h`,Khronos registry 不含,SDL 的 X11 后端必需) |
| C++ 应用框架 compat(依赖复用索引内既有包) | [`compat.eui-neo`](../../pkgs/e/compat.eui-neo.lua)(上游 `3rd/` 自带 8 个 vendored 依赖,此处一个不编,全部改指索引内同版本 `compat.*`) |
| 互斥后端(同包多后端二选一) | [`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` |
| 宿主运行时适配(不 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 —— `library_dirs` 同时进链接行) |
| 宿主运行时适配(不 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,需要的例子见下一行) |
| 生态栈绑定(零 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 注入 · [`compat.libdrm`](../../pkgs/c/compat.libdrm.lua)(GBM 下面那一层 —— `drmModeAddFB2`/`drmModeSetCrtc` 把分配出来的 buffer 变成能扫描输出的画面。它**通过**了 libgbm 没通过的「可独立分发」判据(libdrm 是独立的 freedesktop 项目,Conan 也有真配方),但仍然做成绑定,理由是第二条:`xim:libdrm` 已存在、Mesa 自己的 payload 对它有 DT_NEEDED,一个进程里两份 `libdrm.so.2` 等于两张 DRM 句柄表。**两个 include 根**是真正会咬人的地方:公开头在 include 根、它们 include 的 uapi 头在 `libdrm/` 子目录,而 `xf86drm.h` 第 40 行就是裸的 `#include <drm.h>` —— 只暴露一个根的话什么都编不过) · [`compat.egl`](../../pkgs/c/compat.egl.lua)(让 libgbm 从「只能分配」变成「能渲染」的那一环:`eglGetPlatformDisplay(EGL_PLATFORM_GBM_KHR, gbm_device, NULL)`。提供方是 libglvnd 而不是 Mesa —— EGL 是规范,真正链接的是厂商中立的 dispatch 库,而它必须是进程里唯一的一份。payload 里还有 `GL/`、`GLES2/`、`KHR/`,但本包**只**暴露 `EGL/`:再出一个 `GL/` 提供方会把 compat.glx-headers 记录过的「两个提供方」竞争变成三方;`KHR/` 则交给索引已有的 `compat.khrplatform` —— 这条依赖是必需的,因为 `eglplatform.h` 开头就 `#include <KHR/khrplatform.h>`。X11 **刻意不**作为依赖:那个 include 被 `USE_X11` 门控,而给无显示的 headless GBM 用户强塞 Xorg 恰恰是错的) · [`compat.wayland`](../../pkgs/c/compat.wayland.lua)(client/server/cursor/egl 四个库全部收进 farm,但 `ldflags` 只放 `-lwayland-client`。依赖的 ldflags 会进消费者的链接行且无法退出,所以给每个客户端强塞 server 库是下游修不掉的;compositor 作者自己加 `-lwayland-server`,从 farm 里解析得到。测试成员就是这么写的,于是这条被文档化的退路有回归守着。`wayland-scanner` 与协议 XML **不在这里** —— 那是代码生成器加数据包,属于 `compat.protobuf` 的 `protoc` 形态,应当是另一个包))
| 恒开的 interface define | [`compat.curl`](../../pkgs/c/compat.curl.lua) 的 `CURL_STATICLIB`:`cflags` 恒开但包私有,feature `defines` 可达消费端但需点名 —— `default = { implies = … }` 无条件生效,恰好两者兼得 |
| 单包多 major(形态随版本切换) | [`compat.catch2`](../../pkgs/c/compat.catch2.lua)(3.x 编 `src/catch2/` 出静态库;2.x 走 `single_include/` header-only) |
| 外部构建系统(`install()` 从源码构建) | [`compat.openblas`](../../pkgs/c/compat.openblas.lua)(Make) · [`compat.openssl`](../../pkgs/c/compat.openssl.lua)(Perl Configure + Make,静态 libssl/libcrypto) |
Expand Down
Loading
Loading