From 609b5a43c5db08b9d0bad91d20f66616377f64dc Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Sun, 30 Aug 2026 09:59:36 +0800 Subject: [PATCH 1/5] wip: source-built graphics stack (libdrm/libffi/expat/wayland) --- mcpp.toml | 2 + pkgs/c/compat.egl.lua | 15 +- pkgs/c/compat.expat.lua | 260 +++++++++++++++++++ pkgs/c/compat.libdrm.lua | 384 ++++++++++++++-------------- pkgs/c/compat.libffi.lua | 362 ++++++++++++++++++++++++++ pkgs/c/compat.libgbm.lua | 16 +- pkgs/c/compat.wayland-scanner.lua | 41 +++ pkgs/c/compat.wayland-server.lua | 41 +++ pkgs/c/compat.wayland.lua | 200 ++------------- tests/examples/egl/mcpp.toml | 2 +- tests/examples/expat/mcpp.toml | 12 + tests/examples/expat/tests/xml.cpp | 122 +++++++++ tests/examples/libdrm/mcpp.toml | 2 +- tests/examples/libdrm/tests/drm.cpp | 31 ++- tests/examples/libffi/mcpp.toml | 12 + tests/examples/libffi/tests/ffi.cpp | 87 +++++++ tests/examples/libgbm/mcpp.toml | 2 +- tests/examples/wayland/mcpp.toml | 2 +- 18 files changed, 1214 insertions(+), 379 deletions(-) create mode 100644 pkgs/c/compat.expat.lua create mode 100644 pkgs/c/compat.libffi.lua create mode 100644 pkgs/c/compat.wayland-scanner.lua create mode 100644 pkgs/c/compat.wayland-server.lua create mode 100644 tests/examples/expat/mcpp.toml create mode 100644 tests/examples/expat/tests/xml.cpp create mode 100644 tests/examples/libffi/mcpp.toml create mode 100644 tests/examples/libffi/tests/ffi.cpp 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..a1bf22ce 100644 --- a/pkgs/c/compat.libdrm.lua +++ b/pkgs/c/compat.libdrm.lua @@ -1,241 +1,249 @@ --- 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 +-- WHY `kind = "shared"` WITH A SONAME IS LOAD-BEARING +-- +-- 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`. -- --- 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 `. +-- 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. -- --- So one include root is not enough. Measured while writing compat.libgbm's --- test, which tried exactly that: +-- 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. -- --- xf86drm.h:40:10: fatal error: drm.h: No such file or directory +-- Same shape and same reason as compat.vulkan's loader and the X11 family. -- --- 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. +-- ───────────────────────────────────────────────────────────────────────── +-- TWO INCLUDE ROOTS, AND THIS IS THE ONE THING THAT BITES +-- +-- 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 BUILD, TRANSCRIBED FROM meson.build -- --- 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. +-- 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.123.1"] = { 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.123.tar.xz", + CN = "https://gitcode.com/mcpp-res/libdrm/releases/download/2.4.123.1/libdrm-2.4.123.tar.xz", }, - sha256 = "46183785b2f012d0773646d1974374cbfc754f043d1a423afb0ffea0af2569c1", + sha256 = "a2b98567a149a74b0f50e91e825f9c0315d86e7be9b74394dae8b298caadb79e", }, }, }, mcpp = { - language = "c++23", - import_std = false, - c_standard = "c11", + language = "c++23", + import_std = false, + c_standard = "c11", + + -- `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", + }, - -- Both roots, for the reason in the header comment: `` from - -- the first, the `` it includes from the second. + -- 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_generated/libdrm/include", - "mcpp_generated/libdrm/include/libdrm", + "mcpp/include", + "include/drm", }, - generated_files = { - ["mcpp_generated/libdrm_anchor.c"] = - "int mcpp_compat_libdrm_anchor(void) { return 0; }\n", + -- meson.build's `libdrm_files`, verbatim. + sources = { + "xf86drm.c", + "xf86drmHash.c", + "xf86drmRandom.c", + "xf86drmSL.c", + "xf86drmMode.c", }, - 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" } }, - - 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(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(ALLWINNER, TILED, TILED) }, +}; +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" }, +}; +]], }, }, } 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 - - -- 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 `") + os.tryrm(pkginfo.install_dir()) + os.mv(srcroot, pkginfo.install_dir()) + + -- 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/pkgs/c/compat.wayland-scanner.lua b/pkgs/c/compat.wayland-scanner.lua new file mode 100644 index 00000000..eead804e --- /dev/null +++ b/pkgs/c/compat.wayland-scanner.lua @@ -0,0 +1,41 @@ +-- compat.wayland-scanner — the Wayland protocol code generator, built from source. +-- +-- Form A: the manifest lives in mcpplibs/wayland-m, a fork of freedesktop's +-- wayland 1.23.1 that adds mcpp build support and patches nothing. +-- +-- WHY A FORK RATHER THAN AN INLINE DESCRIPTOR HERE +-- +-- wayland's libraries are mostly GENERATED — protocol/wayland.xml describes +-- every interface and wayland-scanner emits ~12,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 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. +-- +-- The one tarball backs three index entries, each pointing at a different +-- workspace member — the layout grpc/grpcgen/grpc-plugin already use. +package = { + spec = "1", + namespace = "compat", + name = "wayland-scanner", + description = "wayland-scanner — the Wayland protocol code generator, built from source", + licenses = {"MIT"}, + repo = "https://github.com/mcpplibs/wayland-m", + type = "package", + + xpm = { + linux = { + ["1.23.1"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/wayland-m/archive/refs/tags/v1.23.1-1.tar.gz", + CN = "https://gitcode.com/mcpp-res/wayland/releases/download/1.23.1/wayland-m-1.23.1-1.tar.gz", + }, + sha256 = "16ac3d6d22eb1973d2c83b47cc0d4518160837a5597298e3799228d155c43c86", + }, + }, + }, + + mcpp = "*/mcpp/scanner/mcpp.toml", +} diff --git a/pkgs/c/compat.wayland-server.lua b/pkgs/c/compat.wayland-server.lua new file mode 100644 index 00000000..449f4935 --- /dev/null +++ b/pkgs/c/compat.wayland-server.lua @@ -0,0 +1,41 @@ +-- compat.wayland-server — libwayland-server, the compositor side of the protocol. +-- +-- Form A: the manifest lives in mcpplibs/wayland-m, a fork of freedesktop's +-- wayland 1.23.1 that adds mcpp build support and patches nothing. +-- +-- WHY A FORK RATHER THAN AN INLINE DESCRIPTOR HERE +-- +-- wayland's libraries are mostly GENERATED — protocol/wayland.xml describes +-- every interface and wayland-scanner emits ~12,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 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. +-- +-- The one tarball backs three index entries, each pointing at a different +-- workspace member — the layout grpc/grpcgen/grpc-plugin already use. +package = { + spec = "1", + namespace = "compat", + name = "wayland-server", + description = "libwayland-server — the compositor side of the Wayland protocol, built from source", + licenses = {"MIT"}, + repo = "https://github.com/mcpplibs/wayland-m", + type = "package", + + xpm = { + linux = { + ["1.23.1"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/wayland-m/archive/refs/tags/v1.23.1-1.tar.gz", + CN = "https://gitcode.com/mcpp-res/wayland/releases/download/1.23.1/wayland-m-1.23.1-1.tar.gz", + }, + sha256 = "16ac3d6d22eb1973d2c83b47cc0d4518160837a5597298e3799228d155c43c86", + }, + }, + }, + + mcpp = "*/mcpp/server/mcpp.toml", +} diff --git a/pkgs/c/compat.wayland.lua b/pkgs/c/compat.wayland.lua index ea119622..fe0cb007 100644 --- a/pkgs/c/compat.wayland.lua +++ b/pkgs/c/compat.wayland.lua @@ -1,199 +1,41 @@ --- 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. +-- compat.wayland — libwayland-client, the client side of the protocol. -- --- 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. +-- Form A: the manifest lives in mcpplibs/wayland-m, a fork of freedesktop's +-- wayland 1.23.1 that adds mcpp build support and patches nothing. -- --- ───────────────────────────────────────────────────────────────────────── --- SHAPE: a binding, same criterion as compat.libdrm +-- WHY A FORK RATHER THAN AN INLINE DESCRIPTOR HERE -- --- 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. +-- wayland's libraries are mostly GENERATED — protocol/wayland.xml describes +-- every interface and wayland-scanner emits ~12,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 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. -- --- 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. +-- The one tarball backs three index entries, each pointing at a different +-- workspace member — the layout grpc/grpcgen/grpc-plugin already use. package = { spec = "1", namespace = "compat", name = "wayland", - description = "Wayland core client/server libraries, bound to the ecosystem's xim:wayland", + description = "libwayland-client — the client side of the Wayland protocol, built from source", licenses = {"MIT"}, - repo = "https://gitlab.freedesktop.org/wayland/wayland", + repo = "https://github.com/mcpplibs/wayland-m", 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. + ["1.23.1.1"] = { 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", + GLOBAL = "https://github.com/mcpplibs/wayland-m/archive/refs/tags/v1.23.1-1.tar.gz", + CN = "https://gitcode.com/mcpp-res/wayland/releases/download/1.23.1.1/wayland-m-1.23.1-1.tar.gz", }, - sha256 = "147f133b07a9ea767e426944c7c5e3946d642cfbf392f63e28a37888b700fb54", + sha256 = "16ac3d6d22eb1973d2c83b47cc0d4518160837a5597298e3799228d155c43c86", }, }, }, - 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" }, - }, - }, + mcpp = "*/mcpp/client/mcpp.toml", } - -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/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..4c9eda8a 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.123.1" 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/wayland/mcpp.toml b/tests/examples/wayland/mcpp.toml index 736fec60..90999a4f 100644 --- a/tests/examples/wayland/mcpp.toml +++ b/tests/examples/wayland/mcpp.toml @@ -17,7 +17,7 @@ name = "wayland-tests" version = "0.1.0" [target.'cfg(linux)'.dependencies.compat] -wayland = "2026.08.30" +wayland = "1.23.1.1" # 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. From c0e0df6592f5bc751f09fe7376e1cd986e1ba00a Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Sun, 30 Aug 2026 10:09:34 +0800 Subject: [PATCH 2/5] libdrm 2.4.134 --- pkgs/c/compat.libdrm.lua | 23 +++++++++++++++++++---- tests/examples/libdrm/mcpp.toml | 2 +- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/pkgs/c/compat.libdrm.lua b/pkgs/c/compat.libdrm.lua index a1bf22ce..4216b365 100644 --- a/pkgs/c/compat.libdrm.lua +++ b/pkgs/c/compat.libdrm.lua @@ -27,6 +27,14 @@ -- glibc and valgrind/udev are compiled out below -- -- ───────────────────────────────────────────────────────────────────────── +-- 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. +-- +-- ───────────────────────────────────────────────────────────────────────── -- WHY `kind = "shared"` WITH A SONAME IS LOAD-BEARING -- -- Mesa is normally in the same process — `xim:mesa` declares `xim:libdrm`, and @@ -87,12 +95,12 @@ package = { xpm = { linux = { - ["2.4.123.1"] = { + ["2.4.134"] = { url = { - GLOBAL = "https://dri.freedesktop.org/libdrm/libdrm-2.4.123.tar.xz", - CN = "https://gitcode.com/mcpp-res/libdrm/releases/download/2.4.123.1/libdrm-2.4.123.tar.xz", + 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 = "a2b98567a149a74b0f50e91e825f9c0315d86e7be9b74394dae8b298caadb79e", + sha256 = "ac5e74d157830eb8bee44c6a6bf3ad49774ef0dd2a72bdad74a8f20308b52a95", }, }, }, @@ -163,6 +171,8 @@ static const struct drmFormatModifierInfo drm_format_modifier_table[] = { { 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) }, @@ -186,7 +196,10 @@ static const struct drmFormatModifierInfo drm_format_modifier_table[] = { { 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" }, @@ -200,6 +213,8 @@ static const struct drmFormatModifierVendorInfo drm_format_modifier_vendor_table { 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" }, }; ]], }, diff --git a/tests/examples/libdrm/mcpp.toml b/tests/examples/libdrm/mcpp.toml index 4c9eda8a..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 = "2.4.123.1" +libdrm = "2.4.134" From caa69570e3f42306b81e5094a025a403a6a1363a Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Sun, 30 Aug 2026 10:20:28 +0800 Subject: [PATCH 3/5] wayland: four freedesktop packages from the fork --- pkgs/c/compat.wayland-scanner.lua | 41 ----------------------- pkgs/c/compat.wayland-server.lua | 41 ----------------------- pkgs/c/compat.wayland.lua | 41 ----------------------- pkgs/f/freedesktop.wayland-scanner.lua | 45 ++++++++++++++++++++++++++ pkgs/f/freedesktop.wayland-server.lua | 45 ++++++++++++++++++++++++++ pkgs/f/freedesktop.wayland-util.lua | 45 ++++++++++++++++++++++++++ pkgs/f/freedesktop.wayland.lua | 45 ++++++++++++++++++++++++++ tests/examples/wayland/mcpp.toml | 31 +++++++----------- 8 files changed, 192 insertions(+), 142 deletions(-) delete mode 100644 pkgs/c/compat.wayland-scanner.lua delete mode 100644 pkgs/c/compat.wayland-server.lua delete mode 100644 pkgs/c/compat.wayland.lua create mode 100644 pkgs/f/freedesktop.wayland-scanner.lua create mode 100644 pkgs/f/freedesktop.wayland-server.lua create mode 100644 pkgs/f/freedesktop.wayland-util.lua create mode 100644 pkgs/f/freedesktop.wayland.lua diff --git a/pkgs/c/compat.wayland-scanner.lua b/pkgs/c/compat.wayland-scanner.lua deleted file mode 100644 index eead804e..00000000 --- a/pkgs/c/compat.wayland-scanner.lua +++ /dev/null @@ -1,41 +0,0 @@ --- compat.wayland-scanner — the Wayland protocol code generator, built from source. --- --- Form A: the manifest lives in mcpplibs/wayland-m, a fork of freedesktop's --- wayland 1.23.1 that adds mcpp build support and patches nothing. --- --- WHY A FORK RATHER THAN AN INLINE DESCRIPTOR HERE --- --- wayland's libraries are mostly GENERATED — protocol/wayland.xml describes --- every interface and wayland-scanner emits ~12,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 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. --- --- The one tarball backs three index entries, each pointing at a different --- workspace member — the layout grpc/grpcgen/grpc-plugin already use. -package = { - spec = "1", - namespace = "compat", - name = "wayland-scanner", - description = "wayland-scanner — the Wayland protocol code generator, built from source", - licenses = {"MIT"}, - repo = "https://github.com/mcpplibs/wayland-m", - type = "package", - - xpm = { - linux = { - ["1.23.1"] = { - url = { - GLOBAL = "https://github.com/mcpplibs/wayland-m/archive/refs/tags/v1.23.1-1.tar.gz", - CN = "https://gitcode.com/mcpp-res/wayland/releases/download/1.23.1/wayland-m-1.23.1-1.tar.gz", - }, - sha256 = "16ac3d6d22eb1973d2c83b47cc0d4518160837a5597298e3799228d155c43c86", - }, - }, - }, - - mcpp = "*/mcpp/scanner/mcpp.toml", -} diff --git a/pkgs/c/compat.wayland-server.lua b/pkgs/c/compat.wayland-server.lua deleted file mode 100644 index 449f4935..00000000 --- a/pkgs/c/compat.wayland-server.lua +++ /dev/null @@ -1,41 +0,0 @@ --- compat.wayland-server — libwayland-server, the compositor side of the protocol. --- --- Form A: the manifest lives in mcpplibs/wayland-m, a fork of freedesktop's --- wayland 1.23.1 that adds mcpp build support and patches nothing. --- --- WHY A FORK RATHER THAN AN INLINE DESCRIPTOR HERE --- --- wayland's libraries are mostly GENERATED — protocol/wayland.xml describes --- every interface and wayland-scanner emits ~12,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 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. --- --- The one tarball backs three index entries, each pointing at a different --- workspace member — the layout grpc/grpcgen/grpc-plugin already use. -package = { - spec = "1", - namespace = "compat", - name = "wayland-server", - description = "libwayland-server — the compositor side of the Wayland protocol, built from source", - licenses = {"MIT"}, - repo = "https://github.com/mcpplibs/wayland-m", - type = "package", - - xpm = { - linux = { - ["1.23.1"] = { - url = { - GLOBAL = "https://github.com/mcpplibs/wayland-m/archive/refs/tags/v1.23.1-1.tar.gz", - CN = "https://gitcode.com/mcpp-res/wayland/releases/download/1.23.1/wayland-m-1.23.1-1.tar.gz", - }, - sha256 = "16ac3d6d22eb1973d2c83b47cc0d4518160837a5597298e3799228d155c43c86", - }, - }, - }, - - mcpp = "*/mcpp/server/mcpp.toml", -} diff --git a/pkgs/c/compat.wayland.lua b/pkgs/c/compat.wayland.lua deleted file mode 100644 index fe0cb007..00000000 --- a/pkgs/c/compat.wayland.lua +++ /dev/null @@ -1,41 +0,0 @@ --- compat.wayland — libwayland-client, the client side of the protocol. --- --- Form A: the manifest lives in mcpplibs/wayland-m, a fork of freedesktop's --- wayland 1.23.1 that adds mcpp build support and patches nothing. --- --- WHY A FORK RATHER THAN AN INLINE DESCRIPTOR HERE --- --- wayland's libraries are mostly GENERATED — protocol/wayland.xml describes --- every interface and wayland-scanner emits ~12,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 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. --- --- The one tarball backs three index entries, each pointing at a different --- workspace member — the layout grpc/grpcgen/grpc-plugin already use. -package = { - spec = "1", - namespace = "compat", - name = "wayland", - description = "libwayland-client — the client side of the Wayland protocol, built from source", - licenses = {"MIT"}, - repo = "https://github.com/mcpplibs/wayland-m", - type = "package", - - xpm = { - linux = { - ["1.23.1.1"] = { - url = { - GLOBAL = "https://github.com/mcpplibs/wayland-m/archive/refs/tags/v1.23.1-1.tar.gz", - CN = "https://gitcode.com/mcpp-res/wayland/releases/download/1.23.1.1/wayland-m-1.23.1-1.tar.gz", - }, - sha256 = "16ac3d6d22eb1973d2c83b47cc0d4518160837a5597298e3799228d155c43c86", - }, - }, - }, - - mcpp = "*/mcpp/client/mcpp.toml", -} diff --git a/pkgs/f/freedesktop.wayland-scanner.lua b/pkgs/f/freedesktop.wayland-scanner.lua new file mode 100644 index 00000000..977db894 --- /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-1.tar.gz", + CN = "https://gitcode.com/mcpp-res/wayland/releases/download/1.26.0/wayland-1.26.0-1.tar.gz", + }, + sha256 = "e8f367ceb55cee5badc86f968797c525f81b082e6d10a51e915e8c3dcba9eb5d", + }, + }, + }, + + 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..d4ed0955 --- /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-1.tar.gz", + CN = "https://gitcode.com/mcpp-res/wayland/releases/download/1.26.0/wayland-1.26.0-1.tar.gz", + }, + sha256 = "e8f367ceb55cee5badc86f968797c525f81b082e6d10a51e915e8c3dcba9eb5d", + }, + }, + }, + + 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..c97a15f4 --- /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-1.tar.gz", + CN = "https://gitcode.com/mcpp-res/wayland/releases/download/1.26.0/wayland-1.26.0-1.tar.gz", + }, + sha256 = "e8f367ceb55cee5badc86f968797c525f81b082e6d10a51e915e8c3dcba9eb5d", + }, + }, + }, + + 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..f600a510 --- /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-1.tar.gz", + CN = "https://gitcode.com/mcpp-res/wayland/releases/download/1.26.0/wayland-1.26.0-1.tar.gz", + }, + sha256 = "e8f367ceb55cee5badc86f968797c525f81b082e6d10a51e915e8c3dcba9eb5d", + }, + }, + }, + + mcpp = "*/mcpp/client/mcpp.toml", +} diff --git a/tests/examples/wayland/mcpp.toml b/tests/examples/wayland/mcpp.toml index 90999a4f..1698d4ec 100644 --- a/tests/examples/wayland/mcpp.toml +++ b/tests/examples/wayland/mcpp.toml @@ -1,25 +1,18 @@ -# Wayland test project. +# wayland test member. # -# Linux-only, dependency gated, no-op main() elsewhere — same shape as its -# neighbours. +# Two packages now, not one with an ldflags escape hatch: the client and the +# server are distinct SONAMEs (Mesa's libEGL_mesa has DT_NEEDED on both), so +# they are two packages, and a compositor asks for the one it needs by name. # -# 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. +# Both also ship a C++23 module wrapper, and the test uses BOTH spellings — +# `#include ` and `import wayland.client;` — because the +# whole claim of the module layer is that they are the same entities. [package] name = "wayland-tests" version = "0.1.0" +standard = "c++23" -[target.'cfg(linux)'.dependencies.compat] -wayland = "1.23.1.1" - -# 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"] +[target.'cfg(linux)'.dependencies.freedesktop] +wayland = "1.26.0" +wayland-server = "1.26.0" +wayland-util = "1.26.0" From 2c9a54d4dc44dece533b3f0633a791ea1b43e9e2 Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Sun, 30 Aug 2026 10:32:39 +0800 Subject: [PATCH 4/5] wip: split wayland out for a follow-up --- docs/descriptor-examples.md | 3 +- docs/package-types.md | 38 ++++- docs/zh/descriptor-examples.md | 3 +- docs/zh/package-types.md | 34 ++++- pkgs/c/compat.wayland.lua | 199 +++++++++++++++++++++++++ pkgs/f/freedesktop.wayland-scanner.lua | 45 ------ pkgs/f/freedesktop.wayland-server.lua | 45 ------ pkgs/f/freedesktop.wayland-util.lua | 45 ------ pkgs/f/freedesktop.wayland.lua | 45 ------ tests/examples/wayland/mcpp.toml | 31 ++-- 10 files changed, 288 insertions(+), 200 deletions(-) create mode 100644 pkgs/c/compat.wayland.lua delete mode 100644 pkgs/f/freedesktop.wayland-scanner.lua delete mode 100644 pkgs/f/freedesktop.wayland-server.lua delete mode 100644 pkgs/f/freedesktop.wayland-util.lua delete mode 100644 pkgs/f/freedesktop.wayland.lua 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/pkgs/c/compat.wayland.lua b/pkgs/c/compat.wayland.lua new file mode 100644 index 00000000..ea119622 --- /dev/null +++ b/pkgs/c/compat.wayland.lua @@ -0,0 +1,199 @@ +-- 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 deleted file mode 100644 index 977db894..00000000 --- a/pkgs/f/freedesktop.wayland-scanner.lua +++ /dev/null @@ -1,45 +0,0 @@ --- 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-1.tar.gz", - CN = "https://gitcode.com/mcpp-res/wayland/releases/download/1.26.0/wayland-1.26.0-1.tar.gz", - }, - sha256 = "e8f367ceb55cee5badc86f968797c525f81b082e6d10a51e915e8c3dcba9eb5d", - }, - }, - }, - - mcpp = "*/mcpp/scanner/mcpp.toml", -} diff --git a/pkgs/f/freedesktop.wayland-server.lua b/pkgs/f/freedesktop.wayland-server.lua deleted file mode 100644 index d4ed0955..00000000 --- a/pkgs/f/freedesktop.wayland-server.lua +++ /dev/null @@ -1,45 +0,0 @@ --- 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-1.tar.gz", - CN = "https://gitcode.com/mcpp-res/wayland/releases/download/1.26.0/wayland-1.26.0-1.tar.gz", - }, - sha256 = "e8f367ceb55cee5badc86f968797c525f81b082e6d10a51e915e8c3dcba9eb5d", - }, - }, - }, - - mcpp = "*/mcpp/server/mcpp.toml", -} diff --git a/pkgs/f/freedesktop.wayland-util.lua b/pkgs/f/freedesktop.wayland-util.lua deleted file mode 100644 index c97a15f4..00000000 --- a/pkgs/f/freedesktop.wayland-util.lua +++ /dev/null @@ -1,45 +0,0 @@ --- 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-1.tar.gz", - CN = "https://gitcode.com/mcpp-res/wayland/releases/download/1.26.0/wayland-1.26.0-1.tar.gz", - }, - sha256 = "e8f367ceb55cee5badc86f968797c525f81b082e6d10a51e915e8c3dcba9eb5d", - }, - }, - }, - - mcpp = "*/mcpp/util/mcpp.toml", -} diff --git a/pkgs/f/freedesktop.wayland.lua b/pkgs/f/freedesktop.wayland.lua deleted file mode 100644 index f600a510..00000000 --- a/pkgs/f/freedesktop.wayland.lua +++ /dev/null @@ -1,45 +0,0 @@ --- 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-1.tar.gz", - CN = "https://gitcode.com/mcpp-res/wayland/releases/download/1.26.0/wayland-1.26.0-1.tar.gz", - }, - sha256 = "e8f367ceb55cee5badc86f968797c525f81b082e6d10a51e915e8c3dcba9eb5d", - }, - }, - }, - - mcpp = "*/mcpp/client/mcpp.toml", -} diff --git a/tests/examples/wayland/mcpp.toml b/tests/examples/wayland/mcpp.toml index 1698d4ec..736fec60 100644 --- a/tests/examples/wayland/mcpp.toml +++ b/tests/examples/wayland/mcpp.toml @@ -1,18 +1,25 @@ -# wayland test member. +# Wayland test project. # -# Two packages now, not one with an ldflags escape hatch: the client and the -# server are distinct SONAMEs (Mesa's libEGL_mesa has DT_NEEDED on both), so -# they are two packages, and a compositor asks for the one it needs by name. +# Linux-only, dependency gated, no-op main() elsewhere — same shape as its +# neighbours. # -# Both also ship a C++23 module wrapper, and the test uses BOTH spellings — -# `#include ` and `import wayland.client;` — because the -# whole claim of the module layer is that they are the same entities. +# 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" -standard = "c++23" -[target.'cfg(linux)'.dependencies.freedesktop] -wayland = "1.26.0" -wayland-server = "1.26.0" -wayland-util = "1.26.0" +[target.'cfg(linux)'.dependencies.compat] +wayland = "2026.08.30" + +# 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"] From 0cdd8d8c0e490e20f6ac5f04554e15b421a609de Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Sun, 30 Aug 2026 10:43:48 +0800 Subject: [PATCH 5/5] libgbm: attribute the GBM_BACKENDS_PATH check instead of asserting it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The two checks that require GBM_BACKENDS_PATH were red on this PR's CI, and they had never actually run before: the workspace leg of #281 was CANCELLED, so the member merged without them ever executing. The variable is not this package's to set. It comes from xim-pkgindex's graphics discovery layer (openxlings/xim-pkgindex#713, merged today) and reaches the process through mcpp's subos-env handling, so whether it is present is decided by the ECOSYSTEM VERSION. mcpp-index's CI runs a pinned mcpp (MCPP_VERSION = 2026.8.27.2) whose vendored xlings predates that row — a hard assertion there is red everywhere the ecosystem is older and green everywhere it is current, which says nothing about the package. So the presence check is reported rather than asserted, and the checks that DO run when it is set are the real ones — the directory must exist AND hold a backend libgbm can dlopen, which is stronger than presence alone. A regression in either the DISCOVERY row or mcpp's env injection still lands here on any ecosystem that has the feature; on an older one the member says which requirement is missing and tests the package itself. Verified locally, where the ecosystem does declare it: both binaries green with the full backend-path checks running. --- tests/examples/libgbm/tests/gbm.cpp | 23 +++++++++++++++++---- tests/examples/libgbm/tests/stock_usage.cpp | 23 +++++++++++++++++---- 2 files changed, 38 insertions(+), 8 deletions(-) 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 {};