@@ -50,10 +50,13 @@ answer: the whole chain, done the recommended way, declaring dependencies.
5050
5151``` toml
5252[target .'cfg(linux)' .dependencies .compat ]
53- libdrm = " 2.4.134"
54- libgbm = " 25.0.7"
55- egl = " 1.7.0"
56- wayland = " 2026.08.30"
53+ libdrm = " 2.4.134"
54+ libgbm = " 25.0.7"
55+ egl = " 1.7.0"
56+
57+ [target .'cfg(linux)' .dependencies .freedesktop ]
58+ wayland = " 1.26.0"
59+ wayland-server = " 1.26.0"
5760```
5861
5962That is the entire configuration. ` src/main.cpp ` then includes ` <gbm.h> ` ,
@@ -80,47 +83,49 @@ BIN=target/x86_64-linux-gnu/*/bin/graphics-stack
8083```
8184
8285```
83- <project>/target/.../bin/libdrm.so.2 <- built from source, by this build
86+ <project>/bin/libdrm.so.2 <- built by this build
87+ <project>/bin/libwayland-client.so.0 <- built by this build
88+ <project>/bin/libwayland-server.so.0 <- built by this build
89+ <project>/bin/libffi.so.8 <- built by this build
8490compat-x-libgbm/25.0.7/…/libgbm.so.1
8591compat-x-egl/1.7.0/…/libEGL.so.1
86- compat-x-wayland/…/libwayland-client.so.0
87- compat-x-wayland/…/libwayland-server.so.0
8892xim-x-expat/2.6.2/lib/libexpat.so.1
8993xim-x-gcc/16.1.0/lib64/libgcc_s.so.1
94+ xim-x-gcc/16.1.0/lib64/libstdc++.so.6
9095xim-x-glibc/2.44/lib64/libc.so.6
9196xim-x-glibc/2.44/lib64/libm.so.6
92- xim-x-libffi/3.4.4/lib/libffi.so.8
9397xim-x-libglvnd/1.7.0.1/lib/libGLdispatch.so.0
9498```
9599
96- Nothing is under ` /usr/lib ` or ` /lib64 ` . Two things in that list are worth
97- reading closely.
100+ Nothing is under ` /usr/lib ` or ` /lib64 ` . Two things there are worth reading
101+ closely.
98102
99- ** The first line .** ` libdrm.so.2 ` resolves to this project's own build output,
100- not to the ` xim-x- libdrm` the Mesa payload was linked against — even though
101- ` libgbm.so.1 ` has a DT_NEEDED on that soname and an absolute RUNPATH pointing
102- into the payload. The consumer links libdrm directly, so it is mapped first,
103- and Mesa's GBM binds to it: the ` gbm_bo_create ` above ran through it .
103+ ** The first four lines .** They are this project's own build output, not the
104+ ecosystem's copies — including ` libdrm.so.2 ` , even though Mesa's ` libgbm.so.1 `
105+ has a DT_NEEDED on that soname and an absolute RUNPATH into the payload. The
106+ consumer links them directly, so they are mapped first, and Mesa binds to them:
107+ the ` gbm_bo_create ` above ran through this libdrm .
104108
105- ** The bottom half.** ` libexpat ` , ` libffi ` and ` libGLdispatch ` are * transitive* —
106- nothing in ` mcpp.toml ` names them. They are what a directly linked
107- ` libgbm.so.1 ` cascades into, and resolving that cascade is exactly what a host
108- ` -L/usr/lib ` cannot do from inside a private loader.
109+ ** ` libffi ` and ` libGLdispatch ` .** Nothing in ` mcpp.toml ` names either.
110+ ` libffi.so.8 ` is what ` libwayland-client ` dispatches protocol messages through,
111+ ` libGLdispatch ` is what libEGL's vendor dispatch needs — the cascade a directly
112+ linked library pulls behind it, which is exactly what a host ` -L/usr/lib `
113+ cannot resolve from inside a private loader.
109114
110115## The packages
111116
112- Two of them are built from source and two bind the ecosystem's Mesa, and the
113- split is not arbitrary. A library is built from source when upstream ships it
114- as a ** separable unit** ; it is bound when it is an internal build target of a
115- project the ecosystem already owns, where building it would mean forking that
116- project.
117+ Three are built from source and two bind the ecosystem's Mesa, and the split is
118+ not arbitrary. A library is built from source when upstream ships it as a
119+ ** separable unit** ; it is bound when it is an internal build target of a project
120+ the ecosystem already owns, where building it would mean forking that project.
117121
118122| package | | what it gives you |
119123| ---| ---| ---|
120124| ` compat.libdrm ` | source | ` drmModeGetResources ` , ` drmModeAddFB2 ` , ` drmModeSetCrtc ` — the KMS side |
121125| ` compat.libgbm ` | binds ` xim:mesa ` | ` gbm_create_device ` , ` gbm_bo_create ` — buffers out of a DRM device |
122126| ` compat.egl ` | binds ` xim:libglvnd ` | ` eglGetPlatformDisplay(EGL_PLATFORM_GBM_KHR, …) ` — rendering onto them |
123- | ` compat.wayland ` | binds ` xim:wayland ` | client and server libraries for the display protocol |
127+ | ` freedesktop.wayland ` | source | ` libwayland-client.so.0 ` , and ` import wayland.client; ` |
128+ | ` freedesktop.wayland-server ` | source | ` libwayland-server.so.0 ` , and ` import wayland.server; ` |
124129
125130libdrm passes the test — an independent freedesktop project with its own
126131releases — so it is compiled here, five translation units with no dependencies
@@ -129,6 +134,14 @@ at all. GBM fails it: `src/gbm/meson.build` is `link_with: [libloader]`, and
129134library for one function. It is also a * loader* , and the backends it dlopens
130135are Mesa's own, so built apart from Mesa it would have nothing to load.
131136
137+ Wayland passes it too, but needed more than a descriptor: its libraries are
138+ mostly ** generated** — ` protocol/wayland.xml ` describes every interface and
139+ ` wayland-scanner ` emits ~ 13,000 lines from it — and the generator is a C program
140+ in the same tree that must be compiled first. That does not fit an inline index
141+ descriptor, so it lives in [ mcpplibs/wayland] ( https://github.com/mcpplibs/wayland ) ,
142+ a fork that patches no upstream file. Client and server are two packages because
143+ they are two distinct SONAMEs and Mesa's ` libEGL_mesa ` has DT_NEEDED on ** both** .
144+
132145** A payload carrying the same library is not a reason to bind** , which is worth
133146saying because it looks like one. Mesa's ` libgbm.so.1 ` has a DT_NEEDED on
134147` libdrm.so.2 ` and an absolute RUNPATH into the payload's copy — and in this
@@ -161,18 +174,19 @@ puts it (Valve's pressure-vessel, Nix, Conda all do exactly this). Here
161174the processes it launches, so it is simply already set — which is why the
162175program prints it rather than computing it.
163176
164- ** ` compat.wayland ` puts only ` -lwayland-client ` on the link line** , and this
165- example adds the other half itself:
166-
167- ``` toml
168- [target .'cfg(linux)' .build ]
169- ldflags = [" -lwayland-server" ]
170- ```
171-
172- A dependency's ` ldflags ` reach every consumer with no way to opt out, so a
173- package that forced ` libwayland-server ` on every client would be unfixable
174- downstream. All four wayland libraries are present; a compositor asks for the
175- one it needs and it resolves out of the same package.
177+ ** The wayland client and server are separate packages** , and this example asks
178+ for both because it creates a ` wl_display ` on the server side. That is not a
179+ packaging quirk: they are two SONAMEs, Mesa's ` libEGL_mesa ` carries DT_NEEDED on
180+ each, and mcpp links every library target in a package against all of that
181+ package's sources — so one package cannot emit two libraries with disjoint
182+ contents. A client-only program drops the second line and links only
183+ ` libwayland-client.so.0 ` .
184+
185+ Both also ship a C++23 module wrapper. ` import wayland.client; ` in place of
186+ ` #include <wayland-client.h> ` changes nothing else — every exported name is
187+ upstream's, spelled upstream's way — so this file could switch one line at a
188+ time. It uses the headers here because that is what a ported project looks like
189+ on day one.
176190
177191## Running it
178192
0 commit comments