feat(libgbm): add compat.libgbm 2026.08.29, GBM bound to the ecosystem's Mesa - #281
Merged
Conversation
…m's Mesa
GBM is the API a program uses to get scanout-capable buffers out of a DRM
device — gbm_device, gbm_bo, gbm_surface. It sits under EGL on a KMS
console, under a compositor's back end, and under headless GPU rendering.
Shape I, new: an ECOSYSTEM-STACK BINDING. Not a source build, and the
reason is a dependency-surface argument rather than a convenience one.
* Upstream ships no separable unit. `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; plus
-DUSE_DRICONF (expat), libdrm, xcb, xcb-randr. GBM's frontend/backend
dlopen split exists so vendors can ship BACKENDS, not so third parties
rebuild the frontend. (compat.vulkan is not a precedent the other way:
Khronos releases the loader as a standalone project; Mesa does not.)
* In this ecosystem Mesa already has an owner, `xim:mesa`. A source
build would make the index re-import libdrm + expat + xcb + a
Mesa-util carve-out to duplicate a graph the ecosystem has already
resolved — growing the surface to shrink nothing.
Measured surface: host 0, ecosystem 1 (xim:mesa, not xim:graphics's 22),
index 0 (deps = {}), transitive 0 — libgbm.so.1's own RUNPATH resolves
entirely inside xim-x-*.
ZERO HOST, with no escape hatch. Stricter than either neighbour on
purpose: glx-runtime keeps MCPP_HOST_GL_LIBRARY_PATH and vulkan-runtime
harvests /usr/lib outright, both because a proprietary vendor driver can
only come from the host. GBM has no such case, and host libgbm is a leak
the ecosystem already closed — xim:nvidia-gl-host-link names it: "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 (R7)". NVIDIA's own GBM backend, if ever wanted, belongs
in that host-link layer.
The part that is actual work: the backend is unreachable in the sandbox.
libgbm is a loader and Mesa compiles /usr/lib/gbm in as its search path,
which does not exist there —
MESA-LOADER: failed to open dri: /usr/lib/gbm/dri_gbm.so: cannot open
shared object file (search paths /usr/lib/gbm, suffix _gbm)
and xim:mesa declares `lib` into the view while `lib/gbm/` is a
subdirectory that does not follow. So install() also harvests the
backends, as a SIBLING of the farm's libgbm, and a generated TU derives
the path at runtime: dlsym(RTLD_DEFAULT) a gbm symbol, dladdr, append
"/gbm". Verified dladdr reports the FARM path, not the realpath, so the
sibling lands in this package's own payload and nothing is pinned —
unlike baking an absolute path into a generated header, which would fix
the package to whichever mesa payload existed on install day.
Two mechanism findings, both now in the docs:
* runtime.library_dirs renders as -Wl,-rpath and NOT as -L; the -L key
is runtime.link_library_dirs (added 2026.8.10.3), and
transitive_needed_dirs is -Wl,-rpath-link. The catalog and
package-types both asserted library_dirs joined the link line, which
mcpp#304 did observe but the pinned mcpp no longer does. With
library_dirs alone the farm is complete, the rpath right, and the
build dies at `ld: cannot find -lgbm`. Corrected in all four docs.
* c_standard = "gnu11" is still silently ignored, so dladdr/RTLD_DEFAULT
come from cflags -D_GNU_SOURCE (the compat.libaio finding).
Target is gbm_binding, not gbm: a target named gbm would put a libgbm.a
beside the real libgbm.so and let search order decide -lgbm.
Test member asserts what is checkable without a GPU — 19 checks, all
green on a runner with no /dev/dri. The two legacy enumerators are the
load-bearing ones: GBM_BO_FORMAT_XRGB8888 is the value 0, and only the
library's own format_canonicalize() turns it into "XR24", so a
header-only reimplementation would pass the fourcc cases and fail these.
Backend reachability is asserted as PRESENCE at the derived path rather
than a successful dlopen, which stays honest on a host where the stack's
own mesa/glibc skew breaks the load. Device creation is opt-in behind
MCPP_RUN_GBM_DEVICE=1.
CN mirror published at gitcode mcpp-res/libgbm, fetched back and
confirmed byte-identical to GLOBAL. Verified with the CI-pinned mcpp
(2026.8.27.2): `mcpp test -p libgbm` green from cold, all lint gates and
`mcpp xpkg parse` clean across all 136 descriptors, and the assertions
confirmed failable — removing dri_gbm.so from the farm turns the
reachability check red and the binary exits 1.
… API
The first cut exposed `mcpp_gbm.h` and asked the consumer to call
`mcpp_gbm_use_sibling_backends()` before creating a device. That changed
the ordinary way to use libgbm, and it was not only a style problem: it
does not work for the consumers that matter.
libgbm is mostly called from INSIDE other libraries — SDL2's KMSDRM
backend, wlroots, ffmpeg's VAAPI hwcontext all call gbm_create_device()
from their own sources. None of them will ever call a helper of ours, so
an opt-in repair leaves exactly those callers as broken as they were,
while the package's own tests go green.
The API is now stock `#include <gbm.h>` and nothing else. GBM_BACKENDS_PATH
is wired from a CONSTRUCTOR in the package's own TU (priority 101, ahead of
default-priority constructors in case one creates a device). An inherited
value is still left alone — this is a default, not an override.
That is reliable because a dependency's objects enter the consumer's link
eagerly rather than being lazily selected; the emitted build.ninja names
the object on the link line directly, so the constructor cannot be dropped.
It is also what every other ecosystem does, and none of them use an API:
distros (Debian libgbm1, Fedora mesa-libgbm) split libgbm out of the mesa
SOURCE package so the compiled-in $libdir/gbm is right by construction;
relocated and sandboxed stacks set the environment variable instead —
Valve's pressure-vessel hit this exact bug when mesa 24.3 split the
backends out (steam-runtime#797) and answers with GBM_BACKENDS_PATH,
as do Nix, Conda and AppImage at activation time; and Mesa offers
-Dgbm-backends-path= for packagers who control the build. This package is
in the sandboxed case and cannot set a container-wide environment, so the
constructor is the in-process equivalent. Longer term the wiring belongs
in xim:mesa (build with -Dgbm-backends-path=, or declare lib/gbm/ into the
view as it already declares lib and include), and then this package would
carry no constructor at all; noted in the design doc.
mcpp_gbm.h stays, demoted to optional introspection for diagnostics and
for the tests, and says so in its own first line.
Tests now come in two binaries, and the split is the regression guard for
this very mistake:
* tests/stock_usage.cpp includes STOCK <gbm.h> and nothing else — no
mcpp_gbm.h, no helper declaration. If the repair ever goes back to
being opt-in, this fails while the fuller gbm.cpp could still pass.
* tests/gbm.cpp reads GBM_BACKENDS_PATH before calling anything at all,
and re-execs itself with the variable preset to prove an inherited
value survives the constructor — the only way to observe that rule,
since by the time main runs the constructor is finished.
Also drops the generated_files copy of the TU: install() is the only
writer, and the parser takes literals only so the two could not share one
source. Verified cold with the CI-pinned mcpp: 2 passed, 0 failed; both
binaries confirmed failable by removing dri_gbm.so from the farm.
…x / mcpp-index) Records what the PR #281 discussion established: making gbm_create_device() work in an mcpp project needs no new mechanism. Every layer already exists and runs; two wiring points are missing, in two different repos. R1 (xim-pkgindex): GBM_BACKENDS_PATH is absent from graphics.lua's DISCOVERY table. DRI and EGL vendor dirs are there; GBM is the same class of thing (dlopen'd by path, not a link target) and simply never got its counterpart. R2 (mcpp): the default runtime selection reads <xlingsHome>/subos/default, whose envs is {}, while a project's xim: deps declare into <proj>/.mcpp/.xlings/subos/_. Measured: with [xlings] subos = "_" the whole chain works -- ${subosdir} expansion, prepend merge, child injection. So mcpp#352 fixed HOW to inject and not WHERE to read from. R2 is not gbm-specific: LIBGL_DRIVERS_PATH is unset too, so any mcpp-built GL program currently cannot find a DRI driver. Carries the measured evidence, the code locations, per-repo diffs, a verification matrix whose V5 is the mechanical precondition for deleting compat.libgbm's constructor, and the Conan/distro evidence for why the package should stay independent but thin (split axis is the INTERFACE -- gbm.pc vs gl.pc -- not the source project; Conan has no gbm recipe at all and models this class as <name>/system virtual packages).
Four substantive errors in the first cut, three of them 'concluded without running the experiment'. 1. B1/B2 was a false dichotomy. Measured the project subos: it has libc, crt1.o, libm and ld-linux but NOT libgcc_s or libstdc++, so it is not a superset of the toolchain subos. B2 (switch selection) would therefore REGRESS, which is the real objection -- the one I gave (blast radius / full rebuild) was weak, since contractHash already handles invalidation. And B1 (merge envs only) fixes runtime while leaving gbm.h and -lgbm to the package forever. The correct shape is layered inheritance (B3): keep --sysroot on the toolchain subos and overlay the project subos as -isystem / -L / -rpath plus a prepend env merge. One compiler takes one --sysroot, so 'inherit' necessarily means base + overlay, not swap. 2. Never tested whether the compat package could be avoided entirely. It cannot, but not for the reason given: [xlings] deps materializes "deps": ["mesa"] into .mcpp/.xlings.json and then installs nothing (gbm.h not found, no subos created), and [xlings] subos errors rather than bootstrapping a missing subos. So a package's xpm.deps.runtime is currently the ONLY door into the xim layer for an mcpp project -- which is itself a gap, and means compat.libgbm is presently doing a job that is not a library package's to do. 3. The verification matrix conflated the two C2 variants: V5 can only go green under B3. Under B1 the thin shim is permanent, not transitional. 4. R2 was one line where it is three: R2a ([xlings] subos cannot bootstrap), R2b ([xlings] deps materialized but not provisioned), R2c (default selection reads only the toolchain subos, no layering). R1/C1 and the industry argument in §3 are unaffected.
…view R2b: [xlings] deps is materialized into .mcpp/.xlings.json and then nothing installs it. mcpp already has two 'declare -> auto-install' paths to mirror: the toolchain first-run flow (prepare.cppm ~1690, fetcher.resolve_xpkg_path with autoInstall=true) and the project-scope install_packages capability (~2936, which already carries the live progress UI and whose install destination is chosen by package scope, so it lands in the project scope -- which is exactly what creates the project subos). Proposal reuses the latter, keyed off penv.deps, idempotent, with a toolchain-shaped error that prints the manual equivalent. Ordering matters and subsumes R2a: provisioning must run BEFORE runtime selection, or 'selected SubOS does not exist' fires first. So R2a is better understood as 'provision before select' than as 'subos must bootstrap', and [xlings] subos can keep its strict select-only semantics. Third-round review corrects an expectation the earlier rounds got wrong: even with R1 + R2b + B3 all landed, compat.libgbm does NOT disappear. The [xlings] deps route only works for an application's OWN manifest, and GBM's real consumers are mostly LIBRARIES (SDL2's KMSDRM backend, wlroots, ffmpeg VAAPI) which cannot inject [xlings] deps into their consumer's manifest -- they can only declare a dependency edge. Same reason Conan ships opengl/system as a package rather than telling users to write system_requirements themselves. Also records four assumptions still unverified, the sharpest being that B3's overlay must rank BELOW a package's own include_dirs or it would swap out everyone's GL headers (the overlap compat.glx-headers already documents as a real trap).
§11 splits the work into T1–T10 with dependencies, and evaluates it against
architecture / stability / simplicity / UX / compatibility / cross-platform /
consistency / seamless-upgrade / test-coverage. Key structural point: R1
(xim-pkgindex) and R2 (mcpp) are independent chains, so the GBM closed loop
does not wait on the larger mcpp work.
§12 records what implementation actually found, and it overturns §8's central
conclusion. B3 (layer the project SubOS over the toolchain SubOS) is NOT
needed. The fix is to provision `[xlings] deps` at GLOBAL scope, because that
registry's SubOS *is* mcpp's `--sysroot`; once the payload lands there, headers
and libraries are visible with no -isystem/-L overlay at all. Three
measurements got there:
* project scope -> installs fine, headers land in the SubOS the compiler
does not read, gbm.h still not found
* resolve_xpkg_path (global) -> headers reach the sysroot, but it demands
<name>@<version> and rejects a bare name
* install_packages + make_xlings_env (global) -> correct for bare,
namespaced and pinned spellings alike
So §8.1's "the project SubOS lacks libgcc_s/libstdc++" table is still fact; it
just proves "do not install there" rather than "layer over it". The data was
right and the conclusion was backwards. This also keeps the change an order of
magnitude smaller — nothing touches linkmodel.cppm, plan.runtimeSearch or
link_line.cppm, whose comments document exactly the defect that reordering a
mutable view would reintroduce.
Verification recorded in full: the real ecosystem run under
`xlings subos use --sandbox --gpu` allocating an actual gbm buffer object on
card0, and the fresh-MCPP_HOME mcpp run closing compile/link/run/env with zero
mcpp-index packages. Also the one thing still open and out of scope — the
xim-x-mesa payload whose RUNPATH names glibc 2.39 while its own libgallium
needs GLIBC_2.43 — and two verification traps worth knowing (MCPP_HOME appends
another `registry/`, and mcpp keeps its own index copy separate from
~/.xlings).
PRs: openxlings/xim-pkgindex#713 (C1), mcpp-community/mcpp#531 (R2b).
…nv half lives
Records T1-T10 against the three PRs, and answers the review question this
plan invited: PR mcpp#531 contains only provisioning because the SubOS
ENVIRONMENT half was never missing from mcpp. subos_info.cppm parses envs,
runtime_binding collects them, execute.cppm::compute_subos_env injects them
with ${subosdir} expanded and prepend applied, and
tests/e2e/200_subos_env_reaches_program.sh has asserted exactly that since
mcpp#352.
What was missing was the GBM row in the declaration (T1, xim-pkgindex#713)
and a payload in the SubOS mcpp reads (T3, mcpp#531). T4 is retired outright
per §12.1.
mcpp#352 fixed HOW to inject; T1 supplies WHAT to inject; T3 supplies
something to read it from.
…dex packages? mcpp does support shared-library packages -- package-types.md shape F, compat.x11 and linux compat.vulkan -- so this is a should, not a can. mesa itself: no. Two libgbm.so.1 in one process with xim:mesa already providing one (compat.vulkan-runtime reached the same conclusion for libvulkan: 'one loader per process is the whole point'); and mixing a shared closure into an index whose compat.* are all static is a measured silent symbol hijack -- 86 zlib symbols exported from the exe, libgio's 12 zlib calls all bound there, the bundled libz.so.1 fully shadowed, running fine with no warning. vcpkg forbids that with triplets and Conan with a propagated shared option; mcpp/xlings has no such whole-graph switch. But the instinct is right for the separable pieces, and the criterion is the one from section 3: does upstream ship it as a separable unit. libdrm does, Conan has a real recipe for it, and compat.vulkan-runtime currently harvests libdrm*.so.* from the HOST -- exactly the host edge this plan exists to close. A source-built compat.libdrm would close it and trips none of the mesa objections. Recorded as the recommended next package.
The descriptor said the real fix 'is worth filing'. It is filed and implemented: openxlings/xim-pkgindex#713 puts GBM_BACKENDS_PATH in the graphics discovery table, so xim:mesa declares it into the subos and every consumer inherits it (measured: '4 env var(s) from 1 package(s)' where it was 3, and a real gbm_bo_create on card0). So the comment now names the removal CONDITION instead of an intention, and the condition is checkable rather than a judgement call: delete the TU, the lib/gbm farm and mcpp_gbm.h, re-run tests/stock_usage.cpp -- which includes stock <gbm.h> and nothing else -- and if it stays green the ecosystem is supplying the value. Also records that it is NOT green yet as of today: the value only arrives in a home whose installed xim:mesa was configured by an index carrying #713, i.e. after that PR merges and the artifact republishes. Verified again after the edit: parse OK, 2 passed via the CN mirror, cold.
…wins The plan now supersedes itself twice (section 2's C2 by section 8, section 8's B3 by section 12.1), and a reader going top-down would act on retired advice. A short guide up front says which sections are authoritative and which are kept only as a record of reasoning that was overturned — worth keeping, because what overturned each round was a measurement, and those measurements are the durable part. Also states the honest pattern: the only thing that kept changing was the mcpp-side shape, and that is exactly the part I did not read the source or run an experiment on before proposing.
The task asked to pin the internal xlings dependency to 2026.8.27.4, "which should be released by then". Checked, and the premise is inverted: .4 is an OLDER already-published release (2026-08-27T10:18) while .5 is Latest (2026-08-27T13:29, 8 assets), and every pin is already on .5 -- check_version_pins.sh reports "OK: xlings pins all at 2026.8.27.5", and xim-pkgindex's xlings.lua already has latest = 2026.8.27.5. mcpp-index pins mcpp, not xlings. So the intent -- pin to a released xlings -- is already satisfied, and satisfied better. Downgrading would also drop a property the source documents: .5 makes the declaration outrank the index during resolution, so it holds even when "latest" is not the highest entry in the table; .4 does not. The same comment records what the pin is a FLOOR against: below 2026.8.27.2 the bundled xlings takes a subos runtime binding from a compiled-in constant, so a home can declare one glibc and install another, and mcpp is the party that fails. kXlingsVersion is the single source of truth for every pin under .github/, so a downgrade would touch release/CI/bootstrap in a dozen places. The local warning that probably prompted this -- "vendored xlings 2026.8.27.4 is older than the pinned 2026.8.27.5, but no newer source is available" -- is about the copy bundled into the mcpp release tarball, not about .5 being unavailable; .5 has 8 downloadable assets. That message names its own fix, which is to self-update xlings rather than to move mcpp's pin backwards. Not executed; recorded in section 15 instead.
Sunrisepeak
force-pushed
the
feat/add-libgbm
branch
from
August 29, 2026 18:07
21c92c3 to
ebce676
Compare
Section 2's C2 already carried a supersession banner; section 8 did not, so a reader landing there directly would act on the retired B3 conclusion. The reading guide said so, but a guide only helps someone who started at the top. Scoped to 8.1 rather than the whole section: 8.2-8.6 (the untested-alternative finding, the V-matrix correction, the R2a/R2b/R2c split) all still stand, and 8.1's own measurement is still fact -- it just proves "do not install into the project scope" rather than "layer over it".
Section 4 defined deleting the constructor as a mechanical precondition
rather than a judgement call. Simulated the post-merge state locally (copied
#713's graphics.lua and mesa.lua into ~/.mcpp/registry's index copy, re-ran
xlings install so mesa's config() re-declared) and then walked the REAL
dependency path -- [dependencies.compat] libgbm, no [xlings], no ldflags,
just #include <gbm.h> -- cold over the CN mirror:
GBM_BACKENDS_PATH = /home/speak/.mcpp/registry/subos/default/usr/lib/gbm
That is the SUBOS path, not the package's own farm. The constructor is
`if (getenv("GBM_BACKENDS_PATH")) return;`, so a subos-shaped value proves
the ecosystem set it first and the constructor was a no-op. Against an
unpatched index the same path yields the in-package farm value; the only
variable between the two runs is whether the index carries the DISCOVERY row.
Not deleting yet, and the reason is the same gate working as designed: until
#713 merges and the artifact republishes, consumers on the published index
would lose the variable and tests/stock_usage.cpp would go red in CI -- which
is precisely the mechanical check, enforced rather than remembered.
Section 16.4 lists the exact one-step follow-up, and notes that
stock_usage.cpp stays: after the removal it stops asserting "our constructor
ran" and starts asserting "the whole ecosystem loop works", which is the most
valuable regression this package has.
…t host edge
Section 14.2 recommended compat.libdrm on the grounds that it would close
compat.vulkan-runtime's host harvest of libdrm*.so.*. That reasoning is
wrong, and chasing it found a better next step.
vulkan-runtime's farm exists because the dlopen'd ICD ITSELF comes from the
host and carries its own DT_NEEDED (libdrm, LLVM, xcb), which must resolve
through the same directory. That needs host-compatible SHARED libraries. An
in-index compat.libdrm would be a static package like every other compat.*,
and a static archive cannot satisfy a .so's DT_NEEDED. I had conflated "there
is a package called libdrm" with "the farm needs libdrm*.so.*"; they are not
the same thing. libdrm may still be worth adding for build-time consumers,
but not for that reason.
The real next step is one layer up. xim:mesa already ships
share/vulkan/icd.d/radeon_icd.x86_64.json and lib/libvulkan_radeon.so, and
mesa.lua already calls graphics.declare_vulkan_icd(); with DISCOVERY's
XDG_DATA_DIRS the loader finds it. Meanwhile compat.vulkan-runtime still has
deps = {} and still sweeps /usr/lib/x86_64-linux-gnu -- which is exactly where
compat.glx-runtime stood before 2026.08.08, and glx-runtime's fix is the
template: depend on the ecosystem stack, keep the host door only for vendors
the ecosystem does not cover.
Stated honestly: the ecosystem's Vulkan coverage is AMD-only today (mesa.lua
says anv and NVK are still absent), so vulkan-runtime cannot reach the zero-host
position compat.libgbm reached. It should become ecosystem-first with a host
fallback, rather than host-only as it is now.
Asked whether libgbm-2026.08.29.h actually needs downloading, or whether the runtime payload already supplies the header. The payload supplies it: install() symlinks gbm.h out of <subos>/usr/include and libgbm.so* out of <subos>/lib, and the downloaded file is never read. The anchor exists only because the xpm schema wants a url + sha256 per version -- the same inert-anchor trick compat.glx-runtime plays with an OpenGL-Registry README and compat.vulkan-runtime with a Vulkan-Loader README. It used to be Mesa's own src/gbm/main/gbm.h, on the theory that the anchor may as well record which header the package was written against. That was a mistake: an anchor NAMED like the header this package installs reads as though the download is the shipped header, which is the one thing it is not -- and the first person to read the descriptor asked exactly that question. A README cannot be mistaken for a payload, so the anchor is now Mesa's README.rst at the same tag. CN asset published alongside (gtc, mcpp-res/libgbm@2026.08.29), re-fetched and compared byte-for-byte against GLOBAL: 03f0fd62... , 1720 bytes. sha256 taken twice before use. Version deliberately NOT bumped: 2026.08.29 has never been published from this index (the package is still in PR), so no consumer can have resolved the old anchor. Verified cold on both mirrors: MIRROR=CN and MIRROR=GLOBAL each 2 passed.
Two things, both found by reviewing the PR against the ecosystem rather than against itself. 1. THE PACKAGE IS NOW WHAT IT SHOULD ALWAYS HAVE BEEN. 598 -> 303 lines. openxlings/xim-pkgindex#713 merged and the index artifact republished, so xim:mesa now declares GBM_BACKENDS_PATH into the subos and every consumer inherits it. Everything this package carried to compensate is gone: the constructor TU, the lib/gbm backend farm, mesa_libdir(), and mcpp_gbm.h. What remains is a binding and nothing else -- deps = { runtime = xim:mesa }, an install() that symlinks gbm.h and libgbm.so* out of the subos view, and include_dirs / ldflags / runtime dirs. It compiles no upstream source, ships no header of its own, and sets no environment variable. Setting GBM_BACKENDS_PATH was always Mesa's own mechanism and the environment's job; the package doing it was the workaround, not the design. tests/gbm.cpp drops the mcpp_gbm.h include and the re-exec test, and now asserts the ECOSYSTEM supplies the path -- which makes this repo's CI the tripwire on xim-pkgindex's DISCOVERY row and on mcpp's subos-env injection. 2. THE INDEX FLOOR WAS FALSE, and this package proved it. index.toml claimed min_mcpp = 2026.8.3.3. Measured against that version: compat.libgbm reports `parse OK` and then fails to link -- libdrm.so.2, needed by .../libgbm.so, not found undefined reference to `drmGetVersion' because 2026.8.3.3 does not know runtime.link_library_dirs (the string does not occur in the binary) and SILENTLY IGNORES it. Silently-ignored keys are invisible to the lint, so the "floor first, new grammar after" guard could not catch this: it assumes unknown keys are rejected, and runtime.* subkeys are not. 2026.8.10.3 does not even get that far -- it cannot bootstrap against this index at all ("selected RuntimeBinding glibc@2.44 requires payload ... but it is not installed"), which is the compiled-in-binding defect mcpp's own xlings.cppm cites as its reason to floor xlings at 2026.8.27.2. So every client below 2026.8.27.2 was already broken here for reasons predating this package. Floor and latest raised to 2026.8.27.2 -- the version validate.yml has pinned all along, which also restores the "move it with the CI pin" invariant that had quietly drifted. Verified: `mcpp test -p libgbm` 2 passed cold via the CN mirror, against an index synced from the PUBLISHED artifact (xlings update) rather than a hand-patched copy; all lint gates and mcpp xpkg parse clean across 136 descriptors.
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.
Section 16 said the constructor could not be deleted until #713 merged. It has, so 16.3 is marked stale and a new section 18 records what the package actually ended up as: 598 lines to 303, with the constructor, the backend farm, mesa_libdir() and mcpp_gbm.h all gone. Also records what the two test binaries now guard, which is the part worth knowing: their assertions point OUTSIDE this repository -- at xim-pkgindex's DISCOVERY row, at xim:mesa still placing its backends, and at mcpp still injecting subos env. The member stopped being a self-test and became the ecosystem's tripwire. Verification in 18.2 is against the PUBLISHED artifact (xlings update), not a locally patched index copy, which is the distinction section 12.6 warns about.
…S/DRM
stack closes
compat.libgbm on its own can allocate a buffer and do nothing with it. These
three are the rest of the stack, all on the binding shape libgbm validated:
compat.libdrm the layer underneath -- drmModeAddFB2 / drmModeSetCrtc turn
an allocated buffer into a scanout
compat.egl the layer that makes it renderable --
eglGetPlatformDisplay(EGL_PLATFORM_GBM_KHR, gbm_dev, NULL)
compat.wayland the other display path -- be a client of, or be, a compositor
WHY BINDINGS AND NOT SOURCE BUILDS. libdrm and wayland both PASS the
separable-unit test that libgbm fails -- independent freedesktop projects with
their own releases, and Conan carries libdrm as a real recipe. They are
bindings for the second criterion instead: the ecosystem already owns them,
Mesa's own payload has DT_NEEDED on both, and a second libdrm.so.2 or
libwayland-client.so.0 in a process that also loads Mesa means two handle
tables for one connection. EGL is the sharpest case: it is a spec, the thing
you link is glvnd's vendor-neutral dispatch library, and building a second one
would be the "one loader per process" mistake compat.vulkan-runtime already
documents.
Three things worth knowing, each found by making it work:
* libdrm needs TWO include roots. Public headers at the include root, the
uapi headers they include under libdrm/, and xf86drm.h line 40 is a bare
`#include <drm.h>`. Expose one root and nothing compiles -- measured while
writing compat.libgbm's own test, which hit exactly that.
* compat.egl ships ONLY EGL/, out of a payload that also carries GL/, GLES2/,
GLES3/ and KHR/. A third provider of GL/ would turn the two-provider race
compat.glx-headers documents into a three-way one; KHR/ comes from the
index's existing compat.khrplatform, and that edge is load-bearing rather
than tidy -- without it EGL/egl.h does not parse. X11 is deliberately not a
dependency: that include is USE_X11-gated, and forcing Xorg onto headless
GBM users would be exactly wrong.
* compat.wayland harvests four libraries and puts only -lwayland-client on
ldflags. A dependency's ldflags reach the consumer's link line with no way
to opt out, so forcing libwayland-server on every client would be
unfixable downstream. A compositor author adds it themselves and it
resolves out of the farm -- and the test member does exactly that, so the
documented escape hatch has a regression guarding it rather than a promise.
Verified: all three `mcpp test` green cold via the CN mirror. EGL's client
extension list includes EGL_KHR_platform_gbm / EGL_MESA_platform_gbm, which is
the seam with compat.libgbm; libdrm's DRM_FORMAT_XRGB8888 is asserted equal to
the 'XR24' fourcc compat.libgbm asserts, because those two values cross the
gbm_bo -> drmModeAddFB2 boundary and a mismatch shows wrong colours rather than
an error. Lint + xpkg parse clean across 139 descriptors. CN mirrors published
for all three and re-fetched byte-identical against GLOBAL.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
compat.libgbm@2026.08.29— GBM (Generic Buffer Management), the API a program uses to get scanout-capable buffers out of a DRM device (gbm_device,gbm_bo,gbm_surface). It sits under EGL on a KMS console, under a compositor's back end, and under headless GPU rendering.Design doc:
.agents/docs/2026-08-29-add-libgbm-plan.mdShape — a new one: ecosystem-stack binding (I)
Not a source build, and the reason is a dependency-surface argument rather than a convenience one.
Upstream ships no separable unit.
src/gbm/meson.buildislink_with: [libloader], andlibloaderwantsidep_mesautil— the whole of Mesa's internal util library, ~120 TUs plus Python-generated tables — for exactly one function,loader_open_driver_lib; plus-DUSE_DRICONF(expat), libdrm, xcb, xcb-randr. GBM's frontend/backenddlopensplit exists so vendors can ship backends, not so third parties rebuild the frontend.compat.vulkanis not a precedent the other way: Khronos releases the Vulkan-Loader as a standalone project whose whole purpose is to ship separately from drivers; Mesa releases no such thing for GBM.Mesa already has an owner here:
xim:mesa. A source build would make this index re-import libdrm + expat + xcb + a Mesa-util carve-out to duplicate a graph the ecosystem has already resolved hermetically — growing the surface in order to shrink nothing.Measured surface:
xim:mesa, notxim:graphics's 22)deps = {})libgbm.so.1's RUNPATH resolves entirely insidexim-x-*Zero host, with no escape hatch
Deliberately stricter than either neighbour:
compat.glx-runtimekeepsMCPP_HOST_GL_LIBRARY_PATHandcompat.vulkan-runtimeharvests/usr/liboutright — both because a proprietary vendor driver can only come from the host. GBM has no such case, and host libgbm is a leak the ecosystem has already closed.xim:nvidia-gl-host-linknames it:NVIDIA's own GBM backend, if ever wanted, belongs in that host-link layer — not here.
The part that is actual work: the backend is unreachable in the sandbox
Harvesting
libgbm.so+gbm.his the easy half and yields a package you can link and cannot use. libgbm is a loader, and Mesa compiles/usr/lib/gbmin as its backend search path, which does not exist inside the sandbox:xim:mesadeclareslibinto the view, butlib/gbm/is a subdirectory and does not follow. Soinstall()also harvests the backends, laid out as a sibling of the farm's libgbm, and a generated TU derives the path at runtime —dlsym(RTLD_DEFAULT)a gbm symbol,dladdrit, append/gbm.Verified that
dladdrreports the farm path rather than the realpath, so the sibling lands inside this package's own payload and nothing is pinned. Baking an absolute path into a generated header at install time would instead fix the package to whichever mesa payload existed that day — the hazardcompat.glx-runtime's own comment warns about.Two mechanism findings (docs corrected)
runtime.library_dirsdoes not put-Lon the link line. Read off the emittedbuild.ninjaon mcpp 2026.8.27.2:runtime.library_dirs-Wl,-rpathruntime.link_library_dirs-Lruntime.transitive_needed_dirs-Wl,-rpath-linkdocs/descriptor-examples.mdanddocs/package-types.md(both languages) asserted thatlibrary_dirsalso joined the link line — which mcpp#304 genuinely observed, but the separate-Lkey landed in 2026.8.10.3 and the pinned mcpp no longer behaves that way. Withlibrary_dirsalone the farm is complete, the rpath correct, and the build dies atld: cannot find -lgbm. All four docs corrected; the "versioned sonames only" rule stands on its own merits.c_standard = "gnu11"is still silently ignored, sodladdr/RTLD_DEFAULTare reached withcflags = { "-D_GNU_SOURCE" }— thecompat.libaiofinding, unchanged.Also: the lib target is
gbm_binding, notgbm. A target namedgbmwould put alibgbm.aon the link line beside the reallibgbm.soand let search order decide which one-lgbmpicks.Test member
tests/examples/libgbm, linux-gated, no-opmain()elsewhere. 19 checks, all green on a runner with no/dev/dri.The load-bearing ones are the two legacy enumerators:
GBM_BO_FORMAT_XRGB8888is the value0, and only the library's ownformat_canonicalize()turns it into"XR24"— a header-only reimplementation would pass the fourcc cases and fail these. Sixdlsymchecks catch a header/library Mesa mismatch that would otherwise not surface at all.Backend reachability is asserted as presence at the derived path, not a successful
dlopen. That is checkable without a GPU and stays honest on a host where the stack's own skew breaks the load (see below). Device creation is opt-in behindMCPP_RUN_GBM_DEVICE=1.Verification
mcpp test -p libgbmgreen from cold with the CI-pinned mcpp 2026.8.27.2 (target/,.mcpp/and the package's build-cache entry all removed first).dri_gbm.soout of the farm turns the reachability check toFAILEDand the binary exits 1.mcpp xpkg parseclean across all 136 descriptors.NEEDED libgbm.so.1, farm onRPATH, no host path anywhere.gitcode.com/mcpp-res/libgbm@2026.08.29, fetched back and confirmed byte-identical to GLOBAL (95f3b4a6…, 19165 bytes); GLOBAL sha computed twice before use.Known, and not this package's defect
On a host whose
xim-x-mesais 25.0.7.2 againstxim-x-glibc2.39, the backend is found and then fails to load:Note the search path — the reachability gap is closed; what remains is a glibc skew inside the ecosystem's own Mesa build (the shape of mcpp#352), upstream of this package. Worth reporting against
xim:mesaseparately. It is exactly why the test asserts presence rather than a successful load.Deliberately left out
No
capabilitiesentry (no verified DRM counterpart in the engine's vocabulary — coining one that may be silently ignored would document a guarantee this package cannot make), nolibrariesblock (a dotted name there is treated as a package-relative path;ldflagsonly), no features, and no non-linuxxpmsection.Update — the cross-repo closed loop is now implemented
This PR is one of three; the other two remove the reason its constructor exists.
GBM_BACKENDS_PATHin the graphics discovery layer, soxim:mesadeclares it into the SubOS[xlings] depsprovisioned on first build, at global scopecompat.libgbm— the consumable packageWhy this package still exists after those land (and it does): the
[xlings] depsroute works only for an application's own manifest, while GBM's real consumers are mostly libraries — SDL2's KMSDRM backend, wlroots, ffmpeg's VAAPI hwcontext — which cannot inject[xlings] depsinto their consumer's manifest. They can only declare a dependency edge. Same reason Conan shipsopengl/systemas a package rather than telling users to writesystem_requirementsthemselves.What does go away: the constructor, the
lib/gbm/farm andmcpp_gbm.h. The removal condition is mechanical rather than a judgement call, and is now written into the descriptor: delete them and re-runtests/stock_usage.cpp— which includes stock<gbm.h>and nothing else — and if it stays green, the ecosystem is supplying the value. It is not green yet: the SubOS supplies it only in a home whose installedxim:mesawas configured by an index carrying #713, i.e. after that PR merges and the artifact republishes.Ecosystem verification (
xlings subos use --sandbox --gpu, fresh subos, CN mirror configured):The consumer path was also verified through the CN mirror, cold, as an ordinary dependency (
[dependencies.compat] libgbm = "2026.08.29"+#include <gbm.h>, no[xlings], noldflags).Full design record, including the two conclusions that implementation overturned:
.agents/docs/2026-08-30-gbm-cross-repo-closed-loop-plan.md§12–§14.