Skip to content

Commit c1b6051

Browse files
committed
09: wayland is source-built now too, and is two packages
mcpplibs/mcpp-index#290 replaced compat.wayland with freedesktop.wayland and freedesktop.wayland-server, built from source out of mcpplibs/wayland. They are two packages because they are two SONAMEs and Mesa's libEGL_mesa has DT_NEEDED on both, and because mcpp links every library target in a package against all of that package's sources — one package cannot emit two libraries with disjoint contents. So the -lwayland-server escape hatch this example used to demonstrate is gone: a compositor asks for the server package by name. The closure is re-measured. Four of the twelve entries are now this project's own build output — libdrm, libffi and both wayland libraries — and the first of those is the interesting one: Mesa's libgbm has an absolute RUNPATH into the payload's libdrm and still binds to ours, because a soname already in the link map is reused.
1 parent 7e3914a commit c1b6051

2 files changed

Lines changed: 71 additions & 55 deletions

File tree

examples/09-graphics-stack/README.md

Lines changed: 51 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -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

5962
That 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
8490
compat-x-libgbm/25.0.7/…/libgbm.so.1
8591
compat-x-egl/1.7.0/…/libEGL.so.1
86-
compat-x-wayland/…/libwayland-client.so.0
87-
compat-x-wayland/…/libwayland-server.so.0
8892
xim-x-expat/2.6.2/lib/libexpat.so.1
8993
xim-x-gcc/16.1.0/lib64/libgcc_s.so.1
94+
xim-x-gcc/16.1.0/lib64/libstdc++.so.6
9095
xim-x-glibc/2.44/lib64/libc.so.6
9196
xim-x-glibc/2.44/lib64/libm.so.6
92-
xim-x-libffi/3.4.4/lib/libffi.so.8
9397
xim-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

125130
libdrm passes the test — an independent freedesktop project with its own
126131
releases — 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
129134
library for one function. It is also a *loader*, and the backends it dlopens
130135
are 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
133146
saying 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
161174
the processes it launches, so it is simply already set — which is why the
162175
program 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

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
11
[package]
2-
name = "graphics-stack"
3-
version = "0.1.0"
2+
name = "graphics-stack"
3+
version = "0.1.0"
4+
standard = "c++23"
45

56
# The whole KMS/DRM stack, from the index. No host paths, no -L/usr/lib.
67
#
7-
# The versions are the upstream projects' own release numbers, and two of these
8-
# packages are BUILT FROM SOURCE here while two bind the ecosystem's Mesa. The
9-
# split is not arbitrary: a library is built from source when upstream ships it
10-
# as a separable unit, and bound when it is an internal target of a project the
11-
# ecosystem already owns. libdrm passes that test; GBM does not — it is a build
12-
# target inside Mesa, and a loader whose backends are Mesa's own.
8+
# The versions are the upstream projects' own release numbers, and the shapes
9+
# differ on one criterion: a library is BUILT FROM SOURCE when upstream ships it
10+
# as a separable unit, and BOUND to the ecosystem's payload when it is an
11+
# internal target of a project the ecosystem already owns. libdrm and wayland
12+
# pass that test; GBM does not — it is a build target inside Mesa, and a loader
13+
# whose backends are Mesa's own.
1314
[target.'cfg(linux)'.dependencies.compat]
14-
libdrm = "2.4.134" # source-built; the KMS side: modes, CRTCs, framebuffers
15-
libgbm = "25.0.7" # Mesa's GBM: buffer allocation out of a DRM device
16-
egl = "1.7.0" # libglvnd's EGL dispatch: rendering onto those buffers
17-
wayland = "2026.08.30" # the display protocol, client and server
15+
libdrm = "2.4.134" # source-built; the KMS side: modes, CRTCs, framebuffers
16+
libgbm = "25.0.7" # Mesa's GBM: buffer allocation out of a DRM device
17+
egl = "1.7.0" # libglvnd's EGL dispatch: rendering onto those buffers
1818

19-
# compat.wayland puts only -lwayland-client on the link line, because a
20-
# dependency's ldflags reach every consumer with no way to opt out. A
21-
# compositor asks for the server library itself; it resolves out of the same
22-
# package.
23-
[target.'cfg(linux)'.build]
24-
ldflags = ["-lwayland-server"]
19+
# wayland is source-built too, out of mcpplibs/wayland — the client and the
20+
# server are distinct SONAMEs that Mesa's libEGL_mesa needs BOTH of, so they are
21+
# two packages rather than one with an ldflags escape hatch. Each also ships a
22+
# C++23 module wrapper; this example uses the headers, and 09's sibling text
23+
# explains what `import wayland.client;` would change (nothing but the include).
24+
[target.'cfg(linux)'.dependencies.freedesktop]
25+
wayland = "1.26.0"
26+
wayland-server = "1.26.0"

0 commit comments

Comments
 (0)