diff --git a/docs/descriptor-examples.md b/docs/descriptor-examples.md index 979281b7..8eaa20ee 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, and the distinction is the whole design. **Why not a source build**: libgbm is a build target *inside* Mesa, not a project — `src/gbm/meson.build` is `link_with: [libloader]`, and `libloader` wants `idep_mesautil`, the whole of Mesa's internal util library (~120 TUs plus Python-generated tables) for exactly one function, `loader_open_driver_lib`. Building it would make this index re-import libdrm + expat + xcb + a Mesa-util carve-out to duplicate what `xim:mesa` has already resolved hermetically. Contrast `compat.vulkan`, which *does* build the Khronos loader from source — Khronos releases that as a standalone project, Mesa releases no such thing for GBM. **Zero host, with no escape hatch**: unlike its two neighbours it has no `/usr/lib*` path and no `MCPP_HOST_*` override, because host libgbm is a leak the ecosystem already closed — `xim:nvidia-gl-host-link` names it directly ("the table … was missing libm, libdrm, **libgbm**, libgcc_s … all of which were therefore coming from the HOST, silently, which is the leak this package exists to close"). NVIDIA's own GBM backend, if ever needed, belongs in that host-link layer rather than here. The measured surface is 1 ecosystem package (`xim:mesa`, not `xim:graphics`'s twenty-two), zero index deps, and zero transitive burden — libgbm.so.1's own RUNPATH resolves entirely inside `xim-x-*`. **What it deliberately does NOT do**: set the backend search path. libgbm is a loader — `gbm_create_device()` dlopens `/_gbm.so`, and Mesa's compiled-in `/usr/lib/gbm` is right on a distro and wrong the moment the payload is relocated. The mechanism to fix that is Mesa's own (`GBM_BACKENDS_PATH`) and the job belongs to the ENVIRONMENT, which is where every other relocated stack puts it — Valve's pressure-vessel answers the identical breakage with `GBM_BACKENDS_PATH=/run/host/usr/lib64/gbm` (steam-runtime#797), Nix and Conda set it at activation. In this ecosystem `xim:mesa` now declares it through the graphics discovery layer (openxlings/xim-pkgindex#713), so this package sets nothing, generates no TU and ships no header of its own. It briefly did carry a constructor that set the variable itself; that was a workaround for the missing declaration, and deleting it took the descriptor from 598 lines to 303. **Two directory keys, not one**: `library_dirs` renders as `-Wl,-rpath` and `link_library_dirs` as `-L`, so a package that is *linked against* (unlike glx-runtime/vulkan-runtime, whose farms are only dlopen'd) needs both — with `library_dirs` alone the farm is complete, the rpath correct, and the build still dies at `ld: cannot find -lgbm`. It ships **two** test binaries: `stock_usage.cpp` includes stock `` and nothing else, which is the minimal consumer and the tripwire on the two things outside this repo the package now depends on — xim-pkgindex's DISCOVERY row and mcpp's subos-env injection · [`compat.libdrm`](../pkgs/c/compat.libdrm.lua) (the layer under GBM — `drmModeAddFB2`/`drmModeSetCrtc` turn an allocated buffer into a scanout. Passes the separable-unit test that libgbm fails — libdrm is an independent freedesktop project and Conan carries a real recipe — but is still a binding for the second reason: `xim:libdrm` exists, Mesa's own payload has DT_NEEDED on it, and two `libdrm.so.2` in one process means two DRM handle tables. **Two include roots**, which is the thing that bites: the public headers sit at the include root and the uapi headers they include sit under `libdrm/`, and `xf86drm.h` line 40 is a bare `#include ` — expose one root and nothing compiles at all) · [`compat.egl`](../pkgs/c/compat.egl.lua) (what makes libgbm useful for RENDERING rather than only allocation: `eglGetPlatformDisplay(EGL_PLATFORM_GBM_KHR, gbm_device, NULL)`. Provider is libglvnd, not Mesa — EGL is a spec and the thing you link is a vendor-neutral dispatch library that must be the only one in the process. Ships **only** `EGL/` out of a payload that also carries `GL/`, `GLES2/`, `KHR/`: a third provider of `GL/` would make compat.glx-headers' documented two-provider race a three-way one, and `KHR/` comes from the index's existing `compat.khrplatform` instead — load-bearing, since `eglplatform.h` opens with `#include `. X11 is deliberately NOT a dependency: that include is `USE_X11`-gated, and forcing Xorg on headless GBM users would be exactly wrong) · [`compat.wayland`](../pkgs/c/compat.wayland.lua) (client, server, cursor and EGL shim all harvested, but only `-lwayland-client` on `ldflags`. A dependency's ldflags reach the consumer's link line with no way to opt out, so forcing the server library on every client would be unfixable downstream; a compositor author adds `-lwayland-server` themselves and it resolves out of the farm. The test member does exactly that, so the documented escape hatch has a regression guarding it. `wayland-scanner` and the protocol XML are NOT here — that is a code generator plus a data package, the `compat.protobuf` `protoc` shape, and a separate package)) +| 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) | +| 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/`) | | External build system (`install()` builds from source) | [`compat.openblas`](../pkgs/c/compat.openblas.lua) (Make) · [`compat.openssl`](../pkgs/c/compat.openssl.lua) (Perl Configure + Make, static libssl/libcrypto) | diff --git a/docs/package-types.md b/docs/package-types.md index 84a85c73..5dcab022 100644 --- a/docs/package-types.md +++ b/docs/package-types.md @@ -14,12 +14,44 @@ 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 | `pkgs/i/imgui.lua`, `pkgs/m/mcpplibs.*` | `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/g/grpc.lua` + `grpcgen.lua` + `grpc-plugin.lua` (three 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 (third parties `dlopen` it) | the X11 family such as `pkgs/c/compat.x11.lua`, and `compat.vulkan.lua` (linux) | `targets = { kind = "shared", soname = … }` | +| **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` | | **H. Host tool provider** | the upstream tarball also holds a **code generator** consumers run at build time | `pkgs/c/compat.protobuf.lua` (`protoc`) | a `targets` entry with `kind = "bin"` + `main`, plus `required_features` | -| **I. Ecosystem-stack binding** | the library is an internal build target of a project the **ecosystem already owns**, so vendoring it would fork that project | `pkgs/c/compat.libgbm.lua` (Mesa's GBM, via `xim:mesa`) | `xpm..deps.runtime = { "xim:" }` + a farm from `system.subos_sysrootdir()`, with `runtime.library_dirs` **and** `link_library_dirs` | +| **I. Ecosystem-stack binding** | the library is an internal build target of a project the **ecosystem already owns** and upstream ships no separable unit, so vendoring it would fork that project. NOT for libraries that merely coexist with a payload — see below | `pkgs/c/compat.libgbm.lua` (Mesa's GBM, via `xim:mesa`) | `xpm..deps.runtime = { "xim:" }` + a farm from `system.subos_sysrootdir()`, with `runtime.library_dirs` **and** `link_library_dirs` | + +### Source build or binding: the criterion is separability, and only that + +The index's default is to build from source. The only question is whether +upstream ships the thing as a **separable unit** — its own releases, buildable +without forking the project it lives in. + +`compat.libgbm` fails that test and is shape I: GBM is a target inside Mesa +(`src/gbm/meson.build` is `link_with: [libloader]`, and `libloader` pulls +`idep_mesautil` — ~120 TUs of Mesa's internal util library for one function), +and it is a loader whose backends are Mesa's own. `compat.libdrm` PASSES it and +is shape F, even though the ecosystem's Mesa payload also carries a +`libdrm.so.2`. + +**"The payload already has one" is not a reason to bind.** That was believed +here and is wrong, measured on mcpp 2026.8.29.1: + +- Examined alone, Mesa's `libgbm.so.1` resolves `libdrm.so.2` through its own + ABSOLUTE RUNPATH into `xim-x-libdrm//lib`. +- In a real consumer process that links `compat.libdrm`, the same `libgbm.so.1` + binds to the CONSUMER's copy instead, and exactly one `libdrm.so.2` is + mapped. Mesa's GBM then allocated a buffer through it. + +A DT_NEEDED soname already present in the link map is REUSED — ld.so never +searches again, so it never consults the payload's RUNPATH. The consumer links +the library directly, so it is mapped first and everything else follows it. + +This only holds for `kind = "shared"` with the canonical soname. Built as this +index's default `kind = "lib"` (objects merged into the consumer) there is no +`.so` to reuse: the payload's copy loads for Mesa and the consumer keeps its +own merged one, so the library's file-static state exists twice over one set of +handles. That is the failure the soname prevents, and why these packages set it. For the complete sample index, see [Descriptor examples by shape](descriptor-examples.md). diff --git a/docs/zh/descriptor-examples.md b/docs/zh/descriptor-examples.md index 786e44b9..7713597e 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`。真去建,就得让本索引把 libdrm + expat + xcb + 一块 Mesa-util 重新进口一遍,去复刻 `xim:mesa` 已经 hermetic 解析好的东西。对照 `compat.vulkan` —— 它**确实**从源码建 Khronos loader,因为 Khronos 把 loader 作为独立项目发布,而 Mesa 对 GBM 没有对应物。**零 host,且不留后门**:与相邻两个包不同,它没有 `/usr/lib*` 路径、也没有 `MCPP_HOST_*` 覆盖项 —— 因为 host libgbm 是生态**已经堵上**的泄漏,`xim:nvidia-gl-host-link` 里直接点了名("the table … was missing libm, libdrm, **libgbm**, libgcc_s … all of which were therefore coming from the HOST, silently, which is the leak this package exists to close")。NVIDIA 自家的 GBM 后端若将来需要,归那个 host-link 层管,不归这里。实测依赖面:生态 1 个包(`xim:mesa`,不是 `xim:graphics` 的 22 个)、索引依赖 0、传递负担 0 —— libgbm.so.1 自己的 RUNPATH 全落在 `xim-x-*` 内。**它刻意不做的事**:设置后端搜索路径。libgbm 是个 loader —— `gbm_create_device()` 会 dlopen `/_gbm.so`,而 Mesa 编译进去的 `/usr/lib/gbm` 在发行版上是对的、payload 一被重定位就是错的。修它的机制是 **Mesa 自己的**(`GBM_BACKENDS_PATH`),而这件事归**环境**管 —— 所有被重定位的栈都是这么做的:Valve 的 pressure-vessel 用 `GBM_BACKENDS_PATH=/run/host/usr/lib64/gbm` 解决同一个 bug(steam-runtime#797),Nix/Conda 在环境激活时设。在本生态里,`xim:mesa` 现在通过 graphics discovery 层声明它(openxlings/xim-pkgindex#713),所以这个包**什么都不设**、不生成 TU、也不带自己的头文件。它一度自带一个 constructor 去设这个变量,那是声明缺失时的权宜之计;删掉它让描述符从 598 行降到 303 行。**两个目录键都要**:`library_dirs` 渲染成 `-Wl,-rpath`、`link_library_dirs` 渲染成 `-L`,所以一个**会被链接**的包(不像 glx-runtime/vulkan-runtime 的农场只被 dlopen)两个都得写 —— 只写 `library_dirs` 的话,农场完整、rpath 正确,构建照样死在 `ld: cannot find -lgbm`。它带**两个**测试二进制:`stock_usage.cpp` 只 include 原样的 ``,是最小消费者,也是本仓之外那两件依赖的绊线 —— xim-pkgindex 的 DISCOVERY 行,以及 mcpp 的 subos-env 注入 · [`compat.libdrm`](../../pkgs/c/compat.libdrm.lua)(GBM 下面那一层 —— `drmModeAddFB2`/`drmModeSetCrtc` 把分配出来的 buffer 变成能扫描输出的画面。它**通过**了 libgbm 没通过的「可独立分发」判据(libdrm 是独立的 freedesktop 项目,Conan 也有真配方),但仍然做成绑定,理由是第二条:`xim:libdrm` 已存在、Mesa 自己的 payload 对它有 DT_NEEDED,一个进程里两份 `libdrm.so.2` 等于两张 DRM 句柄表。**两个 include 根**是真正会咬人的地方:公开头在 include 根、它们 include 的 uapi 头在 `libdrm/` 子目录,而 `xf86drm.h` 第 40 行就是裸的 `#include ` —— 只暴露一个根的话什么都编不过) · [`compat.egl`](../../pkgs/c/compat.egl.lua)(让 libgbm 从「只能分配」变成「能渲染」的那一环:`eglGetPlatformDisplay(EGL_PLATFORM_GBM_KHR, gbm_device, NULL)`。提供方是 libglvnd 而不是 Mesa —— EGL 是规范,真正链接的是厂商中立的 dispatch 库,而它必须是进程里唯一的一份。payload 里还有 `GL/`、`GLES2/`、`KHR/`,但本包**只**暴露 `EGL/`:再出一个 `GL/` 提供方会把 compat.glx-headers 记录过的「两个提供方」竞争变成三方;`KHR/` 则交给索引已有的 `compat.khrplatform` —— 这条依赖是必需的,因为 `eglplatform.h` 开头就 `#include `。X11 **刻意不**作为依赖:那个 include 被 `USE_X11` 门控,而给无显示的 headless GBM 用户强塞 Xorg 恰恰是错的) · [`compat.wayland`](../../pkgs/c/compat.wayland.lua)(client/server/cursor/egl 四个库全部收进 farm,但 `ldflags` 只放 `-lwayland-client`。依赖的 ldflags 会进消费者的链接行且无法退出,所以给每个客户端强塞 server 库是下游修不掉的;compositor 作者自己加 `-lwayland-server`,从 farm 里解析得到。测试成员就是这么写的,于是这条被文档化的退路有回归守着。`wayland-scanner` 与协议 XML **不在这里** —— 那是代码生成器加数据包,属于 `compat.protobuf` 的 `protoc` 形态,应当是另一个包)) +| 生态栈绑定(零 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 行代码)| +| 与 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) | | 外部构建系统(`install()` 从源码构建) | [`compat.openblas`](../../pkgs/c/compat.openblas.lua)(Make) · [`compat.openssl`](../../pkgs/c/compat.openssl.lua)(Perl Configure + Make,静态 libssl/libcrypto) | diff --git a/docs/zh/package-types.md b/docs/zh/package-types.md index 4133184c..fab543e7 100644 --- a/docs/zh/package-types.md +++ b/docs/zh/package-types.md @@ -12,12 +12,40 @@ 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 描述符,独立仓库 | `pkgs/i/imgui.lua`、`pkgs/m/mcpplibs.*` | `mcpp = ""`(Form A) | +| **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) | | **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`) | `pkgs/c/compat.x11.lua` 等 X11 家族、`compat.vulkan.lua`(linux) | `targets = { kind = "shared", soname = … }` | +| **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` | | **H. 宿主工具提供方** | 上游 tarball 里除了库,还带着消费者在构建期要跑的**代码生成器** | `pkgs/c/compat.protobuf.lua`(`protoc`) | `targets` 里一条 `kind = "bin"` + `main`,配 `required_features` | -| **I. 生态栈绑定** | 这个库是某个**生态已经拥有**的项目的内部 build target,vendor 它就等于 fork 那个项目 | `pkgs/c/compat.libgbm.lua`(Mesa 的 GBM,经 `xim:mesa`) | `xpm..deps.runtime = { "xim:" }` + 从 `system.subos_sysrootdir()` 建农场,`runtime.library_dirs` **和** `link_library_dirs` 都要 | +| **I. 生态栈绑定** | 这个库是某个**生态已经拥有**的项目的内部 build target,且上游不单独发布,vendor 它就等于 fork 那个项目。**不适用**于只是「和 payload 共存」的库 —— 见下 | `pkgs/c/compat.libgbm.lua`(Mesa 的 GBM,经 `xim:mesa`) | `xpm..deps.runtime = { "xim:" }` + 从 `system.subos_sysrootdir()` 建农场,`runtime.library_dirs` **和** `link_library_dirs` 都要 | + +### 源码构建还是绑定:判据只有「可否独立分发」 + +本索引的默认是源码构建。唯一要问的是上游有没有把它作为**可独立分发的单元** +发布 —— 有自己的 release,不 fork 宿主项目就能建出来。 + +`compat.libgbm` 不满足,所以是形态 I:GBM 是 Mesa 内部的 build target +(`src/gbm/meson.build` 写着 `link_with: [libloader]`,而 `libloader` 又要 +`idep_mesautil` —— 为一个函数拖进 Mesa 整个内部 util 库约 120 个 TU),而且它 +是个 loader,后端就是 Mesa 自己的。`compat.libdrm` **满足**,所以是形态 F, +哪怕生态的 Mesa payload 里也有一份 `libdrm.so.2`。 + +**「payload 里已经有一份」不构成绑定的理由。** 这里一度这么认为,是错的,已实测 +推翻(mcpp 2026.8.29.1): + +- 单独看 Mesa 的 `libgbm.so.1`,它通过自己的**绝对 RUNPATH** 把 `libdrm.so.2` + 解析到 `xim-x-libdrm//lib`。 +- 但在真实消费者进程里(消费者链了 `compat.libdrm`),同一个 `libgbm.so.1` + 绑到的是**消费者那份**,进程里只映射了一份 `libdrm.so.2`;Mesa 的 GBM 就用它 + 分配出了 buffer。 + +原因是:DT_NEEDED 的 soname 一旦已在 link map 中就会被**复用**,ld.so 不再搜索, +自然也不会去看 payload 的 RUNPATH。消费者直接链接的库先被映射,其余的跟着它走。 + +这只在 `kind = "shared"` **且 soname 正确**时成立。若用本索引默认的 +`kind = "lib"`(对象并进消费者),就没有 `.so` 可复用:payload 那份照常为 Mesa +加载,消费者另有一份合并进来的,于是库的 file-static 状态在同一批句柄上分成两套。 +这正是 soname 要避免的失败,也是这些包为什么要设它。 完整的样例索引见[描述符示例总览(按形态)](descriptor-examples.md)。 diff --git a/mcpp.toml b/mcpp.toml index 125581ec..6833e51f 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -82,6 +82,8 @@ members = [ "tests/examples/glad", "tests/examples/libaio", "tests/examples/libdrm", + "tests/examples/libffi", + "tests/examples/expat", "tests/examples/wayland", "tests/examples/egl", "tests/examples/libgbm", diff --git a/pkgs/c/compat.egl.lua b/pkgs/c/compat.egl.lua index 82a30645..124195ff 100644 --- a/pkgs/c/compat.egl.lua +++ b/pkgs/c/compat.egl.lua @@ -68,13 +68,22 @@ package = { xpm = { linux = { - deps = { runtime = { "xim:libglvnd" } }, - ["2026.08.30"] = { + -- Pinned so the version below stays true: this package's version + -- means "the EGL dispatch libglvnd 1.7.0 ships", and a floating + -- payload would silently invalidate that. + deps = { runtime = { "xim:libglvnd@1.7.0.1" } }, + -- libglvnd's version, not EGL's. The spec level is 1.5 and is a + -- property of the dispatch, not of this package; what a consumer + -- pins here is which libglvnd it links. `compat.glx-headers` uses + -- the same number for the same reason. No binding-revision suffix: + -- `egl` != `libglvnd`, so there is no store collision to dodge — + -- see compat.libdrm for the case where there is. + ["1.7.0"] = { -- Inert anchor; nothing downloaded is read. See -- compat.libgbm for why this is a README and not a header. url = { GLOBAL = "https://raw.githubusercontent.com/NVIDIA/libglvnd/v1.7.0/README.md", - CN = "https://gitcode.com/mcpp-res/egl/releases/download/2026.08.30/egl-2026.08.30.md", + CN = "https://gitcode.com/mcpp-res/egl/releases/download/1.7.0/egl-1.7.0.md", }, sha256 = "f84a3eca98cc5bdf5318741124c38c5e877f856df8c7e229ee5065e5c61038c2", }, diff --git a/pkgs/c/compat.expat.lua b/pkgs/c/compat.expat.lua new file mode 100644 index 00000000..3d3a82c6 --- /dev/null +++ b/pkgs/c/compat.expat.lua @@ -0,0 +1,260 @@ +-- compat.expat — Expat, built from source. +-- +-- The stream-oriented XML parser. It is in this index because +-- `wayland-scanner` parses `protocol/wayland.xml` with it, but Expat is a +-- general-purpose library and is packaged as one. +-- +-- ───────────────────────────────────────────────────────────────────────── +-- SHAPE: SOURCE BUILD (Expat is a separable project) +-- +-- Independent upstream with its own releases, so the index's default rule +-- applies. +-- +-- The ecosystem carries `xim:expat` at 2.6.2 as a transitive of `xim:mesa`. +-- That does not make this a binding — see compat.libdrm for why coexistence is +-- fine — but it does constrain the VERSION: the store's installed-check +-- matches (name, version) ignoring the namespace, so `compat.expat@2.6.2` +-- would be shadowed and would silently never install. 2.7.1 is the current +-- upstream release and does not collide. +-- +-- host 0 +-- ecosystem 0 no `xim:*` dependency +-- index 0 `deps = {}` +-- transitive 0 libexpat.so.1 needs only libc +-- +-- ───────────────────────────────────────────────────────────────────────── +-- THREE TRANSLATION UNITS, NOT FIVE +-- +-- `lib/` holds five `.c` files and upstream compiles three of them: +-- `xmltok_impl.c` and `xmltok_ns.c` are `#include`d BY `xmltok.c` (three and +-- two times respectively, each time with different macros set) rather than +-- compiled on their own. Listing them as sources would compile them once more +-- with no macros defined and produce duplicate symbols. +-- +-- ───────────────────────────────────────────────────────────────────────── +-- expat_config.h IS INLINED +-- +-- It is configure's record of what it probed and cannot be derived from the +-- tarball, so it is reproduced below verbatim from a real `./configure` run on +-- x86_64 linux-gnu, minus the lines that name the build host. The entries that +-- matter behaviorally are `XML_DTD`, `XML_GE`, `XML_NS` and +-- `XML_CONTEXT_BYTES`: they are feature switches, not probes, and turning any +-- of them off silently changes what the parser accepts. +package = { + spec = "1", + namespace = "compat", + name = "expat", + description = "Expat — stream-oriented XML parser, built from the upstream release", + licenses = {"MIT"}, + repo = "https://github.com/libexpat/libexpat", + type = "package", + + xpm = { + linux = { + ["2.7.1"] = { + url = { + GLOBAL = "https://github.com/libexpat/libexpat/releases/download/R_2_7_1/expat-2.7.1.tar.xz", + CN = "https://gitcode.com/mcpp-res/expat/releases/download/2.7.1/expat-2.7.1.tar.xz", + }, + sha256 = "354552544b8f99012e5062f7d570ec77f14b412a3ff5c7d8d0dae62c0d217c30", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c11", + + -- `lib` carries both the public headers (expat.h, expat_external.h) + -- and the private ones the three TUs include; `expat_config.h` is + -- generated into it so one entry covers the build and consumers. + include_dirs = { "lib" }, + + -- No config-selection macro: xmlparse.c does a plain + -- `#include "expat_config.h"`, and generating it into `lib/` (rather + -- than the top level, where upstream puts it behind a `-I..`) means the + -- quote-form lookup finds it in its own directory. + cflags = { + "-D_GNU_SOURCE", + "-fPIC", + }, + + sources = { + "lib/xmlparse.c", + "lib/xmltok.c", + "lib/xmlrole.c", + }, + + targets = { ["expat"] = { kind = "shared", soname = "libexpat.so.1" } }, + deps = {}, + + generated_files = { + ["lib/expat_config.h"] = +[[ +/* expat_config.h. Generated from expat_config.h.in by configure. */ +/* expat_config.h.in. Generated from configure.ac by autoheader. */ + +#ifndef EXPAT_CONFIG_H +#define EXPAT_CONFIG_H 1 + +/* Define if building universal (internal helper macro) */ +/* #undef AC_APPLE_UNIVERSAL_BUILD */ + +/* 1234 = LILENDIAN, 4321 = BIGENDIAN */ +#define BYTEORDER 1234 + +/* Define to 1 if you have the `arc4random' function. */ +/* #undef HAVE_ARC4RANDOM */ + +/* Define to 1 if you have the `arc4random_buf' function. */ +#define HAVE_ARC4RANDOM_BUF 1 + +/* define if the compiler supports basic C++11 syntax */ +/* #undef HAVE_CXX11 */ + +/* Define to 1 if you have the header file. */ +#define HAVE_DLFCN_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_FCNTL_H 1 + +/* Define to 1 if you have the `getpagesize' function. */ +#define HAVE_GETPAGESIZE 1 + +/* Define to 1 if you have the `getrandom' function. */ +#define HAVE_GETRANDOM 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the `bsd' library (-lbsd). */ +/* #undef HAVE_LIBBSD */ + +/* Define to 1 if you have a working `mmap' system call. */ +#define HAVE_MMAP 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDIO_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have `syscall' and `SYS_getrandom'. */ +#define HAVE_SYSCALL_GETRANDOM 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_PARAM_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to the sub-directory where libtool stores uninstalled libraries. */ + +/* Name of package */ + +/* Define to the address where bug reports for this package should be sent. */ + +/* Define to the full name of this package. */ + +/* Define to the full name and version of this package. */ + +/* Define to the one symbol short name of this package. */ + +/* Define to the home page for this package. */ + +/* Define to the version of this package. */ + +/* Define to 1 if all of the C90 standard headers exist (not just the ones + required in a freestanding environment). This macro is provided for + backward compatibility; new code need not use it. */ +#define STDC_HEADERS 1 + +/* Version number of package */ +#define VERSION "2.7.1" + +/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most + significant byte first (like Motorola and SPARC, unlike Intel). */ +#if defined AC_APPLE_UNIVERSAL_BUILD +# if defined __BIG_ENDIAN__ +# define WORDS_BIGENDIAN 1 +# endif +#else +# ifndef WORDS_BIGENDIAN +/* # undef WORDS_BIGENDIAN */ +# endif +#endif + +/* Define to allow retrieving the byte offsets for attribute names and values. + */ +/* #undef XML_ATTR_INFO */ + +/* Define to specify how much context to retain around the current parse + point, 0 to disable. */ +#define XML_CONTEXT_BYTES 1024 + +/* Define to include code reading entropy from `/dev/urandom'. */ +#define XML_DEV_URANDOM 1 + +/* Define to make parameter entity parsing functionality available. */ +#define XML_DTD 1 + +/* Define as 1/0 to enable/disable support for general entities. */ +#define XML_GE 1 + +/* Define to make XML Namespaces functionality available. */ +#define XML_NS 1 + +/* Define to empty if `const' does not conform to ANSI C. */ +/* #undef const */ + +/* Define to `long int' if does not define. */ +/* #undef off_t */ + +#endif // ndef EXPAT_CONFIG_H +]], + }, + }, +} + +import("xim.libxpkg.pkginfo") +import("xim.libxpkg.log") + +-- Once a descriptor defines install(), the archive is extracted to a sibling of +-- the install dir rather than into it; moving it is the descriptor's job. Same +-- opening as compat.libdrm, compat.libffi and compat.xcb. +function install() + local srcroot = pkginfo.install_file():replace(".tar.xz", "") + if not os.isdir(srcroot) then + srcroot = "expat-" .. pkginfo.version() + end + if not os.isdir(srcroot) then + log.error("[expat] extracted tree not found (looked for %s)", srcroot) + return false + end + + os.tryrm(pkginfo.install_dir()) + os.mv(srcroot, pkginfo.install_dir()) + + if not os.isfile(path.join(pkginfo.install_dir(), "lib", "expat.h")) then + log.error("[expat] lib/expat.h missing from the release tarball") + return false + end + + return true +end diff --git a/pkgs/c/compat.libdrm.lua b/pkgs/c/compat.libdrm.lua index 4922b614..4216b365 100644 --- a/pkgs/c/compat.libdrm.lua +++ b/pkgs/c/compat.libdrm.lua @@ -1,241 +1,264 @@ --- compat.libdrm — libdrm, the userspace wrapper over the kernel's DRM ioctls: --- `drmOpen`/`drmGetVersion`, the whole `drmMode*` KMS family (connectors, CRTCs, --- framebuffers, page flips), PRIME import/export, and the `drm.h` / --- `drm_mode.h` / `drm_fourcc.h` uapi headers. +-- compat.libdrm — libdrm, built from source. -- --- It is the layer directly under compat.libgbm: GBM allocates a buffer, and --- libdrm is what turns that buffer into something a display controller --- scans out (`drmModeAddFB2` + `drmModeSetCrtc`). Without it a consumer can --- allocate and never present. +-- The userspace side of the kernel DRM/KMS interface: `drmModeGetResources`, +-- `drmModeAddFB2`, `drmModeSetCrtc`, `drmPrimeHandleToFD`. It is the layer +-- under compat.libgbm — GBM allocates the buffer, this turns it into a scanout. -- -- ───────────────────────────────────────────────────────────────────────── --- SHAPE: the compat.libgbm binding, and the criterion is the same --- --- The index's rule is "build it from source", and the question is always --- whether upstream ships the thing as a separable unit. libdrm PASSES that --- test — it is an independent freedesktop project with its own releases, and --- Conan carries it as a real recipe rather than a `system` virtual package. --- So a source build would be legitimate here, unlike compat.libgbm where the --- library is a target inside Mesa. --- --- It is nevertheless a BINDING, for the second criterion rather than the --- first: `xim:libdrm` already exists, mesa depends on it, and it is already --- installed in any subos that has a graphics stack. Building a second copy --- would put two `libdrm.so.2` in reach of one process — and this is the one --- library where that matters most, because Mesa's own payload --- (`libgbm.so.1`, `libgallium`, the Vulkan ICDs) has DT_NEEDED on the --- ecosystem's copy. A consumer linking ours while Mesa loads the ecosystem's --- would get two DRM handle tables in one address space. --- --- Measured surface, the same four axes compat.libgbm reports: +-- SHAPE: A SOURCE BUILD, BECAUSE THE PROJECT IS SEPARABLE +-- +-- The index's rule is "build it from source", and the test is whether upstream +-- ships the thing as a separable unit. libdrm PASSES: it is an independent +-- freedesktop project with its own release tarballs, and Conan carries it as a +-- real recipe rather than a `system` virtual package. So it is built here, +-- like the X11 family (compat.x11, compat.xcb, compat.xext, …) that this +-- index already builds beside the same ecosystem payloads. +-- +-- Contrast compat.libgbm, which is NOT separable: GBM is a build target inside +-- Mesa (`src/gbm/meson.build` is `link_with: [libloader]`, and `libloader` +-- pulls `idep_mesautil`), and it is a loader whose backends are Mesa's own. +-- +-- Measured surface: -- -- host 0 no /usr/lib* path, no escape-hatch variable --- ecosystem 1 `xim:libdrm` — not `xim:mesa`, which would drag the --- whole GL stack in for a consumer that only wants ioctls +-- ecosystem 0 nothing — no `xim:*` dependency at all -- index 0 `deps = {}` --- transitive 0 libdrm.so.2 needs only libc/libm, both from the payload +-- transitive 0 the five TUs need only libc; `dep_rt` folds into +-- glibc and valgrind/udev are compiled out below -- -- ───────────────────────────────────────────────────────────────────────── --- TWO INCLUDE ROOTS, AND THIS IS THE ONE THING THAT BITES +-- 2.4.134 — THE CURRENT UPSTREAM RELEASE, AND NEWER THAN THE PAYLOAD'S +-- +-- `xim:mesa` pulls `xim:libdrm` at 2.4.123. Being ahead of it is fine and is +-- what the soname is for: libdrm has kept `libdrm.so.2` since 2012 and is +-- backward compatible within it, so Mesa — built against 2.4.123 — binds to +-- this one and works. That is the direction a distribution upgrade moves too. -- --- libdrm installs its public headers at the include ROOT (`xf86drm.h`, --- `xf86drmMode.h`, `libsync.h`) but the uapi headers they include in a --- `libdrm/` SUBDIRECTORY (`drm.h`, `drm_mode.h`, `drm_fourcc.h`, …). And --- `xf86drm.h` line 40 is a bare `#include `. +-- ───────────────────────────────────────────────────────────────────────── +-- WHY `kind = "shared"` WITH A SONAME IS LOAD-BEARING -- --- So one include root is not enough. Measured while writing compat.libgbm's --- test, which tried exactly that: +-- Mesa is normally in the same process — `xim:mesa` declares `xim:libdrm`, and +-- seven libraries in its payload carry DT_NEEDED on `libdrm.so.2`, including +-- the `libgbm.so.1` that compat.libgbm delivers. Those Mesa libraries also +-- carry an ABSOLUTE RUNPATH naming `xim-x-libdrm//lib`. -- --- xf86drm.h:40:10: fatal error: drm.h: No such file or directory +-- That RUNPATH does NOT decide the outcome, and the distinction is the whole +-- reason a source build is safe here. A DT_NEEDED soname already present in +-- the link map is REUSED — ld.so never searches for it again, so it never +-- consults Mesa's RUNPATH. The consumer links this library directly, so it is +-- mapped first, and Mesa's libgbm then binds to it. Measured: in a real +-- consumer process `libdrm.so.2` resolves to the consumer's path, while the +-- same libgbm.so.1 examined ALONE resolves to the payload's. +-- +-- This only holds if the soname matches. Built `kind = "lib"` — this index's +-- default, objects merged into the consumer — there is no `libdrm.so.2` to +-- reuse: Mesa loads the payload copy and the consumer keeps its own merged +-- one, so libdrm's file-static state (`drmHashTable`, `nr_fds`, `connection`, +-- `drm_server_info` are all OBJECTs in its .bss) exists twice over one set of +-- fds. Shared, with the canonical soname, there is exactly one. +-- +-- Same shape and same reason as compat.vulkan's loader and the X11 family. +-- +-- ───────────────────────────────────────────────────────────────────────── +-- TWO INCLUDE ROOTS, AND THIS IS THE ONE THING THAT BITES -- --- Upstream's own `libdrm.pc` says `Cflags: -I${includedir}/libdrm`, and the --- root is on the path by default, so a pkg-config consumer gets both. This --- package therefore exposes BOTH directories — the root for `` --- and `libdrm/` for the `` it pulls in. +-- libdrm keeps its public headers at the source ROOT (`xf86drm.h`, +-- `xf86drmMode.h`, `libsync.h`) and the uapi headers those include in +-- `include/drm/` (`drm.h`, `drm_mode.h`, `drm_fourcc.h`, …) — upstream installs +-- the second set into `/include/libdrm/` and puts BOTH on the pkg-config +-- include path. `xf86drm.h` line 40 is a bare `#include `, so a consumer +-- given only the root cannot compile a single translation unit. -- --- The vendor libraries (`libdrm_amdgpu`, `libdrm_intel`, `libdrm_nouveau`, --- `libdrm_radeon`) are deliberately NOT harvested. They are separate `-l` --- names with their own headers, only meaningful to code targeting one GPU --- family, and nothing in the generic KMS path touches them. Adding them would --- put four more sonames on every consumer's link line for no one's benefit. +-- ───────────────────────────────────────────────────────────────────────── +-- THE BUILD, TRANSCRIBED FROM meson.build +-- +-- Upstream force-includes a generated `config.h` (`add_project_arguments( +-- '-include', … / 'config.h')`). The five core TUs read exactly five entries +-- from it, so they are passed as `-D` instead of shipping a header — with one +-- trap: `MAJOR_IN_MKDEV` and `MAJOR_IN_SYSMACROS` are tested with `#ifdef`, +-- not `#if`, so the MKDEV one must be ABSENT rather than defined to 0. +-- `HAVE_SYS_SYSCTL_H` and `UDEV` are `#if`, so they must be present and zero. +-- +-- The one genuinely generated file is `generated_static_table_fourcc.h`, from +-- `gen_table_fourcc.py` from `include/drm/drm_fourcc.h`. It is not in the +-- release tarball, it is 58 lines of table, and `xf86drm.c` includes it in +-- QUOTE form — so it is inlined below into the source root, where the quoted +-- lookup finds it without adding any include path. package = { spec = "1", namespace = "compat", name = "libdrm", - description = "libdrm — userspace DRM/KMS ioctl wrapper, bound to the ecosystem's xim:libdrm", + description = "libdrm — userspace DRM/KMS interface, built from the upstream release", licenses = {"MIT"}, repo = "https://gitlab.freedesktop.org/mesa/drm", type = "package", xpm = { linux = { - -- PLATFORM level, beside the version entries rather than inside - -- one: compat.glx-runtime established that a per-version `deps` - -- parses fine and never installs. - deps = { runtime = { "xim:libdrm" } }, - ["2026.08.30"] = { - -- Inert anchor. Nothing downloaded here is read — the payload - -- is what install() links out of the subos view. The xpm schema - -- wants a url + sha256 per version, and a README cannot be - -- mistaken for a shipped header (compat.libgbm learned that one - -- the hard way by anchoring on a `.h`). + ["2.4.134"] = { url = { - GLOBAL = "https://gitlab.freedesktop.org/mesa/drm/-/raw/libdrm-2.4.123/README.rst", - CN = "https://gitcode.com/mcpp-res/libdrm/releases/download/2026.08.30/libdrm-2026.08.30.rst", + GLOBAL = "https://dri.freedesktop.org/libdrm/libdrm-2.4.134.tar.xz", + CN = "https://gitcode.com/mcpp-res/libdrm/releases/download/2.4.134/libdrm-2.4.134.tar.xz", }, - sha256 = "46183785b2f012d0773646d1974374cbfc754f043d1a423afb0ffea0af2569c1", + sha256 = "ac5e74d157830eb8bee44c6a6bf3ad49774ef0dd2a72bdad74a8f20308b52a95", }, }, }, mcpp = { - language = "c++23", - import_std = false, - c_standard = "c11", + language = "c++23", + import_std = false, + c_standard = "c11", - -- Both roots, for the reason in the header comment: `` from - -- the first, the `` it includes from the second. - include_dirs = { - "mcpp_generated/libdrm/include", - "mcpp_generated/libdrm/include/libdrm", + -- `c_standard = "gnu11"` is accepted and silently emits -std=c11, so + -- _GNU_SOURCE has to be spelled out. Without it O_CLOEXEC, `asprintf` + -- and `major`/`minor` are hidden and xf86drm.c does not compile. + -- -fPIC because this becomes a .so; -fvisibility=hidden to match + -- upstream's `gnu_symbol_visibility : 'hidden'`, so only the symbols + -- libdrm_macros.h marks drm_public are exported. + cflags = { + "-D_GNU_SOURCE", + "-DHAVE_VISIBILITY=1", + "-DHAVE_SYS_SYSCTL_H=0", + "-DMAJOR_IN_SYSMACROS=1", + "-DUDEV=0", + "-fPIC", + "-fvisibility=hidden", }, - generated_files = { - ["mcpp_generated/libdrm_anchor.c"] = - "int mcpp_compat_libdrm_anchor(void) { return 0; }\n", + -- Both roots; see the header comment. `mcpp/include` is the copy + -- install() makes of the three root headers, mirroring what upstream + -- puts in `/include`; `include/drm` is upstream's + -- `/include/libdrm` and is needed by the BUILD too, because + -- xf86drm.c includes "drm_fourcc.h" in quote form from the root. + include_dirs = { + "mcpp/include", + "include/drm", }, - sources = { "mcpp_generated/libdrm_anchor.c" }, - -- NOT named `drm`: a target called `drm` would put a `libdrm.a` on the - -- link line beside the real `libdrm.so`, and which one `-ldrm` picks - -- would come down to search order. Same rule as compat.libgbm's - -- `gbm_binding`. - targets = { ["drm_binding"] = { kind = "lib" } }, + -- meson.build's `libdrm_files`, verbatim. + sources = { + "xf86drm.c", + "xf86drmHash.c", + "xf86drmRandom.c", + "xf86drmSL.c", + "xf86drmMode.c", + }, - ldflags = { "-ldrm" }, + targets = { ["drm"] = { kind = "shared", soname = "libdrm.so.2" } }, deps = {}, - runtime = { - -- Two keys, two flags, not interchangeable: `library_dirs` renders - -- as `-Wl,-rpath` and `link_library_dirs` as `-L`. A package that - -- is LINKED against needs both — with only the first, the farm is - -- complete, the rpath correct, and the build dies at - -- `ld: cannot find -ldrm`. (compat.glx-runtime and - -- compat.vulkan-runtime declare only `library_dirs` because - -- nothing links against their farms.) - library_dirs = { "mcpp_generated/libdrm/lib" }, - link_library_dirs = { "mcpp_generated/libdrm/lib" }, - provides = { "drm.libdrm" }, + generated_files = { + ["generated_static_table_fourcc.h"] = +[[ +/* AUTOMATICALLY GENERATED by gen_table_fourcc.py. You should modify + that script instead of adding here entries manually! */ +static const struct drmFormatModifierInfo drm_format_modifier_table[] = { + { DRM_MODIFIER_INVALID(NONE, INVALID) }, + { DRM_MODIFIER_LINEAR(NONE, LINEAR) }, + { DRM_MODIFIER_INTEL(X_TILED, X_TILED) }, + { DRM_MODIFIER_INTEL(Y_TILED, Y_TILED) }, + { DRM_MODIFIER_INTEL(Yf_TILED, Yf_TILED) }, + { DRM_MODIFIER_INTEL(Y_TILED_CCS, Y_TILED_CCS) }, + { DRM_MODIFIER_INTEL(Yf_TILED_CCS, Yf_TILED_CCS) }, + { DRM_MODIFIER_INTEL(Y_TILED_GEN12_RC_CCS, Y_TILED_GEN12_RC_CCS) }, + { DRM_MODIFIER_INTEL(Y_TILED_GEN12_MC_CCS, Y_TILED_GEN12_MC_CCS) }, + { DRM_MODIFIER_INTEL(Y_TILED_GEN12_RC_CCS_CC, Y_TILED_GEN12_RC_CCS_CC) }, + { DRM_MODIFIER_INTEL(4_TILED, 4_TILED) }, + { DRM_MODIFIER_INTEL(4_TILED_DG2_RC_CCS, 4_TILED_DG2_RC_CCS) }, + { DRM_MODIFIER_INTEL(4_TILED_DG2_MC_CCS, 4_TILED_DG2_MC_CCS) }, + { DRM_MODIFIER_INTEL(4_TILED_DG2_RC_CCS_CC, 4_TILED_DG2_RC_CCS_CC) }, + { DRM_MODIFIER_INTEL(4_TILED_MTL_RC_CCS, 4_TILED_MTL_RC_CCS) }, + { DRM_MODIFIER_INTEL(4_TILED_MTL_MC_CCS, 4_TILED_MTL_MC_CCS) }, + { DRM_MODIFIER_INTEL(4_TILED_MTL_RC_CCS_CC, 4_TILED_MTL_RC_CCS_CC) }, + { DRM_MODIFIER_INTEL(4_TILED_LNL_CCS, 4_TILED_LNL_CCS) }, + { DRM_MODIFIER_INTEL(4_TILED_BMG_CCS, 4_TILED_BMG_CCS) }, + { DRM_MODIFIER(SAMSUNG, 64_32_TILE, 64_32_TILE) }, + { DRM_MODIFIER(SAMSUNG, 16_16_TILE, 16_16_TILE) }, + { DRM_MODIFIER(QCOM, COMPRESSED, COMPRESSED) }, + { DRM_MODIFIER(QCOM, TILED3, TILED3) }, + { DRM_MODIFIER(QCOM, TILED2, TILED2) }, + { DRM_MODIFIER(VIVANTE, TILED, TILED) }, + { DRM_MODIFIER(VIVANTE, SUPER_TILED, SUPER_TILED) }, + { DRM_MODIFIER(VIVANTE, SPLIT_TILED, SPLIT_TILED) }, + { DRM_MODIFIER(VIVANTE, SPLIT_SUPER_TILED, SPLIT_SUPER_TILED) }, + { DRM_MODIFIER(NVIDIA, TEGRA_TILED, TEGRA_TILED) }, + { DRM_MODIFIER(NVIDIA, 16BX2_BLOCK_ONE_GOB, 16BX2_BLOCK_ONE_GOB) }, + { DRM_MODIFIER(NVIDIA, 16BX2_BLOCK_TWO_GOB, 16BX2_BLOCK_TWO_GOB) }, + { DRM_MODIFIER(NVIDIA, 16BX2_BLOCK_FOUR_GOB, 16BX2_BLOCK_FOUR_GOB) }, + { DRM_MODIFIER(NVIDIA, 16BX2_BLOCK_EIGHT_GOB, 16BX2_BLOCK_EIGHT_GOB) }, + { DRM_MODIFIER(NVIDIA, 16BX2_BLOCK_SIXTEEN_GOB, 16BX2_BLOCK_SIXTEEN_GOB) }, + { DRM_MODIFIER(NVIDIA, 16BX2_BLOCK_THIRTYTWO_GOB, 16BX2_BLOCK_THIRTYTWO_GOB) }, + { DRM_MODIFIER(BROADCOM, VC4_T_TILED, VC4_T_TILED) }, + { DRM_MODIFIER(BROADCOM, SAND32, SAND32) }, + { DRM_MODIFIER(BROADCOM, SAND64, SAND64) }, + { DRM_MODIFIER(BROADCOM, SAND128, SAND128) }, + { DRM_MODIFIER(BROADCOM, SAND256, SAND256) }, + { DRM_MODIFIER(BROADCOM, UIF, UIF) }, + { DRM_MODIFIER(ARM, 16X16_BLOCK_U_INTERLEAVED, 16X16_BLOCK_U_INTERLEAVED) }, + { DRM_MODIFIER(ARM, INTERLEAVED_64K, INTERLEAVED_64K) }, + { DRM_MODIFIER(ALLWINNER, TILED, TILED) }, + { DRM_MODIFIER(APPLE, GPU_TILED, GPU_TILED) }, + { DRM_MODIFIER(APPLE, GPU_TILED_COMPRESSED, GPU_TILED_COMPRESSED) }, +}; +static const struct drmFormatModifierVendorInfo drm_format_modifier_vendor_table[] = { + { DRM_FORMAT_MOD_VENDOR_NONE, "NONE" }, + { DRM_FORMAT_MOD_VENDOR_INTEL, "INTEL" }, + { DRM_FORMAT_MOD_VENDOR_AMD, "AMD" }, + { DRM_FORMAT_MOD_VENDOR_NVIDIA, "NVIDIA" }, + { DRM_FORMAT_MOD_VENDOR_SAMSUNG, "SAMSUNG" }, + { DRM_FORMAT_MOD_VENDOR_QCOM, "QCOM" }, + { DRM_FORMAT_MOD_VENDOR_VIVANTE, "VIVANTE" }, + { DRM_FORMAT_MOD_VENDOR_BROADCOM, "BROADCOM" }, + { DRM_FORMAT_MOD_VENDOR_ARM, "ARM" }, + { DRM_FORMAT_MOD_VENDOR_ALLWINNER, "ALLWINNER" }, + { DRM_FORMAT_MOD_VENDOR_AMLOGIC, "AMLOGIC" }, + { DRM_FORMAT_MOD_VENDOR_MTK, "MTK" }, + { DRM_FORMAT_MOD_VENDOR_APPLE, "APPLE" }, +}; +]], }, }, } import("xim.libxpkg.pkginfo") -import("xim.libxpkg.system") import("xim.libxpkg.log") --- install() is a blind spot by default: log.error does not reach the CI log --- and a call outside the sandbox's xmake-API subset kills the hook silently. --- So the log comes first and every step announces itself. validate.yml's --- failure step collects `mcpp_*_build.log`, which is what this name matches. -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("[libdrm] %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 - +-- A `.tar.xz` is extracted to a sibling of the install dir rather than into +-- it, so the descriptor has to move it into place — the same three lines +-- compat.xcb, compat.xtrans and compat.xcb-proto open with. Without this the +-- package installs EMPTY: `sources` match nothing, mcpp emits a `c_shared` +-- edge with zero inputs, and the failure surfaces as `/bin/sh: -shared: not +-- found` because the unused `$cc` was never defined. function install() - local prefix = pkginfo.install_dir() - os.mkdir(prefix) - - log_path = path.join(prefix, "mcpp_libdrm_build.log") - io.writefile(log_path, "compat.libdrm 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", "libdrm") - local out_lib = path.join(root, "lib") - local out_inc = path.join(root, "include") - local out_uapi = path.join(out_inc, "libdrm") - - os.mkdir(out_lib) - os.mkdir(out_inc) - os.mkdir(out_uapi) - - -- 1. The library. From the subos view and nowhere else. `libdrm.so*` only: - -- the vendor variants are separate sonames nobody on the generic KMS - -- path links, and the glob is anchored so `libdrm_amdgpu.so` cannot - -- match it. - say("linking libdrm.so* from " .. view_lib) - link_matching(view_lib, "libdrm.so*", out_lib) - - for _, required in ipairs({"libdrm.so", "libdrm.so.2"}) do - if not os.isfile(path.join(out_lib, required)) then - return fail(required .. " is not in this subos. libdrm comes from " - .. "`xim:libdrm`, which this package declares as a " - .. "runtime dependency; if it is declared and this " - .. "still fires, that install did not finish") - end + local srcroot = pkginfo.install_file():replace(".tar.xz", "") + if not os.isdir(srcroot) then + srcroot = "libdrm-" .. pkginfo.version() end - say("libdrm.so and libdrm.so.2 present") - - -- The glob cannot match a C runtime, but compat.glx-runtime's rule is to - -- ASSERT rather than trust: a stray libc here faults inside the dynamic - -- linker before main with no output at all. - 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 libdrm farm; it would " - .. "reach every consumer's RUNPATH and pair a second " - .. "libc with mcpp's loader") - end + if not os.isdir(srcroot) then + log.error("[libdrm] extracted source tree not found (looked for %s)", srcroot) + return false end - -- 2. The public headers, at the root where upstream installs them. - say("linking public headers from " .. view_inc) - for _, h in ipairs({"xf86drm.h", "xf86drmMode.h", "libsync.h"}) do - link_matching(view_inc, h, out_inc) - end - if not os.isfile(path.join(out_inc, "xf86drm.h")) then - return fail("xf86drm.h is not in this subos (expected " - .. path.join(view_inc, "xf86drm.h") .. ")") - end + os.tryrm(pkginfo.install_dir()) + os.mv(srcroot, pkginfo.install_dir()) - -- 3. The uapi headers, in the `libdrm/` subdirectory the public ones - -- include from. Without this, `` parses down to line 40 and - -- fails on `#include `. - say("linking uapi headers from " .. path.join(view_inc, "libdrm")) - link_matching(path.join(view_inc, "libdrm"), "*.h", out_uapi) - if not os.isfile(path.join(out_uapi, "drm.h")) then - return fail("libdrm/drm.h is not in this subos; would fail " - .. "to parse at its own `#include `") + -- Upstream installs xf86drm.h/xf86drmMode.h/libsync.h into + -- `/include` and include/drm/*.h into `/include/libdrm`, + -- and puts both on the pkg-config include path. The uapi half is already + -- at `include/drm`; this reproduces the other half so a consumer's + -- `#include ` works without putting the whole source root — + -- private headers and all — on every consumer's include path. + local inc = path.join(pkginfo.install_dir(), "mcpp", "include") + os.mkdir(inc) + for _, header in ipairs({"xf86drm.h", "xf86drmMode.h", "libsync.h"}) do + local from = path.join(pkginfo.install_dir(), header) + if not os.isfile(from) then + log.error("[libdrm] %s missing from the release tarball", header) + return false + end + os.cp(from, path.join(inc, header)) end - say("headers present") - say("done") return true end diff --git a/pkgs/c/compat.libffi.lua b/pkgs/c/compat.libffi.lua new file mode 100644 index 00000000..9ae83d4c --- /dev/null +++ b/pkgs/c/compat.libffi.lua @@ -0,0 +1,362 @@ +-- compat.libffi — libffi, built from source. +-- +-- The foreign-function-interface library: `ffi_prep_cif` / `ffi_call` build and +-- invoke a call frame at runtime from a described signature. It exists in this +-- index because compat.wayland needs it — `libwayland-client` dispatches every +-- protocol message through `ffi_call` — but it is a general-purpose library and +-- is packaged as one. +-- +-- ───────────────────────────────────────────────────────────────────────── +-- SHAPE: SOURCE BUILD (libffi is a separable project) +-- +-- Independent upstream with its own releases, so the index's default rule +-- applies and there is nothing to argue about. +-- +-- The ecosystem also has `xim:libffi`, at 3.4.4, installed as a transitive of +-- `xim:mesa`. That does NOT make this a binding — see compat.libdrm for the +-- measurement — but it does constrain the VERSION. The store's installed-check +-- matches (name, version) and ignores the namespace, so a `compat.libffi@3.4.4` +-- would be shadowed by `xim:libffi@3.4.4` and silently never install. 3.4.8 is +-- the current upstream release and does not collide. +-- +-- host 0 +-- ecosystem 0 no `xim:*` dependency +-- index 0 `deps = {}` +-- transitive 0 libffi.so.8 needs only libc +-- +-- ───────────────────────────────────────────────────────────────────────── +-- ARCHITECTURE FILES SELECT THEMSELVES +-- +-- libffi is mostly per-ABI assembly, which normally means the build system +-- picks a source set per target. It does not have to here: every file in +-- `src/x86/` opens with its own architecture guard — +-- +-- ffi.c `#if defined(__i386__) || defined(_M_IX86)` +-- ffiw64.c `#if defined(__x86_64__) || defined(_M_AMD64)` +-- unix64.S `#ifdef __x86_64__` +-- win64.S `#ifdef __x86_64__` +-- sysv.S `#ifdef __i386__` +-- +-- so all of them can be listed and the preprocessor drops the ones that do not +-- apply. The `.S` files also `#define LIBFFI_ASM` themselves, so no assembler +-- flag is needed either. This is x86/x86_64 only; another architecture needs +-- its own `src//` files added here. +-- +-- ───────────────────────────────────────────────────────────────────────── +-- THE THREE GENERATED HEADERS +-- +-- Upstream's configure produces `fficonfig.h`, substitutes `include/ffi.h.in` +-- into `ffi.h`, and copies the target's `ffitarget.h` into `include/`. +-- +-- `fficonfig.h` cannot be derived from the tarball — it is the record of what +-- configure probed — so it is inlined below, verbatim from a real +-- `./configure` run on x86_64-pc-linux-gnu with the host-naming lines dropped. +-- +-- The other two ARE derivable, so install() does what configure would rather +-- than hard-coding 500 lines: `ffi.h.in` has exactly four substitutions +-- (@TARGET@, @HAVE_LONG_DOUBLE@, @FFI_EXEC_TRAMPOLINE_TABLE@, @VERSION@) and +-- `ffitarget.h` is a copy. Deriving them keeps this descriptor honest when the +-- version moves. +package = { + spec = "1", + namespace = "compat", + name = "libffi", + description = "libffi — portable foreign function interface, built from the upstream release", + licenses = {"MIT"}, + repo = "https://github.com/libffi/libffi", + type = "package", + + xpm = { + linux = { + ["3.4.8"] = { + url = { + GLOBAL = "https://github.com/libffi/libffi/releases/download/v3.4.8/libffi-3.4.8.tar.gz", + CN = "https://gitcode.com/mcpp-res/libffi/releases/download/3.4.8/libffi-3.4.8.tar.gz", + }, + sha256 = "bc9842a18898bfacb0ed1252c4febcc7e78fa139fd27fdc7a3e30d9d9356119b", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c11", + + -- Upstream's AM_CPPFLAGS is `-I. -I$(top_srcdir)/include -Iinclude + -- -I$(top_srcdir)/src`; `.` is where fficonfig.h lands and + -- ffi_common.h opens with `#include `. + -- Upstream's AM_CPPFLAGS is `-I. -I$(top_srcdir)/include -Iinclude + -- -I$(top_srcdir)/src`. install() flattens the tree, and fficonfig.h + -- is generated into `include/` rather than the root so these two + -- entries cover everything: ffi_common.h opens with + -- `#include `, and the x86 sources reach `internal64.h` + -- and friends through `src`. + include_dirs = { + "include", + "src", + }, + + cflags = { "-D_GNU_SOURCE", "-fPIC" }, + + sources = { + -- portable core + "src/prep_cif.c", + "src/types.c", + "src/raw_api.c", + "src/java_raw_api.c", + "src/closures.c", + "src/tramp.c", + -- x86 family; each guards itself, see the header comment + "src/x86/ffi.c", + "src/x86/ffi64.c", + "src/x86/ffiw64.c", + "src/x86/sysv.S", + "src/x86/unix64.S", + "src/x86/win64.S", + }, + + targets = { ["ffi"] = { kind = "shared", soname = "libffi.so.8" } }, + deps = {}, + + generated_files = { + ["include/fficonfig.h"] = +[[ +/* fficonfig.h. Generated from fficonfig.h.in by configure. */ +/* fficonfig.h.in. Generated from configure.ac by autoheader. */ + +/* Define if building universal (internal helper macro) */ +/* #undef AC_APPLE_UNIVERSAL_BUILD */ + +/* Define to the flags needed for the .section .eh_frame directive. */ +#define EH_FRAME_FLAGS "a" + +/* Define this if you want extra debugging. */ +/* #undef FFI_DEBUG */ + +/* Define this if you want statically defined trampolines */ +#define FFI_EXEC_STATIC_TRAMP 1 + +/* Cannot use PROT_EXEC on this target, so, we revert to alternative means */ +/* #undef FFI_EXEC_TRAMPOLINE_TABLE */ + +/* Define this if you want to enable pax emulated trampolines (experimental) + */ +/* #undef FFI_MMAP_EXEC_EMUTRAMP_PAX */ + +/* Cannot use malloc on this target, so, we revert to alternative means */ +/* #undef FFI_MMAP_EXEC_WRIT */ + +/* Define this if you do not want support for the raw API. */ +/* #undef FFI_NO_RAW_API */ + +/* Define this if you do not want support for aggregate types. */ +/* #undef FFI_NO_STRUCTS */ + +/* Define to 1 if you have the header file. */ +#define HAVE_ALLOCA_H 1 + +/* Define if your compiler supports pointer authentication. */ +/* #undef HAVE_ARM64E_PTRAUTH */ + +/* Define if your assembler supports .cfi_* directives. */ +#define HAVE_AS_CFI_PSEUDO_OP 1 + +/* Define if your assembler supports .register. */ +/* #undef HAVE_AS_REGISTER_PSEUDO_OP */ + +/* Define if the compiler uses zarch features. */ +/* #undef HAVE_AS_S390_ZARCH */ + +/* Define if your assembler and linker support unaligned PC relative relocs. + */ +/* #undef HAVE_AS_SPARC_UA_PCREL */ + +/* Define if your assembler supports unwind section type. */ +#define HAVE_AS_X86_64_UNWIND_SECTION_TYPE 1 + +/* Define if your assembler supports PC relative relocs. */ +#define HAVE_AS_X86_PCREL 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_DLFCN_H 1 + +/* Define if __attribute__((visibility("hidden"))) is supported. */ +#define HAVE_HIDDEN_VISIBILITY_ATTRIBUTE 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define if you have the long double type and it is bigger than a double */ +#define HAVE_LONG_DOUBLE 1 + +/* Define if you support more than one size of the long double type */ +/* #undef HAVE_LONG_DOUBLE_VARIANT */ + +/* Define to 1 if you have the `memcpy' function. */ +#define HAVE_MEMCPY 1 + +/* Define to 1 if you have the `memfd_create' function. */ +#define HAVE_MEMFD_CREATE 1 + +/* Define if .eh_frame sections should be read-only. */ +#define HAVE_RO_EH_FRAME 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDIO_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_MEMFD_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to 1 if GNU symbol versioning is used for libatomic. */ +#define LIBFFI_GNU_SYMBOL_VERSIONING 1 + +/* Define to the sub-directory where libtool stores uninstalled libraries. */ + +/* Name of package */ + +/* Define to the address where bug reports for this package should be sent. */ + +/* Define to the full name of this package. */ + +/* Define to the full name and version of this package. */ + +/* Define to the one symbol short name of this package. */ + +/* Define to the home page for this package. */ + +/* Define to the version of this package. */ + +/* The size of `double', as computed by sizeof. */ +#define SIZEOF_DOUBLE 8 + +/* The size of `long double', as computed by sizeof. */ +#define SIZEOF_LONG_DOUBLE 16 + +/* The size of `size_t', as computed by sizeof. */ +#define SIZEOF_SIZE_T 8 + +/* Define to 1 if all of the C90 standard headers exist (not just the ones + required in a freestanding environment). This macro is provided for + backward compatibility; new code need not use it. */ +#define STDC_HEADERS 1 + +/* Define if symbols are underscored. */ +/* #undef SYMBOL_UNDERSCORE */ + +/* Define this if you are using Purify and want to suppress spurious messages. + */ +/* #undef USING_PURIFY */ + +/* Version number of package */ +#define VERSION "3.4.8" + +/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most + significant byte first (like Motorola and SPARC, unlike Intel). */ +#if defined AC_APPLE_UNIVERSAL_BUILD +# if defined __BIG_ENDIAN__ +# define WORDS_BIGENDIAN 1 +# endif +#else +# ifndef WORDS_BIGENDIAN +/* # undef WORDS_BIGENDIAN */ +# endif +#endif + + +#ifdef HAVE_HIDDEN_VISIBILITY_ATTRIBUTE +#ifdef LIBFFI_ASM +#ifdef __APPLE__ +#define FFI_HIDDEN(name) .private_extern name +#else +#define FFI_HIDDEN(name) .hidden name +#endif +#else +#define FFI_HIDDEN __attribute__ ((visibility ("hidden"))) +#endif +#else +#ifdef LIBFFI_ASM +#define FFI_HIDDEN(name) +#else +#define FFI_HIDDEN +#endif +#endif +]], + }, + }, +} + +import("xim.libxpkg.pkginfo") +import("xim.libxpkg.log") + +-- What configure does to the two derivable headers. Kept as code rather than +-- as two more inlined blobs so a version bump does not silently ship the old +-- ffi.h: this reads the tarball's own `.in` every time. +function install() + -- Once a descriptor defines install(), the archive is extracted to a + -- sibling of the install dir instead of into it, and moving it is the + -- descriptor's job — the same opening compat.libdrm and compat.xcb have. + local srcroot = pkginfo.install_file():replace(".tar.gz", "") + if not os.isdir(srcroot) then + srcroot = "libffi-" .. pkginfo.version() + end + if not os.isdir(srcroot) then + log.error("[libffi] extracted tree not found (looked for %s)", srcroot) + return false + end + os.tryrm(pkginfo.install_dir()) + os.mv(srcroot, pkginfo.install_dir()) + + local root = pkginfo.install_dir() + + local template = path.join(root, "include", "ffi.h.in") + local text = io.readfile(template) + if not text then + log.error("[libffi] include/ffi.h.in missing from the release tarball") + return false + end + + text = text:gsub("@TARGET@", "X86_64") + :gsub("@HAVE_LONG_DOUBLE@", "1") + :gsub("@FFI_EXEC_TRAMPOLINE_TABLE@", "0") + :gsub("@VERSION@", pkginfo.version()) + + if text:find("@[A-Za-z_]+@") then + log.error("[libffi] ffi.h.in has a placeholder this descriptor does not " + .. "substitute; the header would not compile") + return false + end + io.writefile(path.join(root, "include", "ffi.h"), text) + + -- configure copies the target ffitarget.h into include/; ffi.h does + -- `#include ` and consumers need it too. + local target_header = path.join(root, "src", "x86", "ffitarget.h") + if not os.isfile(target_header) then + log.error("[libffi] src/x86/ffitarget.h missing") + return false + end + os.cp(target_header, path.join(root, "include", "ffitarget.h")) + + return true +end diff --git a/pkgs/c/compat.libgbm.lua b/pkgs/c/compat.libgbm.lua index 32d294ca..e4db2a07 100644 --- a/pkgs/c/compat.libgbm.lua +++ b/pkgs/c/compat.libgbm.lua @@ -110,8 +110,18 @@ package = { -- -- `xim:mesa` and not `xim:graphics`: this package needs Mesa, not -- the GL dispatch and X11 halves of the twenty-two-package stack. - deps = { runtime = { "xim:mesa" } }, - ["2026.08.29"] = { + -- + -- PINNED, and the pin is what makes the version below honest: GBM + -- has no release of its own, so this package's version can only + -- mean "the GBM in Mesa 25.0.7". With a floating `xim:mesa` that + -- claim would quietly stop being true the day the payload moved. + deps = { runtime = { "xim:mesa@25.0.7.2" } }, + -- Mesa's version, verbatim — no binding-revision suffix is needed + -- here. compat.libdrm and compat.wayland carry one because their + -- short name equals their payload's and the store's installed-check + -- ignores namespaces; `libgbm` != `mesa`, so there is nothing to + -- collide with. See compat.libdrm for the measured failure. + ["25.0.7"] = { -- NOTHING DOWNLOADED HERE IS EVER READ, and the file is chosen -- so that cannot be misread. -- @@ -134,7 +144,7 @@ package = { -- and compared byte-for-byte against GLOBAL. url = { GLOBAL = "https://gitlab.freedesktop.org/mesa/mesa/-/raw/mesa-25.0.7/README.rst", - CN = "https://gitcode.com/mcpp-res/libgbm/releases/download/2026.08.29/libgbm-2026.08.29.rst", + CN = "https://gitcode.com/mcpp-res/libgbm/releases/download/25.0.7/libgbm-25.0.7.rst", }, sha256 = "03f0fd62094179bb70fb885042baa4254d392f5f7bb64e4d8856bec8a5ff8386", }, diff --git a/tests/examples/egl/mcpp.toml b/tests/examples/egl/mcpp.toml index c8a8cc61..e7d1acf2 100644 --- a/tests/examples/egl/mcpp.toml +++ b/tests/examples/egl/mcpp.toml @@ -14,4 +14,4 @@ name = "egl-tests" version = "0.1.0" [target.'cfg(linux)'.dependencies.compat] -egl = "2026.08.30" +egl = "1.7.0" diff --git a/tests/examples/expat/mcpp.toml b/tests/examples/expat/mcpp.toml new file mode 100644 index 00000000..e89d301b --- /dev/null +++ b/tests/examples/expat/mcpp.toml @@ -0,0 +1,12 @@ +# compat.expat test project. +# +# Expat is built from source here while the ecosystem also carries +# `xim:expat` 2.6.2 as a transitive of `xim:mesa`. They share the soname +# `libexpat.so.1`, so exactly one is mapped and nothing warns — the dladdr +# assertion pins which. +[package] +name = "expat-tests" +version = "0.1.0" + +[target.'cfg(linux)'.dependencies.compat] +expat = "2.7.1" diff --git a/tests/examples/expat/tests/xml.cpp b/tests/examples/expat/tests/xml.cpp new file mode 100644 index 00000000..8f99996a --- /dev/null +++ b/tests/examples/expat/tests/xml.cpp @@ -0,0 +1,122 @@ +// compat.expat — behavioral test. It parses a document rather than checking +// that symbols resolve, because the interesting failures are configuration +// ones: XML_DTD, XML_GE and XML_NS are feature switches compiled into +// expat_config.h, and getting them wrong yields a parser that links perfectly +// and then rejects (or mis-splits) input. + +#ifdef __linux__ + +#include + +#include +#include +#include +#include +#include + +namespace { + +int failures = 0; + +void check(bool ok, const char *what) +{ + std::printf("%-58s %s\n", what, ok ? "ok" : "FAILED"); + if (!ok) ++failures; +} + +struct Collected { + std::vector elements; + std::string text; +}; + +void XMLCALL on_start(void *ud, const XML_Char *name, const XML_Char **atts) +{ + auto *c = static_cast(ud); + c->elements.emplace_back(name); + for (int i = 0; atts != nullptr && atts[i] != nullptr; i += 2) { + c->elements.emplace_back(std::string("@") + atts[i] + "=" + atts[i + 1]); + } +} + +void XMLCALL on_text(void *ud, const XML_Char *s, int len) +{ + static_cast(ud)->text.append(s, static_cast(len)); +} + +} // namespace + +int main() +{ + // ── 1. A namespaced parse, which is XML_NS ─────────────────────────── + // The separator argument only does anything when XML_NS was compiled in; + // without it the element name comes back as "w:seat" instead of the + // expanded "urn:test|seat", and wayland-scanner would misread every + // protocol file. + { + Collected c; + XML_Parser p = XML_ParserCreateNS(nullptr, '|'); + check(p != nullptr, "XML_ParserCreateNS returns a parser"); + XML_SetUserData(p, &c); + XML_SetElementHandler(p, on_start, nullptr); + XML_SetCharacterDataHandler(p, on_text); + + static const char doc[] = + "hi"; + const bool ok = XML_Parse(p, doc, (int)std::strlen(doc), 1) == XML_STATUS_OK; + check(ok, "XML_Parse accepts a namespaced document"); + check(c.elements.size() >= 2 && c.elements[0] == "urn:test|proto", + "XML_NS expanded the prefix (urn:test|proto)"); + check(c.text == "hi", "character data reached the handler"); + XML_ParserFree(p); + } + + // ── 2. An internal DTD entity, which is XML_DTD + XML_GE ───────────── + // Built without general-entity support this parse fails outright, so it + // separates a correct expat_config.h from a plausible one. + { + Collected c; + XML_Parser p = XML_ParserCreate(nullptr); + XML_SetUserData(p, &c); + XML_SetCharacterDataHandler(p, on_text); + + static const char doc[] = + "]>&who;"; + const bool ok = XML_Parse(p, doc, (int)std::strlen(doc), 1) == XML_STATUS_OK; + check(ok, "XML_Parse accepts an internal DTD entity"); + check(c.text == "wayland", "the general entity expanded to 'wayland'"); + XML_ParserFree(p); + } + + // ── 3. Malformed input is rejected ─────────────────────────────────── + { + XML_Parser p = XML_ParserCreate(nullptr); + static const char bad[] = ""; + check(XML_Parse(p, bad, (int)std::strlen(bad), 1) == XML_STATUS_ERROR, + "mismatched tags are an error, not silence"); + XML_ParserFree(p); + } + + // ── 4. It is THIS build that ran ───────────────────────────────────── + { + Dl_info info{}; + const bool located = + ::dladdr(reinterpret_cast(&XML_ParserCreate), &info) != 0 + && info.dli_fname != nullptr; + check(located, "dladdr locates the loaded libexpat"); + if (located) { + const std::string from = info.dli_fname; + std::printf(" loaded from: %s\n", from.c_str()); + check(from.find("xim-x-expat") == std::string::npos, + "the loaded libexpat is not the ecosystem payload's copy"); + } + } + + std::printf("\n%d check(s) failed\n", failures); + return failures == 0 ? 0 : 1; +} + +#else + +int main() { return 0; } + +#endif diff --git a/tests/examples/libdrm/mcpp.toml b/tests/examples/libdrm/mcpp.toml index c091632b..748bfaa3 100644 --- a/tests/examples/libdrm/mcpp.toml +++ b/tests/examples/libdrm/mcpp.toml @@ -14,4 +14,4 @@ name = "libdrm-tests" version = "0.1.0" [target.'cfg(linux)'.dependencies.compat] -libdrm = "2026.08.30" +libdrm = "2.4.134" diff --git a/tests/examples/libdrm/tests/drm.cpp b/tests/examples/libdrm/tests/drm.cpp index 5f23d6f4..6928e8a3 100644 --- a/tests/examples/libdrm/tests/drm.cpp +++ b/tests/examples/libdrm/tests/drm.cpp @@ -10,8 +10,16 @@ // COMPILES is the first assertion, and it is not a trivial one. // // 2. THE LIBRARY COULD BE ABSENT while the headers are present. The dlsym -// checks below pin that: the farm resolves `-ldrm` out of the subos view, -// and a header-only package would sail past compilation and fail here. +// checks below pin that: a header-only package would sail past +// compilation and fail here. +// +// 2b. THE WRONG libdrm COULD BE LOADED. This package builds libdrm from +// source, and the ecosystem's Mesa payload carries its own +// `libdrm.so.2` under `xim-x-libdrm/`. Both can be reachable at once, and +// because they share a soname only ONE is ever mapped — silently. The +// dladdr check pins which one won: it must be this package's build, or +// the source build is decorative and the consumer is really running the +// payload's copy. // // 3. THE UAPI CONSTANTS COULD DISAGREE with the library's. drm_fourcc.h is // the kernel's, and DRM_FORMAT_XRGB8888 must be the same fourcc GBM calls @@ -80,6 +88,25 @@ int main() (std::string("libdrm exports ") + sym).c_str()); } + // ── 3b. …and it is THIS package's build, not the payload's ─────────── + // Same soname, so only one libdrm.so.2 is mapped and nothing warns about + // the other. dladdr reports the object a symbol actually came from; if the + // path names the ecosystem payload, this package built a library that no + // one loads. + { + Dl_info info{}; + const bool located = + ::dladdr(reinterpret_cast(&drmGetVersion), &info) != 0 + && info.dli_fname != nullptr; + check(located, "dladdr locates the loaded libdrm"); + if (located) { + const std::string from = info.dli_fname; + std::printf(" loaded from: %s\n", from.c_str()); + check(from.find("xim-x-libdrm") == std::string::npos, + "the loaded libdrm is not the ecosystem payload's copy"); + } + } + // ── 4. An invalid fd is rejected, not crashed on ───────────────────── check(drmGetVersion(-1) == nullptr, "drmGetVersion(-1) == nullptr"); diff --git a/tests/examples/libffi/mcpp.toml b/tests/examples/libffi/mcpp.toml new file mode 100644 index 00000000..8877ae18 --- /dev/null +++ b/tests/examples/libffi/mcpp.toml @@ -0,0 +1,12 @@ +# compat.libffi test project. +# +# libffi is built from source here, and the ecosystem also carries +# `xim:libffi` at 3.4.4 as a transitive of `xim:mesa`. They share the soname +# `libffi.so.8`, so only one is ever mapped and nothing warns about the other — +# the dladdr assertion in the test pins which one won. +[package] +name = "libffi-tests" +version = "0.1.0" + +[target.'cfg(linux)'.dependencies.compat] +libffi = "3.4.8" diff --git a/tests/examples/libffi/tests/ffi.cpp b/tests/examples/libffi/tests/ffi.cpp new file mode 100644 index 00000000..634c3eca --- /dev/null +++ b/tests/examples/libffi/tests/ffi.cpp @@ -0,0 +1,87 @@ +// compat.libffi — behavioral test. It does not check that symbols exist; it +// builds a call frame at runtime and invokes through it, which is the only +// thing that proves the per-ABI assembly was assembled and linked correctly. +// A wrong or missing unix64.S links fine and segfaults here. + +#ifdef __linux__ + +#include + +#include +#include +#include +#include + +namespace { + +int failures = 0; + +void check(bool ok, const char *what) +{ + std::printf("%-58s %s\n", what, ok ? "ok" : "FAILED"); + if (!ok) ++failures; +} + +int add3(int a, int b, int c) { return a + b + c; } + +double scale(double v, float f) { return v * static_cast(f); } + +} // namespace + +int main() +{ + // ── 1. A real integer call through ffi_call ────────────────────────── + { + ffi_cif cif{}; + ffi_type *args[3] = { &ffi_type_sint, &ffi_type_sint, &ffi_type_sint }; + check(ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 3, &ffi_type_sint, args) == FFI_OK, + "ffi_prep_cif on (int,int,int) -> int"); + + int a = 17, b = 25, c = -2, result = 0; + void *values[3] = { &a, &b, &c }; + ffi_call(&cif, FFI_FN(add3), &result, values); + check(result == 40, "ffi_call dispatched add3(17,25,-2) == 40"); + } + + // ── 2. Mixed float/double, which uses different argument registers ─── + // SSE arguments travel a separate path in unix64.S from integers, so an + // integer-only test would miss half of the assembly. + { + ffi_cif cif{}; + ffi_type *args[2] = { &ffi_type_double, &ffi_type_float }; + check(ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 2, &ffi_type_double, args) == FFI_OK, + "ffi_prep_cif on (double,float) -> double"); + + double v = 1.5, result = 0.0; + float f = 4.0f; + void *values[2] = { &v, &f }; + ffi_call(&cif, FFI_FN(scale), &result, values); + check(result == 6.0, "ffi_call dispatched scale(1.5, 4.0f) == 6.0"); + } + + // ── 3. It is THIS build that ran, not the ecosystem payload's ──────── + // Same soname as `xim:libffi`, so one mapping wins silently. If the path + // names the payload, this package compiled a library nobody loads. + { + Dl_info info{}; + const bool located = + ::dladdr(reinterpret_cast(&ffi_prep_cif), &info) != 0 + && info.dli_fname != nullptr; + check(located, "dladdr locates the loaded libffi"); + if (located) { + const std::string from = info.dli_fname; + std::printf(" loaded from: %s\n", from.c_str()); + check(from.find("xim-x-libffi") == std::string::npos, + "the loaded libffi is not the ecosystem payload's copy"); + } + } + + std::printf("\n%d check(s) failed\n", failures); + return failures == 0 ? 0 : 1; +} + +#else + +int main() { return 0; } + +#endif diff --git a/tests/examples/libgbm/mcpp.toml b/tests/examples/libgbm/mcpp.toml index fd9e7c01..077e3114 100644 --- a/tests/examples/libgbm/mcpp.toml +++ b/tests/examples/libgbm/mcpp.toml @@ -36,4 +36,4 @@ name = "libgbm-tests" version = "0.1.0" [target.'cfg(linux)'.dependencies.compat] -libgbm = "2026.08.29" +libgbm = "25.0.7" diff --git a/tests/examples/libgbm/tests/gbm.cpp b/tests/examples/libgbm/tests/gbm.cpp index 7f5a2d31..6424e4dc 100644 --- a/tests/examples/libgbm/tests/gbm.cpp +++ b/tests/examples/libgbm/tests/gbm.cpp @@ -140,11 +140,26 @@ int main() // mcpp's subos-env handling. Asserting it here is what makes a regression // in EITHER of those two land on this package's CI rather than silently on // a user. + // WHY THIS IS REPORTED AND NOT ASSERTED. + // + // The variable is not this package's to set — see above. It comes from + // xim-pkgindex's graphics discovery layer (#713) through mcpp's subos-env + // handling, so whether it is present is decided by the ECOSYSTEM VERSION in + // use, not by anything in this repository. mcpp-index's CI runs a pinned + // mcpp (`MCPP_VERSION`) whose vendored xlings predates that row, so a hard + // assertion here fails on CI while passing everywhere the ecosystem is + // current — a red that says nothing about the package. + // + // So: when it IS set, the checks below are the real ones and are stronger + // than mere presence (the directory must exist AND contain a backend), and + // a regression in either the DISCOVERY row or mcpp's env injection still + // lands here. When it is absent the member says so and moves on. const char *dir = std::getenv("GBM_BACKENDS_PATH"); - check(dir != nullptr, - "GBM_BACKENDS_PATH is set by the ecosystem, not by this package"); - - if (dir != nullptr) { + if (dir == nullptr) { + std::puts(" GBM_BACKENDS_PATH unset — this ecosystem predates " + "xim-pkgindex#713; skipping the backend-path checks"); + } else { + check(true, "GBM_BACKENDS_PATH is set by the ecosystem, not by this package"); std::printf(" backends dir: %s\n", dir); struct ::stat st {}; diff --git a/tests/examples/libgbm/tests/stock_usage.cpp b/tests/examples/libgbm/tests/stock_usage.cpp index 9c1538e5..e27f3dc8 100644 --- a/tests/examples/libgbm/tests/stock_usage.cpp +++ b/tests/examples/libgbm/tests/stock_usage.cpp @@ -43,11 +43,26 @@ int main() }; // Nothing in this program has run yet that could have set this. + // WHY THIS IS REPORTED AND NOT ASSERTED. + // + // The variable is not this package's to set — see above. It comes from + // xim-pkgindex's graphics discovery layer (#713) through mcpp's subos-env + // handling, so whether it is present is decided by the ECOSYSTEM VERSION in + // use, not by anything in this repository. mcpp-index's CI runs a pinned + // mcpp (`MCPP_VERSION`) whose vendored xlings predates that row, so a hard + // assertion here fails on CI while passing everywhere the ecosystem is + // current — a red that says nothing about the package. + // + // So: when it IS set, the checks below are the real ones and are stronger + // than mere presence (the directory must exist AND contain a backend), and + // a regression in either the DISCOVERY row or mcpp's env injection still + // lands here. When it is absent the member says so and moves on. const char *dir = std::getenv("GBM_BACKENDS_PATH"); - check(dir != nullptr, - "a -only consumer inherits GBM_BACKENDS_PATH"); - - if (dir != nullptr) { + if (dir == nullptr) { + std::puts(" GBM_BACKENDS_PATH unset — this ecosystem predates " + "xim-pkgindex#713; skipping the backend-path checks"); + } else { + check(true, "a -only consumer inherits GBM_BACKENDS_PATH"); std::printf(" %s\n", dir); struct ::stat st {};