diff --git a/docs/descriptor-examples.md b/docs/descriptor-examples.md index 8eaa20ee..9b47cacc 100644 --- a/docs/descriptor-examples.md +++ b/docs/descriptor-examples.md @@ -31,7 +31,8 @@ in the [root README](../README.md#reference-examples). | 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_` 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. 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. **Why not a source build**: libgbm is a build target *inside* Mesa, not a separable project — `src/gbm/meson.build` is `link_with: [libloader]`, and `libloader` wants `idep_mesautil`, ~120 TUs of Mesa's internal util library for exactly one function, `loader_open_driver_lib`; and it is a **loader**, whose `gbm_create_device()` dlopens Mesa's own `dri_gbm.so`, so built apart from Mesa it would have nothing to load. **The test on this row is separability, NOT whether the payload also carries a copy** — `compat.libdrm`, from the same stack, passes it and is a source build (see the row below); the two coexist, measured. **What it deliberately does NOT do**: set the backend search path. `GBM_BACKENDS_PATH` is Mesa's own mechanism and the ENVIRONMENT's job — `xim:mesa` declares it through the graphics discovery layer (openxlings/xim-pkgindex#713), so this package sets nothing. **Two directory keys, not one**: `library_dirs` renders as `-Wl,-rpath` and `link_library_dirs` as `-L`, so a package that is *linked against* needs both — with the first alone the build still dies at `ld: cannot find -lgbm`) · [`compat.egl`](../pkgs/c/compat.egl.lua) (the EGL dispatch, provided by libglvnd rather than Mesa. libglvnd **is** a separable project, so by the criterion this should be a source build; it is still a binding for effort alone: `libEGL.so` also needs `egl_dispatch_stubs` (Python-generated `g_egldispatchstubs.c/.h`), `winsys_dispatch` and the whole of `libGLdispatch.so`, plus the vendor discovery path `__EGL_VENDOR_LIBRARY_DIRS` — a fork of the same size as wayland's. 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` — load-bearing, since `eglplatform.h` opens with `#include `. X11 is deliberately NOT a dependency: that include is `USE_X11`-gated) · [`compat.wayland`](../pkgs/c/compat.wayland.lua) (client, server, cursor and EGL shim harvested from `xim:wayland`, with 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. Wayland IS a separable project, so this row is where it does not belong: it moves to a source build in mcpplibs/wayland, which needs `build.mcpp` because wayland-scanner has to be compiled before it can generate the ~13,000 lines the two libraries are mostly made of) | +| 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. **Why not a source build**: libgbm is a build target *inside* Mesa, not a separable project — `src/gbm/meson.build` is `link_with: [libloader]`, and `libloader` wants `idep_mesautil`, ~120 TUs of Mesa's internal util library for exactly one function, `loader_open_driver_lib`; and it is a **loader**, whose `gbm_create_device()` dlopens Mesa's own `dri_gbm.so`, so built apart from Mesa it would have nothing to load. **The test on this row is separability, NOT whether the payload also carries a copy** — `compat.libdrm`, from the same stack, passes it and is a source build (see the row below); the two coexist, measured. **What it deliberately does NOT do**: set the backend search path. `GBM_BACKENDS_PATH` is Mesa's own mechanism and the ENVIRONMENT's job — `xim:mesa` declares it through the graphics discovery layer (openxlings/xim-pkgindex#713), so this package sets nothing. **Two directory keys, not one**: `library_dirs` renders as `-Wl,-rpath` and `link_library_dirs` as `-L`, so a package that is *linked against* needs both — with the first alone the build still dies at `ld: cannot find -lgbm`) · [`compat.egl`](../pkgs/c/compat.egl.lua) (the EGL dispatch, provided by libglvnd rather than Mesa. libglvnd **is** a separable project, so by the criterion this should be a source build; it is still a binding for effort alone: `libEGL.so` also needs `egl_dispatch_stubs` (Python-generated `g_egldispatchstubs.c/.h`), `winsys_dispatch` and the whole of `libGLdispatch.so`, plus the vendor discovery path `__EGL_VENDOR_LIBRARY_DIRS` — a fork of the same size as wayland's. 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` — load-bearing, since `eglplatform.h` opens with `#include `. X11 is deliberately NOT a dependency: that include is `USE_X11`-gated) | +| Source build that needs a real project (Form A fork) | [`freedesktop.wayland`](../pkgs/f/freedesktop.wayland.lua) · [`freedesktop.wayland-server`](../pkgs/f/freedesktop.wayland-server.lua) · [`freedesktop.wayland-scanner`](../pkgs/f/freedesktop.wayland-scanner.lua) · [`freedesktop.wayland-util`](../pkgs/f/freedesktop.wayland-util.lua) (wayland 1.26.0, four entries out of one tarball — [mcpplibs/wayland](https://github.com/mcpplibs/wayland), a fork that patches no upstream file. **Why a fork and not an inline descriptor**: wayland is mostly GENERATED — `protocol/wayland.xml` describes every interface and wayland-scanner emits ~13,000 lines from it — and the generator is a C program in the same tree, so it must be COMPILED before it can run. An inline descriptor has no build step, and `install()` cannot do it either: mcpp compiles a package's sources at CONSUMER-BUILD time, so no package binary exists while another package installs. `build.mcpp` is the mechanism, and it only exists for a real mcpp project. **Why four packages**: `libwayland-client.so.0` and `libwayland-server.so.0` are distinct SONAMEs that Mesa's libEGL_mesa needs BOTH of, and mcpp links every library target against ALL of a package's sources — measured: a feature-gated second target still receives the feature's objects — so one package cannot emit two libraries with disjoint contents. mcpp's own diagnostic names the remedy: "split into a workspace member". **The module layer ships with the library, not beside it**: each package carries its C library AND its `.cppm`, so there is one package per library rather than a C one and a module one. The wrappers add no API — every exported name is upstream's, and the lists are generated from the public headers so a version bump cannot silently drop one. **Macros are the one thing that cannot cross**: `export` names entities and a macro is not one, so `wayland-util` maps each to what it actually is — `WL_MARSHAL_FLAG_DESTROY` to a constexpr, `wl_container_of` to a function template, the six `*_for_each` loops to ranges — and its test instantiates all of them including the `_safe` removal guarantee) | | Source-built library sharing a soname with the payload | [`compat.libdrm`](../pkgs/c/compat.libdrm.lua) (libdrm 2.4.134 — five TUs plus an inlined `generated_static_table_fourcc.h`, and zero dependencies. It carries the same `libdrm.so.2` the ecosystem's Mesa payload does, and that is **not** a reason to bind: a DT_NEEDED soname already in the link map is REUSED, ld.so never searches again, so Mesa's `libgbm.so.1` binds to the copy the consumer linked — measured: one mapping in the process, and Mesa's GBM allocated a buffer through it. It holds only for `kind = "shared"` with the right soname: as the default `kind = "lib"` there is no `.so` to reuse, the payload's loads for Mesa, the consumer keeps its own merged one, and libdrm's file-static state (`drmHashTable`, `nr_fds`, `connection`) exists twice over one set of fds. The test asserts with `dladdr` that the loaded library is this package's. **Two include roots**: the public headers sit at the source root and the uapi headers under `include/drm/`, and `xf86drm.h` line 40 is a bare `#include `) · [`compat.libffi`](../pkgs/c/compat.libffi.lua) (3.4.8. The x86 assembly **gates itself** — `ffi.c`/`ffiw64.c`/`unix64.S`/`sysv.S`/`win64.S` each open with `#ifdef __x86_64__` or `__i386__` — so all of them are listed and the preprocessor selects. `fficonfig.h` is configure's record of what it probed and cannot be derived, so it is inlined; `ffi.h` CAN be derived, so install() performs configure's four substitutions on `ffi.h.in` and stays honest across a version bump) · [`compat.expat`](../pkgs/c/compat.expat.lua) (2.7.1, three TUs — `xmltok_impl.c` and `xmltok_ns.c` are `#include`d BY `xmltok.c`, five times between them, so listing them would produce duplicate symbols) | | 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/`) | diff --git a/docs/package-types.md b/docs/package-types.md index 5dcab022..7b824c6b 100644 --- a/docs/package-types.md +++ b/docs/package-types.md @@ -14,7 +14,7 @@ combined as needed. | **A. C-source compat** | plain C or a handful of sources; the user writes `#include ` | `pkgs/c/compat.cjson.lua`, `compat.zlib.lua`, `compat.gtest.lua` | `sources` and `c_standard` | | **B. header-only** | headers only, nothing to compile | `pkgs/c/compat.eigen.lua`, `compat.opengl.lua`, `compat.khrplatform.lua` | `include_dirs` and an anchor source | | **C. C++23 module** | exposes `import x.y;` | `pkgs/n/nlohmann.json.lua` | `modules` plus `generated_files` or a source `.cppm` | -| **D. External Form-A module repo** | upstream ships its own mcpp descriptor in a separate repository — or the build needs something an inline descriptor cannot express (`build.mcpp`, a workspace, a code generator that must be compiled first) | `pkgs/i/imgui.lua`, `pkgs/m/mcpplibs.*`, `pkgs/g/grpc.lua` + `grpcgen.lua` + `grpc-plugin.lua` (three entries out of one fork) | `mcpp = ""` (Form A) | +| **D. External Form-A module repo** | upstream ships its own mcpp descriptor in a separate repository — or the build needs something an inline descriptor cannot express (`build.mcpp`, a workspace, a code generator that must be compiled first) | `pkgs/i/imgui.lua`, `pkgs/m/mcpplibs.*`, `pkgs/f/freedesktop.wayland*.lua` (four entries out of one fork) | `mcpp = ""` (Form A) | | **E. Whole-source direct build with a generated config** | upstream generates its config header through configure/CMake; here a snapshot of it lands in `generated_files` | `pkgs/c/compat.libpng.lua`, `compat.curl.lua`, `compat.sdl2.lua`, `compat.ffmpeg.lua` | `generated_files` + `include_dirs` | | **F. Shared-library compat** | has to be the **only** copy of that `.so` in the process — either because third parties `dlopen` it, or because the ecosystem payload links the same soname | the X11 family such as `pkgs/c/compat.x11.lua`, `compat.vulkan.lua`, `compat.libdrm.lua`, `compat.libffi.lua`, `compat.expat.lua` | `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` | diff --git a/docs/zh/descriptor-examples.md b/docs/zh/descriptor-examples.md index 7713597e..ea730b62 100644 --- a/docs/zh/descriptor-examples.md +++ b/docs/zh/descriptor-examples.md @@ -29,7 +29,8 @@ | 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_`,在强制包含头里做前置判定。另注意 `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,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`;而且它是个 **loader**,`gbm_create_device()` dlopen 的后端就是 Mesa 自己的 `dri_gbm.so`,脱离 Mesa 建出来就是个没东西可加载的 loader。**注意这一行的判据是「可否独立分发」,不是「会不会和 payload 共存」** —— 同处一栈的 `compat.libdrm` 满足前者,于是走源码构建(见「与 payload 同 soname 的源码库」一行),两者共存已实测无碍。**它刻意不做的事**:设置后端搜索路径。`GBM_BACKENDS_PATH` 是 Mesa 自己的机制,归**环境**管 —— `xim:mesa` 通过 graphics discovery 层声明它(openxlings/xim-pkgindex#713),所以这个包什么都不设。**两个目录键都要**:`library_dirs` 渲染成 `-Wl,-rpath`、`link_library_dirs` 渲染成 `-L`,一个**会被链接**的包两个都得写,只写前者会死在 `ld: cannot find -lgbm`) · [`compat.egl`](../../pkgs/c/compat.egl.lua)(EGL dispatch,提供方是 libglvnd 而不是 Mesa。libglvnd **是**独立项目,按判据本该源码构建,现在仍是绑定纯粹是工作量问题:`libEGL.so` 还要 `egl_dispatch_stubs`(Python 生成的 `g_egldispatchstubs.c/.h`)、`winsys_dispatch`,以及完整的 `libGLdispatch.so`,加上 vendor 发现路径 `__EGL_VENDOR_LIBRARY_DIRS` —— 与 wayland 同级的一个 fork 工程。payload 里还有 `GL/`、`GLES2/`、`KHR/`,本包**只**暴露 `EGL/`:再出一个 `GL/` 提供方会把 compat.glx-headers 记录过的「两个提供方」竞争变成三方;`KHR/` 交给索引已有的 `compat.khrplatform` —— 这条依赖是必需的,因为 `eglplatform.h` 开头就 `#include `。X11 **刻意不**作为依赖:那个 include 被 `USE_X11` 门控) · [`compat.wayland`](../../pkgs/c/compat.wayland.lua)(从 `xim:wayland` 收 client/server/cursor/egl 四个库进 farm,但 `ldflags` 只放 `-lwayland-client` —— 依赖的 ldflags 会进消费者链接行且无法退出,给每个客户端强塞 server 库是下游修不掉的。wayland **是**可独立分发的项目,所以它本不该在这一行:它会迁去 mcpplibs/wayland 做源码构建,而那需要 `build.mcpp` —— wayland-scanner 必须先被编译出来,才能生成两个库大部分体量的那约 13000 行代码)| +| 生态栈绑定(零 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`;而且它是个 **loader**,`gbm_create_device()` dlopen 的后端就是 Mesa 自己的 `dri_gbm.so`,脱离 Mesa 建出来就是个没东西可加载的 loader。**注意这一行的判据是「可否独立分发」,不是「会不会和 payload 共存」** —— 同处一栈的 `compat.libdrm` 满足前者,于是走源码构建(见「与 payload 同 soname 的源码库」一行),两者共存已实测无碍。**它刻意不做的事**:设置后端搜索路径。`GBM_BACKENDS_PATH` 是 Mesa 自己的机制,归**环境**管 —— `xim:mesa` 通过 graphics discovery 层声明它(openxlings/xim-pkgindex#713),所以这个包什么都不设。**两个目录键都要**:`library_dirs` 渲染成 `-Wl,-rpath`、`link_library_dirs` 渲染成 `-L`,一个**会被链接**的包两个都得写,只写前者会死在 `ld: cannot find -lgbm`) · [`compat.egl`](../../pkgs/c/compat.egl.lua)(EGL dispatch,提供方是 libglvnd 而不是 Mesa。libglvnd **是**独立项目,按判据本该源码构建,现在仍是绑定纯粹是工作量问题:`libEGL.so` 还要 `egl_dispatch_stubs`(Python 生成的 `g_egldispatchstubs.c/.h`)、`winsys_dispatch`,以及完整的 `libGLdispatch.so`,加上 vendor 发现路径 `__EGL_VENDOR_LIBRARY_DIRS` —— 与 wayland 同级的一个 fork 工程。payload 里还有 `GL/`、`GLES2/`、`KHR/`,本包**只**暴露 `EGL/`:再出一个 `GL/` 提供方会把 compat.glx-headers 记录过的「两个提供方」竞争变成三方;`KHR/` 交给索引已有的 `compat.khrplatform` —— 这条依赖是必需的,因为 `eglplatform.h` 开头就 `#include `。X11 **刻意不**作为依赖:那个 include 被 `USE_X11` 门控))| +| 需要真实工程的源码构建(Form A fork) | [`freedesktop.wayland`](../../pkgs/f/freedesktop.wayland.lua) · [`freedesktop.wayland-server`](../../pkgs/f/freedesktop.wayland-server.lua) · [`freedesktop.wayland-scanner`](../../pkgs/f/freedesktop.wayland-scanner.lua) · [`freedesktop.wayland-util`](../../pkgs/f/freedesktop.wayland-util.lua)(wayland 1.26.0,一个 tarball 出四个条目 —— [mcpplibs/wayland](https://github.com/mcpplibs/wayland),不改上游任何文件的 fork。**为什么要 fork 而不是内联描述符**:wayland 大部分是**生成**出来的 —— `protocol/wayland.xml` 描述了每个接口,wayland-scanner 从它生成约 13000 行 —— 而生成器是同一棵树里的 C 程序,**必须先被编译**才能跑。内联描述符没有构建步骤;`install()` 也做不到,因为 mcpp 是在**消费者构建期**才编译包源码,别的包安装时不存在任何包产物。`build.mcpp` 正是为此而设,而它只存在于真实的 mcpp 工程里。**为什么是四个包**:`libwayland-client.so.0` 与 `libwayland-server.so.0` 是两个不同 soname,Mesa 的 libEGL_mesa 对**两者都**有 DT_NEEDED,所以必须是内容互不相交的两个文件;而 mcpp 把包的源码编一次、让**每个**库 target 都链接全部对象 —— 实测:feature 门控的第二个 target 照样拿到该 feature 的对象 —— 一个包出不了两个互不相交的库。mcpp 自己的告警就点了办法:「split into a workspace member」。**模块层与库同包**:每个包同时带 C 库和它的 `.cppm`,所以是一库一包,而不是 C 一个、模块一个。包装层**不增加 API**:导出名全是上游的,而且列表是从公开头**生成**的,版本一动不会悄悄少一个名字。**宏是唯一过不去的东西**:`export` 命名的是实体而宏不是,所以 `wayland-util` 把每个宏映射成它实际是的东西 —— `WL_MARSHAL_FLAG_DESTROY` → constexpr、`wl_container_of` → 函数模板、六个 `*_for_each` → range,它的测试把它们全部实例化,包括 `_safe` 的「循环体内可删除当前元素」保证)| | 与 payload 同 soname 的源码库 | [`compat.libdrm`](../../pkgs/c/compat.libdrm.lua)(libdrm 2.4.134,5 个 TU + 内联的 `generated_static_table_fourcc.h`,零依赖。它和生态 payload 里的 `libdrm.so.2` 同名共存,而这**不是**绑定的理由:DT_NEEDED 的 soname 一旦在 link map 中就被复用,ld.so 不再搜索,所以 Mesa 的 `libgbm.so.1` 绑到的是消费者链接的这一份 —— 实测进程里只有一份,且 Mesa 的 GBM 用它分配出了 buffer。前提是 `kind = "shared"` **且 soname 正确**:用默认的 `kind = "lib"` 就没有 `.so` 可复用,payload 那份照常为 Mesa 加载,消费者另有一份合并进来的,libdrm 的 file-static 状态(`drmHashTable`/`nr_fds`/`connection`)在同一批 fd 上分成两套账。测试用 `dladdr` 断言加载的确实是本包所建。**两个 include 根**:公开头在源码根、uapi 头在 `include/drm/`,而 `xf86drm.h` 第 40 行就是裸的 `#include `) · [`compat.libffi`](../../pkgs/c/compat.libffi.lua)(3.4.8。x86 汇编**自带架构门控**,`ffi.c`/`ffiw64.c`/`unix64.S`/`sysv.S`/`win64.S` 各自 `#ifdef __x86_64__`/`__i386__`,所以全部列进 sources 由预处理器筛;`fficonfig.h` 是 configure 的探测结果、无法从 tarball 推导,内联;`ffi.h` **能**推导,于是 install() 照 configure 的四个替换从 `ffi.h.in` 生成,版本一动就跟着动) · [`compat.expat`](../../pkgs/c/compat.expat.lua)(2.7.1,3 个 TU —— `xmltok_impl.c`/`xmltok_ns.c` 是被 `xmltok.c` **include** 五次的,列进 sources 会产生重复符号) | | 恒开的 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) | diff --git a/docs/zh/package-types.md b/docs/zh/package-types.md index fab543e7..0dc0ff80 100644 --- a/docs/zh/package-types.md +++ b/docs/zh/package-types.md @@ -12,7 +12,7 @@ A–D 是四种**基础**形态,先按它们判定;E–G 是在基础形态之 | **A. C 源码 compat** | 纯 C 或少量源码,用户 `#include ` | `pkgs/c/compat.cjson.lua`、`compat.zlib.lua`、`compat.gtest.lua` | `sources` 与 `c_standard` | | **B. header-only** | 纯头文件,无需编译 | `pkgs/c/compat.eigen.lua`、`compat.opengl.lua`、`compat.khrplatform.lua` | `include_dirs` 与 anchor 源 | | **C. C++23 module** | 暴露 `import x.y;` | `pkgs/n/nlohmann.json.lua` | `modules` 与 `generated_files` 或源 `.cppm` | -| **D. 外部 Form-A 模块仓** | 上游自带 mcpp 描述符的独立仓库,或者构建需要内联描述符表达不了的东西(`build.mcpp`、workspace、必须先编译出来才能跑的代码生成器) | `pkgs/i/imgui.lua`、`pkgs/m/mcpplibs.*`、`pkgs/g/grpc.lua` + `grpcgen.lua` + `grpc-plugin.lua`(一个 fork 出三个条目) | `mcpp = ""`(Form A) | +| **D. 外部 Form-A 模块仓** | 上游自带 mcpp 描述符的独立仓库,或者构建需要内联描述符表达不了的东西(`build.mcpp`、workspace、必须先编译出来才能跑的代码生成器) | `pkgs/i/imgui.lua`、`pkgs/m/mcpplibs.*`、`pkgs/f/freedesktop.wayland*.lua`(一个 fork 出四个条目) | `mcpp = ""`(Form A) | | **E. 生成 config 的全源码直编** | 上游用 configure/CMake 生成配置头,此处以 `generated_files` 落一份快照 | `pkgs/c/compat.libpng.lua`、`compat.curl.lua`、`compat.sdl2.lua`、`compat.ffmpeg.lua` | `generated_files` + `include_dirs` | | **F. 共享库 compat** | 必须是进程里**唯一**的那个 `.so` —— 或因为会被第三方 `dlopen`,或因为生态 payload 链的是同一个 soname | `pkgs/c/compat.x11.lua` 等 X11 家族、`compat.vulkan.lua`、`compat.libdrm.lua`、`compat.libffi.lua`、`compat.expat.lua` | `targets = { kind = "shared", soname = … }` | | **G. 宿主运行时适配** | 驱动之类无法 vendor 的东西,只做符号链接农场 + 元数据 | `pkgs/c/compat.glx-runtime.lua`、`compat.vulkan-runtime.lua` | `runtime.library_dirs` / `capabilities` | diff --git a/pkgs/c/compat.wayland.lua b/pkgs/c/compat.wayland.lua deleted file mode 100644 index ea119622..00000000 --- a/pkgs/c/compat.wayland.lua +++ /dev/null @@ -1,199 +0,0 @@ --- compat.wayland — the Wayland core libraries: the client library a GUI --- application links (`wl_display_connect`, the `wl_registry` / proxy --- machinery), the server library a compositor links, the cursor-theme loader, --- and the `wl_egl_window` shim that binds a surface to EGL. --- --- It completes the display half of the stack these packages now cover: with --- compat.libdrm and compat.libgbm a program can allocate and scan out on a --- bare KMS console, and with this it can instead be a client of — or itself --- be — a Wayland compositor. --- --- ───────────────────────────────────────────────────────────────────────── --- SHAPE: a binding, same criterion as compat.libdrm --- --- Wayland is an independent freedesktop project with its own releases, so a --- source build would be defensible on the "separable unit" test. It is a --- binding for the second reason: `xim:wayland` already exists and Mesa depends --- on it (`libEGL_mesa` has a DT_NEEDED on `libwayland-client`, which is why --- mesa.lua lists it as a hard dependency rather than an option). A second --- `libwayland-client.so.0` in a process that also loads Mesa's EGL would mean --- two proxy tables for one connection. --- --- host 0 no /usr/lib* path, no escape-hatch variable --- ecosystem 1 `xim:wayland` --- index 0 `deps = {}` --- transitive 0 the wayland libs need only libc/libm/libffi, all --- resolved inside xim-x-* --- --- ───────────────────────────────────────────────────────────────────────── --- FOUR LIBRARIES, ONE DEFAULT ON THE LINK LINE --- --- The payload carries `libwayland-client`, `libwayland-server`, --- `libwayland-cursor` and `libwayland-egl`, and all four are harvested — the --- farm is on `-L`, so any of them can be linked. But `ldflags` names only --- `-lwayland-client`. --- --- That asymmetry is deliberate. A client is overwhelmingly the common case, --- and it is the one where getting it wrong is silent; a compositor author --- knows they need `-lwayland-server` and will say so. Putting all four in --- `ldflags` would instead force every consumer to carry the server library — --- and `ldflags` from a dependency reaches the consumer's link line, so there --- is no way for them to opt out short of not using this package. --- --- A consumer wanting more adds them to its own `[build] ldflags`, and they --- resolve out of this package's farm without any further declaration: --- --- [build] --- ldflags = ["-lwayland-server"] # or -lwayland-cursor, -lwayland-egl --- --- WHAT IS NOT HERE: the protocol XML and `wayland-scanner`. Real clients --- generate `xdg-shell` and friends from `wayland-protocols` at build time, and --- that is a code generator plus a data package — a different shape (the --- compat.protobuf `protoc` shape, a `kind = "bin"` target) and a separate --- package. This one is the runtime libraries and the core headers only, which --- is what `wl_display_connect` and the EGL platform need. -package = { - spec = "1", - namespace = "compat", - name = "wayland", - description = "Wayland core client/server libraries, bound to the ecosystem's xim:wayland", - licenses = {"MIT"}, - repo = "https://gitlab.freedesktop.org/wayland/wayland", - type = "package", - - xpm = { - linux = { - deps = { runtime = { "xim:wayland" } }, - ["2026.08.30"] = { - -- Inert anchor; nothing downloaded is read. See compat.libgbm - -- for why this is a README rather than a header. - url = { - GLOBAL = "https://gitlab.freedesktop.org/wayland/wayland/-/raw/1.23.1/README.md", - CN = "https://gitcode.com/mcpp-res/wayland/releases/download/2026.08.30/wayland-2026.08.30.md", - }, - sha256 = "147f133b07a9ea767e426944c7c5e3946d642cfbf392f63e28a37888b700fb54", - }, - }, - }, - - mcpp = { - language = "c++23", - import_std = false, - c_standard = "c11", - - include_dirs = { "mcpp_generated/wayland/include" }, - - generated_files = { - ["mcpp_generated/wayland_anchor.c"] = - "int mcpp_compat_wayland_anchor(void) { return 0; }\n", - }, - sources = { "mcpp_generated/wayland_anchor.c" }, - - -- NOT named `wayland`: a `libwayland.a` beside the real shared objects - -- would let search order decide. Same rule as compat.libgbm's - -- `gbm_binding`. - targets = { ["wayland_binding"] = { kind = "lib" } }, - - -- The client only; see the header comment. The other three are in the - -- farm and reachable through a consumer's own ldflags. - ldflags = { "-lwayland-client" }, - deps = {}, - - runtime = { - library_dirs = { "mcpp_generated/wayland/lib" }, - link_library_dirs = { "mcpp_generated/wayland/lib" }, - provides = { "wayland.client" }, - }, - }, -} - -import("xim.libxpkg.pkginfo") -import("xim.libxpkg.system") -import("xim.libxpkg.log") - -local log_path = nil - -local function say(msg) - if log_path == nil then return end - local prev = io.readfile(log_path) or "" - io.writefile(log_path, prev .. msg .. "\n") -end - -local function fail(msg) - say("FAILED: " .. msg) - log.error("[wayland] %s", msg) - return false -end - -local function sh_quote(value) - return "'" .. tostring(value):gsub("'", "'\\''") .. "'" -end - -local function link_matching(srcdir, pattern, outdir) - os.exec( - "for f in " .. sh_quote(srcdir) .. "/" .. pattern .. - "; do [ -e \"$f\" ] || continue; " .. - "ln -sf \"$f\" " .. sh_quote(outdir) .. "/\"$(basename \"$f\")\"; " .. - "done" - ) -end - -function install() - local prefix = pkginfo.install_dir() - os.mkdir(prefix) - - log_path = path.join(prefix, "mcpp_wayland_build.log") - io.writefile(log_path, "compat.wayland install()\n") - - local view = system.subos_sysrootdir() - say("subos view: " .. tostring(view)) - - local view_lib = path.join(view, "lib") - local view_inc = path.join(view, "usr", "include") - - local root = path.join(prefix, "mcpp_generated", "wayland") - local out_lib = path.join(root, "lib") - local out_inc = path.join(root, "include") - - os.mkdir(out_lib) - os.mkdir(out_inc) - - -- 1. All four libraries. Only the client is on the link line by default, - -- but the others must be PRESENT or a consumer's own - -- `-lwayland-server` would have nothing to resolve against. - say("linking libwayland-*.so* from " .. view_lib) - link_matching(view_lib, "libwayland-*.so*", out_lib) - - for _, required in ipairs({"libwayland-client.so", "libwayland-client.so.0"}) do - if not os.isfile(path.join(out_lib, required)) then - return fail(required .. " is not in this subos. The wayland " - .. "libraries come from `xim:wayland`, which this " - .. "package declares as a runtime dependency; if it is " - .. "declared and this still fires, that install did " - .. "not finish") - end - end - say("libwayland-client present") - - for _, bad in ipairs({"libc.so.6", "libm.so.6", "ld-linux-x86-64.so.2"}) do - if os.isfile(path.join(out_lib, bad)) then - return fail(bad .. " was linked into the wayland farm; it would " - .. "reach every consumer's RUNPATH and pair a second " - .. "libc with mcpp's loader") - end - end - - -- 2. The core headers, which upstream installs flat at the include root. - -- `wayland-client.h`, `-server.h`, `-cursor.h`, `-egl.h` and the - -- `-core`/`-protocol` halves they include. - say("linking wayland-*.h from " .. view_inc) - link_matching(view_inc, "wayland-*.h", out_inc) - if not os.isfile(path.join(out_inc, "wayland-client.h")) then - return fail("wayland-client.h is not in this subos (expected " - .. path.join(view_inc, "wayland-client.h") .. ")") - end - say("wayland headers present") - - say("done") - return true -end diff --git a/pkgs/f/freedesktop.wayland-scanner.lua b/pkgs/f/freedesktop.wayland-scanner.lua new file mode 100644 index 00000000..6b928130 --- /dev/null +++ b/pkgs/f/freedesktop.wayland-scanner.lua @@ -0,0 +1,45 @@ +-- freedesktop.wayland-scanner — the Wayland protocol code generator. +-- +-- Form A: the manifest lives in mcpplibs/wayland, a fork of freedesktop's +-- wayland 1.26.0 that adds mcpp build support and patches no upstream file. +-- +-- WHY A FORK RATHER THAN AN INLINE DESCRIPTOR +-- +-- wayland's libraries are mostly GENERATED — protocol/wayland.xml describes +-- every interface and wayland-scanner emits ~13,000 lines from it — and the +-- generator is a C program in the same tree, so it has to be COMPILED before it +-- can run. An inline descriptor has no build step, and an install() hook cannot +-- do it either: mcpp compiles a package's sources at CONSUMER-BUILD time, so no +-- package binary exists while another package is installing. `build.mcpp` is +-- the mechanism for exactly this, and it only exists for a real mcpp project. +-- Same shape and same reason as mcpplibs/grpc-m. +-- +-- One tarball backs four index entries, each pointing at a different workspace +-- member — the layout grpc/grpcgen/grpc-plugin already use. They are four +-- packages rather than one because `libwayland-client.so.0` and +-- `libwayland-server.so.0` are distinct SONAMEs that Mesa's libEGL_mesa needs +-- BOTH of, and mcpp links every library target in a package against all of its +-- sources — so one package cannot emit two libraries with disjoint contents. +package = { + spec = "1", + namespace = "freedesktop", + name = "wayland-scanner", + description = "wayland-scanner — the Wayland protocol code generator, built from source", + licenses = {"MIT"}, + repo = "https://github.com/mcpplibs/wayland", + type = "package", + + xpm = { + linux = { + ["1.26.0"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/wayland/archive/refs/tags/v1.26.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/wayland/releases/download/1.26.0/wayland-1.26.0.tar.gz", + }, + sha256 = "0a5dd54ab01e675c9a150774ade0c1262a7ad4285e0362327f26e5612d0a2f47", + }, + }, + }, + + mcpp = "*/mcpp/scanner/mcpp.toml", +} diff --git a/pkgs/f/freedesktop.wayland-server.lua b/pkgs/f/freedesktop.wayland-server.lua new file mode 100644 index 00000000..5b062195 --- /dev/null +++ b/pkgs/f/freedesktop.wayland-server.lua @@ -0,0 +1,45 @@ +-- freedesktop.wayland-server — libwayland-server, plus `import wayland.server;`. +-- +-- Form A: the manifest lives in mcpplibs/wayland, a fork of freedesktop's +-- wayland 1.26.0 that adds mcpp build support and patches no upstream file. +-- +-- WHY A FORK RATHER THAN AN INLINE DESCRIPTOR +-- +-- wayland's libraries are mostly GENERATED — protocol/wayland.xml describes +-- every interface and wayland-scanner emits ~13,000 lines from it — and the +-- generator is a C program in the same tree, so it has to be COMPILED before it +-- can run. An inline descriptor has no build step, and an install() hook cannot +-- do it either: mcpp compiles a package's sources at CONSUMER-BUILD time, so no +-- package binary exists while another package is installing. `build.mcpp` is +-- the mechanism for exactly this, and it only exists for a real mcpp project. +-- Same shape and same reason as mcpplibs/grpc-m. +-- +-- One tarball backs four index entries, each pointing at a different workspace +-- member — the layout grpc/grpcgen/grpc-plugin already use. They are four +-- packages rather than one because `libwayland-client.so.0` and +-- `libwayland-server.so.0` are distinct SONAMEs that Mesa's libEGL_mesa needs +-- BOTH of, and mcpp links every library target in a package against all of its +-- sources — so one package cannot emit two libraries with disjoint contents. +package = { + spec = "1", + namespace = "freedesktop", + name = "wayland-server", + description = "libwayland-server — the compositor side of the Wayland protocol, with a C++23 module wrapper", + licenses = {"MIT"}, + repo = "https://github.com/mcpplibs/wayland", + type = "package", + + xpm = { + linux = { + ["1.26.0"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/wayland/archive/refs/tags/v1.26.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/wayland/releases/download/1.26.0/wayland-1.26.0.tar.gz", + }, + sha256 = "0a5dd54ab01e675c9a150774ade0c1262a7ad4285e0362327f26e5612d0a2f47", + }, + }, + }, + + mcpp = "*/mcpp/server/mcpp.toml", +} diff --git a/pkgs/f/freedesktop.wayland-util.lua b/pkgs/f/freedesktop.wayland-util.lua new file mode 100644 index 00000000..4fcf02e7 --- /dev/null +++ b/pkgs/f/freedesktop.wayland-util.lua @@ -0,0 +1,45 @@ +-- freedesktop.wayland-util — wayland's public macros, as a C++23 module. +-- +-- Form A: the manifest lives in mcpplibs/wayland, a fork of freedesktop's +-- wayland 1.26.0 that adds mcpp build support and patches no upstream file. +-- +-- WHY A FORK RATHER THAN AN INLINE DESCRIPTOR +-- +-- wayland's libraries are mostly GENERATED — protocol/wayland.xml describes +-- every interface and wayland-scanner emits ~13,000 lines from it — and the +-- generator is a C program in the same tree, so it has to be COMPILED before it +-- can run. An inline descriptor has no build step, and an install() hook cannot +-- do it either: mcpp compiles a package's sources at CONSUMER-BUILD time, so no +-- package binary exists while another package is installing. `build.mcpp` is +-- the mechanism for exactly this, and it only exists for a real mcpp project. +-- Same shape and same reason as mcpplibs/grpc-m. +-- +-- One tarball backs four index entries, each pointing at a different workspace +-- member — the layout grpc/grpcgen/grpc-plugin already use. They are four +-- packages rather than one because `libwayland-client.so.0` and +-- `libwayland-server.so.0` are distinct SONAMEs that Mesa's libEGL_mesa needs +-- BOTH of, and mcpp links every library target in a package against all of its +-- sources — so one package cannot emit two libraries with disjoint contents. +package = { + spec = "1", + namespace = "freedesktop", + name = "wayland-util", + description = "wayland's macros as a C++23 module — constants, container_of, list/array ranges", + licenses = {"MIT"}, + repo = "https://github.com/mcpplibs/wayland", + type = "package", + + xpm = { + linux = { + ["1.26.0"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/wayland/archive/refs/tags/v1.26.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/wayland/releases/download/1.26.0/wayland-1.26.0.tar.gz", + }, + sha256 = "0a5dd54ab01e675c9a150774ade0c1262a7ad4285e0362327f26e5612d0a2f47", + }, + }, + }, + + mcpp = "*/mcpp/util/mcpp.toml", +} diff --git a/pkgs/f/freedesktop.wayland.lua b/pkgs/f/freedesktop.wayland.lua new file mode 100644 index 00000000..3a28049e --- /dev/null +++ b/pkgs/f/freedesktop.wayland.lua @@ -0,0 +1,45 @@ +-- freedesktop.wayland — libwayland-client, plus `import wayland.client;`. +-- +-- Form A: the manifest lives in mcpplibs/wayland, a fork of freedesktop's +-- wayland 1.26.0 that adds mcpp build support and patches no upstream file. +-- +-- WHY A FORK RATHER THAN AN INLINE DESCRIPTOR +-- +-- wayland's libraries are mostly GENERATED — protocol/wayland.xml describes +-- every interface and wayland-scanner emits ~13,000 lines from it — and the +-- generator is a C program in the same tree, so it has to be COMPILED before it +-- can run. An inline descriptor has no build step, and an install() hook cannot +-- do it either: mcpp compiles a package's sources at CONSUMER-BUILD time, so no +-- package binary exists while another package is installing. `build.mcpp` is +-- the mechanism for exactly this, and it only exists for a real mcpp project. +-- Same shape and same reason as mcpplibs/grpc-m. +-- +-- One tarball backs four index entries, each pointing at a different workspace +-- member — the layout grpc/grpcgen/grpc-plugin already use. They are four +-- packages rather than one because `libwayland-client.so.0` and +-- `libwayland-server.so.0` are distinct SONAMEs that Mesa's libEGL_mesa needs +-- BOTH of, and mcpp links every library target in a package against all of its +-- sources — so one package cannot emit two libraries with disjoint contents. +package = { + spec = "1", + namespace = "freedesktop", + name = "wayland", + description = "libwayland-client — the client side of the Wayland protocol, with a C++23 module wrapper", + licenses = {"MIT"}, + repo = "https://github.com/mcpplibs/wayland", + type = "package", + + xpm = { + linux = { + ["1.26.0"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/wayland/archive/refs/tags/v1.26.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/wayland/releases/download/1.26.0/wayland-1.26.0.tar.gz", + }, + sha256 = "0a5dd54ab01e675c9a150774ade0c1262a7ad4285e0362327f26e5612d0a2f47", + }, + }, + }, + + mcpp = "*/mcpp/client/mcpp.toml", +} diff --git a/tests/examples/wayland/mcpp.toml b/tests/examples/wayland/mcpp.toml index 736fec60..c1551bb6 100644 --- a/tests/examples/wayland/mcpp.toml +++ b/tests/examples/wayland/mcpp.toml @@ -1,25 +1,28 @@ -# Wayland test project. +# wayland test member. # -# Linux-only, dependency gated, no-op main() elsewhere — same shape as its -# neighbours. -# -# No compositor is required and none is assumed: the assertions cover the -# header/library seam and the ONE call that is meaningful without a server -# (`wl_display_connect` against a socket that is not there must fail cleanly -# rather than crash). CI runners have no WAYLAND_DISPLAY. -# -# The second binary checks the part this package deliberately does not put on -# the link line: libwayland-server is in the farm, so a consumer can reach it -# through its own ldflags. If the farm ever stops carrying it, that is a silent -# regression for compositor authors, and this is what catches it. -[package] -name = "wayland-tests" -version = "0.1.0" +# THIS MEMBER DECLARES ITS OWN [indices], and has to. The workspace root maps +# the `compat` namespace to this checkout, and an index is looked up BY +# NAMESPACE — so `freedesktop.*` would resolve against the published index +# rather than the packages under test. A member-level table REPLACES the root's +# rather than merging, which is what keeps this to one repo per member; the +# transitive `compat.libffi` these packages need therefore comes from the +# published index, which is correct: it is published. +[indices] +freedesktop = { path = "../../.." } -[target.'cfg(linux)'.dependencies.compat] -wayland = "2026.08.30" +[package] +name = "wayland-tests" +version = "0.1.0" +standard = "c++23" -# Exactly the escape hatch the descriptor documents: the package puts only -# -lwayland-client on the link line, and a consumer adds what else it needs. -[target.'cfg(linux)'.build] -ldflags = ["-lwayland-server"] +# Two packages, not one with an ldflags escape hatch. The client and the server +# are distinct SONAMEs — Mesa's libEGL_mesa carries DT_NEEDED on both — so they +# are two libraries with disjoint contents, and a compositor asks for the one it +# needs by name. +# +# wayland-util is the third: wayland's public MACROS, which no module can +# export, mapped to the entities they actually are. +[target.'cfg(linux)'.dependencies.freedesktop] +wayland = "1.26.0" +wayland-server = "1.26.0" +wayland-util = "1.26.0" diff --git a/tests/examples/wayland/tests/modules.cpp b/tests/examples/wayland/tests/modules.cpp new file mode 100644 index 00000000..8047a20b --- /dev/null +++ b/tests/examples/wayland/tests/modules.cpp @@ -0,0 +1,103 @@ +// The module wrappers, used the way a consumer would. +// +// The claim they make is narrow and testable: `import wayland.client;` gives +// you the same entities `#include ` does, spelled the same +// way. So this file includes NOTHING from wayland — no header — and still +// calls the stock API. If an export were missing, this would not compile; if a +// name had been renamed or wrapped, it would not compile either. +// +// tests/wayland.cpp is the header-based sibling. Both must pass: the module +// layer is an addition, not a replacement. + +#ifdef __linux__ + +#include +#include +#include + +import wayland.client; +import wayland.server; +import wayland.util; + +namespace { + +int failures = 0; + +void check(bool ok, const char *what) +{ + std::printf("%-58s %s\n", what, ok ? "ok" : "FAILED"); + if (!ok) ++failures; +} + +// A node linked through wl_list, the shape every wayland consumer writes. +struct listener_node { + int id; + wl_list link; +}; + +} // namespace + +int main() +{ + // ── 1. Client entities, with no #include anywhere ──────────────────── + { + wl_display *d = wl_display_connect("mcpp-no-such-compositor"); + check(d == nullptr, "wl_display_connect through the module returns NULL"); + + // A protocol interface object: generated code, exported by the module. + check(std::string(wl_registry_interface.name) == "wl_registry", + "wl_registry_interface arrives through wayland.client"); + check(std::string(wl_compositor_interface.name) == "wl_compositor", + "so does wl_compositor_interface"); + } + + // ── 2. Server entities ─────────────────────────────────────────────── + { + wl_display *s = wl_display_create(); + check(s != nullptr, "wl_display_create through wayland.server"); + if (s != nullptr) { + wl_event_loop *loop = wl_display_get_event_loop(s); + check(loop != nullptr, "wl_display_get_event_loop returns a loop"); + wl_display_destroy(s); + } + } + + // ── 3. The macros, as entities ─────────────────────────────────────── + // These are the names a module cannot export as macros; wayland.util + // carries them as a constant, a template and ranges. Exercised here in a + // consumer rather than only in the package's own test. + { + wl_list head; + head.prev = &head; + head.next = &head; + + listener_node a{1, {}}, b{2, {}}; + for (listener_node *n : {&a, &b}) { + wl_list *prev = head.prev; + n->link.prev = prev; n->link.next = &head; + prev->next = &n->link; head.prev = &n->link; + } + + std::vector seen; + for (listener_node *n : wl_list_each<&listener_node::link>(&head)) { + seen.push_back(n->id); + } + check(seen.size() == 2 && seen[0] == 1 && seen[1] == 2, + "wl_list_each walks a consumer's own list type"); + + check(wl_container_of<&listener_node::link>(&b.link) == &b, + "wl_container_of recovers the containing object"); + + check(WL_MARSHAL_FLAG_DESTROY == 1u, + "WL_MARSHAL_FLAG_DESTROY keeps upstream's value"); + } + + std::printf("\n%d check(s) failed\n", failures); + return failures == 0 ? 0 : 1; +} + +#else + +int main() { return 0; } + +#endif