diff --git a/.agents/docs/2026-07-29-add-eui-neo-plan.md b/.agents/docs/2026-07-29-add-eui-neo-plan.md new file mode 100644 index 00000000..a5abcfd5 --- /dev/null +++ b/.agents/docs/2026-07-29-add-eui-neo-plan.md @@ -0,0 +1,482 @@ +# Design doc: add `compat.eui-neo` (EUI-NEO 0.5.3) + +Date: 2026-07-29 + +Follow-up to `.agents/docs/2026-07-28-add-eui-compat-deps-plan.md`, which landed the six +dependency packages as PR #131 and deferred the framework itself. This is that framework. + +**Depends on the five packages in `2026-07-29-add-gui-backend-packages-plan.md`** +(compat.vulkan / vulkan-headers / curl / sdl2 / glx-headers) — the `vulkan`, `sdl2` and +`network` features resolve against them. + +Rebuilt from upstream rather than derived from the first revision of this PR — see +"What the first revision got wrong" below for why. + +## Motivation + +[EUI-NEO](https://github.com/sudoevolve/EUI-NEO) is a declarative retained-mode C++17 UI +framework. This adds it in **header-compat shape only**: a consumer writes `#include `. The C++23 module surface +(`import eui;`) is explicitly out of scope — upstream ships no module interface units, so +`import` would mean hand-authoring wrappers over 40+ component headers. Header compat is a +prerequisite for that work, not an alternative to it. + +## Source and version + +| | | +|---|---| +| Upstream | `https://github.com/sudoevolve/EUI-NEO` | +| Version | `0.5.3` (latest release, published 2026-07-27) | +| Tarball | `archive/refs/tags/v0.5.3.tar.gz` | +| sha256 | `6951ac330d0307c633bafe720b7888bf32785103eb16973adb4ee05ef06e64d1` (computed twice, stable) | +| Wrap dir | `EUI-NEO-0.5.3/` — absorbed by the standard `*/` glob prefix, no `install()` hook | +| CN mirror | `gitcode.com/mcpp-res/eui-neo` @ `0.5.3`, byte-identical (see below) | +| License | Apache-2.0 | + +## Shape decision: C++ source compat (Form B), deps reused from the index + +Upstream vendors its whole dependency set under `3rd/` — freetype, glfw, libpng, zlib, +glad, tray, yyjson, md4c, all fully checked in (no submodules). **None of them are built +here.** Each already exists in this index at the same version upstream pins, and building +them once for the ecosystem is the entire point of having them: + +| upstream `3rd/` | index package | version match | +|---|---|---| +| `3rd/freetype` | `compat.freetype` | 2.13.3 | +| `3rd/libpng-1.6.43` | `compat.libpng` | 1.6.43 | +| `3rd/zlib-1.3.1` | `compat.zlib` | 1.3.2 | +| `3rd/glfw` | `compat.glfw` | 3.4 | +| `3rd/glad` | `compat.glad` | 0.0.0-651a425 — the exact commit `3rd/dependencies.cmake` fetches | +| `3rd/tray` | `compat.tray` | 0.0.0-8dd1358 | +| `3rd/yyjson-0.12.0` | `compat.yyjson` | 0.12.0 | +| `3rd/md4c` | `compat.md4c` | 0.5.3 (feature-gated) | + +`3rd/` still sits on the include path: `stb_image.h`, `nanosvg.h` and `nanosvgrast.h` are +genuinely vendored single-file headers at its root, and the sources include them as +`"3rd/stb_image.h"`. + +The build recipe tracks upstream `CMakeLists.txt` v0.5.3: `CORE_SOURCES` + the OpenGL +backend + glfw's `ime_bridge.c` = 20 translation units. + +### Backend selection is a build-time constant, not a feature + +Upstream picks the render and window backend at configure time and compiles exactly one +in. Only `opengl` + `glfw` are modelled. `vulkan`, `sdl2` and `network` would need +`compat.vulkan`, `compat.sdl2` and `compat.curl`, none of which exist in this index — +declaring features whose deps cannot resolve only moves the failure downstream, so they +are omitted rather than stubbed. + +### One TU goes through a generated stub (mcpp#233/#240) + +`core/platform/platform.cpp` is **not** declared directly. mcpp emits every package's +objects into a single flat per-link `obj/` directory keyed by source basename, so upstream's +`core/platform/platform.cpp` and `compat.glfw`'s `src/platform.c` both want `platform.o`. + +This is not theoretical. On a cold 646-object link with the naive declaration, `platform.o` +was absent entirely and **both** packages lost their TU — neither `core::platform::*` nor +`_glfwSelectPlatform` reached the binary. It still linked green, because the minimal test +happened to reference neither. A real EUI application would not be so lucky, and neither +would any consumer of `compat.glfw` that links a sibling package with a `platform.*`. + +The fix is the technique `compat.opencv5` established for its `modules/*/src` collisions: a +uniquely named `generated_files` stub that `#include`s the real source. + +```lua +generated_files = { + ["mcpp_generated/eui_neo_platform_tu.cpp"] = "#include \"core/platform/platform.cpp\"\n", +}, +sources = { …, "mcpp_generated/eui_neo_platform_tu.cpp" }, +``` + +Renaming one side is enough: with `platform.o` no longer contested, glfw's object survives +too. `tests/examples/eui-neo` now calls `core::platform::consumeFrameRequest()` so a +regression becomes an undefined reference rather than a silent pass. + +A scan of eui-neo's 20 sources against every transitively linked package +(freetype/libpng/zlib/glfw/glad/tray/yyjson/opengl + the X11 stack) found `platform` to be +the **only** collision. + +### Linux tray is deliberately a no-op + +Upstream sets `EUI_TRAY_APPINDICATOR=1` only when pkg-config finds **both** GTK3 and +libappindicator. This index carries neither, so the Linux profile sets no tray define at +all and `tray_bridge.c` compiles its `EUI_TRAY_HAS_BACKEND 0` stub — which is exactly what +upstream produces on a machine without those dev packages. Windows (`EUI_TRAY_WINAPI`) and +macOS (`EUI_TRAY_APPKIT`, Cocoa-native) get real tray backends. + +## Backend selection — and why `default` cannot express it + +Upstream selects both backends at configure time and compiles exactly one of each: +`core/render/render_backend.cpp` dispatches on +`#if defined(EUI_RENDER_BACKEND_OPENGL) … #elif defined(…VULKAN)`, and +`core/window/window_backend.cpp` on `#if defined(EUI_WINDOW_BACKEND_SDL2)` / else-GLFW. +Define both halves of either pair and the first silently wins — the caller's choice is +ignored, and since neither backend runs headless, CI would never notice. + +mcpp features are additive and there is no `default-features = false` (mcpp#242 — the same +wall that stopped the ffmpeg trimmed profiles, see +`2026-07-19-compat-ffmpeg-trimmed-profiles-decision.md`). Three encodings were tried and +**each failed silently**, so each is written down: + +| encoding | behaviour | +|---|---| +| `default = { defines/sources/deps = … }` | **inert** — never applied at all | +| `default = { implies = { … } }` | **always** applied, even when the consumer names a different feature | +| package-level define + additive feature | a feature cannot unset a define | + +Verified on both mcpp 0.0.109 (the CI pin) and 2026.7.29.1 (latest at time of writing) — +the behaviour is identical, so this is not something a version bump fixes. + +The middle row is what makes the first row dangerous: an early revision read it as Cargo's +rule ("suppressed when features are named"), which looks identical from one observation. +What disproved it was the plain `eui-neo` member passing its smoke test while +`createRenderBackend()` compiled to its `#else` branch and returned a null backend, because +nothing in a headless test ever asks for one. + +### What works + +mcpp passes `-DMCPP_FEATURE_` for every enabled feature into the package's own +translation units. The exclusivity is therefore resolved in the preprocessor, by a +force-included generated header, and the features carry only sources and dependencies: + +```c +#if defined(MCPP_FEATURE_VULKAN) +# define EUI_RENDER_BACKEND_VULKAN 1 +#else +# define EUI_RENDER_BACKEND_OPENGL 1 +#endif +#if defined(MCPP_FEATURE_SDL2) +# define EUI_WINDOW_BACKEND_SDL2 1 +#endif +``` + +Strictly better than anything built on `default` would have been: naming an unrelated +feature no longer drops the backends. + +```toml +eui-neo = "0.5.3" # opengl + glfw +eui-neo = { …, features = ["vulkan"] } # vulkan + glfw +eui-neo = { …, features = ["sdl2"] } # opengl + SDL2 +eui-neo = { …, features = ["vulkan","sdl2"] } # vulkan + SDL2 +eui-neo = { …, features = ["markdown"] } # opengl + glfw, markdown on +``` + +### `cflags` is C-only + +Force-including that header exposed a second problem: **mcpp routes `cflags` to C +translation units and `cxxflags` to C++ ones.** With `-include` in `cflags` alone, exactly +three objects received it — `ime_bridge.c`, `native_bridge.c`, `tray_bridge.c` — and every +`.cpp` compiled without it. + +An earlier revision of this descriptor carried +`cflags = { "-DEUI_RENDER_BACKEND_OPENGL=1" }` and nothing else, so `render_backend.cpp` +never saw it: the package built, linked, passed its tests, and had no render backend at +all. Both lists now carry the flag; `NOMINMAX` on Windows got the same treatment. + +### Verified structurally + +A passing test proves nothing here. What is checked is which backend each member's dispatch +translation unit actually references: + +| member | `render_backend.o` → | `window_backend.o` → | +|---|---|---| +| `eui-neo` (no features) | `OpenGLRenderBackend` | `glfwCreateWindow` | +| `eui-neo-vulkan` | `VulkanRenderBackend` | `glfwCreateWindow` | +| `eui-neo-sdl2` | `OpenGLRenderBackend` | `SDL_CreateWindow` | +| `eui-neo-markdown` | `OpenGLRenderBackend` | `glfwCreateWindow` | + +The last row is the one that catches a `default`-based regression. + +## Features + +| feature | gates | default | +|---|---|---| +| `vulkan` | Vulkan render backend + `compat.vulkan` | off (OpenGL) | +| `sdl2` | SDL2 window backend + `compat.sdl2` | off (GLFW) | +| `network` | `compat.curl` + `EUI_HAS_CURL=1` | off | +| `app-main` | `core/app/glfw_app_main.cpp` — upstream's `int main()` and render loop | off | +| `app-main-sdl2` | `core/app/sdl2_app_main.cpp`, the SDL2 counterpart | off | +| `markdown` | `compat.md4c` dep + `EUI_HAS_MD4C=1` interface define | off | + +**`app-main`** is a sources-only gate, the direct analogue of `compat.gtest`'s `main` +(gtest_main.cc). CMake adds this file per-application (`EUI_APP_MAIN_SOURCE`), never to +the library, for the same reason it is opt-in here: a consumer with its own `main()` must +not be handed a second one. A real EUI application enables it and supplies only +`app::dslAppConfig()` and `app::compose()`. + +**`markdown`** is the more interesting one. `components/markdown.h` is header-only and +compiles one of *two* definitions of `detail::parseMarkdownBlocks` depending on +`EUI_HAS_MD4C` — the md4c parser, or a fallback that wraps the entire source in one +Paragraph. The library itself gains no translation unit either way, so the whole feature +lives on the consumer side. That is why the define goes in `defines` (an INTERFACE define, +propagated to the consumer's TUs) rather than `cflags` (package-private): with `cflags`, +md4c would link and the component would still silently compile out. + +Note the skill doc's "features 仅能门控 sources" reflects mcpp 0.0.68. On the pinned +0.0.109, `defines` / `deps` / `implies` / `requires` / `provides` are all accepted — see +`compat.eigen`, `chriskohlhoff.asio`, `compat.spdlog`. + +## Consumer contract (worth knowing before using this package) + +`eui_neo.h` pulls in `eui/detail/dsl_app_impl.h`, which emits `app::update()` / +`app::render()` into the *consumer's* translation unit and leaves two symbols for the +application to define: + +```cpp +namespace app { +const DslAppConfig& dslAppConfig(); +void compose(eui::Ui& ui, const eui::Screen& screen); +} +``` + +Omitting them is a link error, not a compile error. This mirrors upstream's +`examples/*.cpp`, all of which define exactly these two. Both test members do the same. + +## Verification + +Local, mcpp **0.0.109** (matching `validate.yml` `env.MCPP_VERSION`), linux-x86_64, gcc 16.1.0. + +All four workspace members pass, cold, on all three platforms in CI: + +``` +$ mcpp test -p eui-neo +compat.eui-neo smoke test: ok (parsed eui-neo v3, markdown gated off) + +$ mcpp test -p eui-neo-markdown +compat.eui-neo[markdown]: ok (2 blocks, h1 = 'Heading') + +$ mcpp test -p eui-neo-vulkan # windows asserts the default OpenGL build +compat.eui-neo[vulkan]: ok (backend=vulkan, loader api 1.4.357) + +$ mcpp test -p eui-neo-sdl2 +compat.eui-neo[sdl2,network]: ok (SDL driver=dummy, curl 8.21.0 ssl=OpenSSL/3.5.1) +``` + +### The library really is built + +The first revision passed CI while compiling **zero** translation units (see below), so +this is checked against the objects rather than inferred from a green test. Per-package +counts from the build cache: + +``` +compat.eui-neo@0.5.3 20 objs <- exactly the 20 declared sources +compat.freetype@2.13.3 29 objs +compat.glfw@3.4 23 objs +compat.libpng@1.6.43 15 objs +compat.zlib@1.3.2 15 objs +compat.x11@1.8.13 406 objs +compat.yyjson@0.12.0 1 obj +compat.glad@… 1 obj +compat.tray@… 1 obj +``` + +The default member's assertions run on `eui::json::Document` (`core/platform/json.cpp`) +and `core::platform::consumeFrameRequest()` (`core/platform/platform.cpp`) — an empty or +partial library fails at **link** time instead of silently passing. + +### The mcpp#233 collision fix, measured + +Cold link of `tests/examples/eui-neo`, before vs after routing platform.cpp through the +generated stub: + +| | objects in the link | `core::platform::*` | `_glfwSelectPlatform` | +|---|---|---|---| +| before | 646 | absent | absent | +| after | 648 | `eui_neo_platform_tu.o` | `platform.o` | + +Two objects recovered: eui-neo's TU, and `compat.glfw`'s `platform.o` that the contested +name had been taking down with it. + +### Real GUI verification, on a machine with a display + +CI is headless, so the graphics path was unverified. Run locally on a workstation with an +X display and an RTX 4080 (OpenGL 4.6, Vulkan 1.3, plus lavapipe): a harness that opens a +real window, creates the render backend through `core::render::createRenderBackend`, and +drives three full frames (`beginFrame` → `ensureRenderCache` → `beginRenderCacheFrame` → +`blitRenderCache` → `present`). + +**All four backend combinations render, with no environment variables set:** + +| combination | result | +|---|---| +| OpenGL + GLFW | ok — 3 frames presented at 320x240 | +| OpenGL + SDL2 | ok — 3 frames presented | +| Vulkan + GLFW | ok — 3 frames presented | +| Vulkan + SDL2 | ok — 3 frames presented | + +Getting the Vulkan half there took two fixes, both of which are in this PR and neither of +which was visible from a headless test. + +#### 1. `compat.vulkan-runtime` — host ICDs were unreachable + +The loader found every ICD manifest on the system and then failed to `dlopen` a single +driver: + +``` +DRIVER: Found the following files: /usr/share/vulkan/icd.d/lvp_icd.json … (9 of them) +ERROR: libvulkan_lvp.so: cannot open shared object file: No such file or directory +``` + +The libraries are in `/usr/lib/x86_64-linux-gnu`. What cannot reach them is the process: +an mcpp binary runs under mcpp's **own** glibc (`interp: …/xim-x-glibc/2.39/…`, rpath +covering only mcpp's own trees), so a bare-soname `dlopen` never searches the host's path. +That is deliberate — it is what makes builds reproducible — and it is exactly the problem +`compat.glx-runtime` already solves for OpenGL, which is *why* the OpenGL rows passed from +the start. + +`compat.vulkan-runtime` is the Vulkan counterpart: a symlink farm plus +`runtime.library_dirs`, no vendored driver. Instance extensions went 4 → 22. + +Two details worth keeping: + +- **Versioned sonames only.** mcpp puts `runtime.library_dirs` on the LINK line too, so a + bare `libxcb.so` in the farm shadows this index's own `compat.xcb` and the link fails on + `XauDisposeAuth`. Versioned names are invisible to the linker and are exactly what + `dlopen` asks for. +- **The closure must be complete.** A farm with `libxcb.so.1` but not `libXau.so.6` + shadows a host copy that *would* have resolved, and the executable then fails to start. + +#### 2. `compat.vulkan` had to be a shared library + +With the ICDs reachable, Vulkan + GLFW rendered but Vulkan + SDL2 still failed — +`createWindow`, then `createSurface` once a shared loader was on the path. The cause is +structural: `SDL_CreateWindow(SDL_WINDOW_VULKAN)` calls `SDL_Vulkan_LoadLibrary(NULL)`, +which **dlopens `libvulkan.so.1`** and resolves surface creation through whatever it finds. +Against a statically linked loader the application ends up with two of them — its own for +`vkCreateInstance`, SDL's for `vkCreateXlibSurfaceKHR` — and the surface call gets an +instance its loader never saw. + +So `compat.vulkan` builds `kind = "shared", soname = "libvulkan.so.1"` on Linux, the same +shape the X11 family in this index already uses. The declaration is **inside the linux +block**, not at the top: a shared target propagates `-fPIC` to consumers, and clang rejects +that outright for the msvc target. A platform block's `targets` does override the base one +(`compat.ffmpeg` declares one per platform), so Windows keeps a plain lib around its import +library. Everything converges on one object: the +application links it, GLFW is handed its `vkGetInstanceProcAddr` through +`glfwInitVulkanLoader`, and SDL's `dlopen` lands on it by soname. That is also simply what +the Vulkan loader is designed to be. + +#### One upstream-shaped bug found + +`glfwInitVulkanLoader` must be called **before** `glfwInit`. Upstream's +`glfw_app_main.cpp` gets this right (line 400, one above its `glfwInit`); a consumer +writing its own entry point has to as well, or GLFW reports +`GLFW_API_UNAVAILABLE "Vulkan: Loader not found"`. + +### `app-main`, and what is NOT covered### `app-main`, and what is NOT covered + +`app-main` has no workspace member, because a member that enables it cannot run on a CI +runner: the feature's whole point is that `main()` comes from upstream's render loop, which +calls `glfwInit()` and opens a window. Verified out-of-tree on linux-x86_64 instead, with a +throwaway member whose only source defines `dslAppConfig()` + `compose()` and no `main`: + +``` +Compiling compat.eui-neo v0.5.3 +Compiling probe (test) + Running bin/probe +probe ... FAIL (exit 255) <- headless; glfwInit() has no display + +obj/glfw_app_main.o present, 648 objects in the link +nm: 0000000000001897 T main <- main comes from the feature, not the consumer +``` + +So the gate compiles, supplies `main()`, and links against a consumer that has none. It is +**not** verified on macOS or Windows, and the render loop is never executed anywhere. + +More generally, nothing in this package's CI test surface draws a frame. Every member is +headless by construction, so what CI proves is: the library builds on three platforms, its +umbrella header is consumable, and the non-graphical facades (JSON, platform frame flags, +markdown parsing) behave. Window creation, GL context setup, text rasterization, image +decode, input and IME are all **unexercised**, as are the Windows WinAPI and macOS AppKit +tray paths — they compile, they have never run. + +Not modelled at all, and therefore not usable through this package: the `vulkan` render +backend, the `sdl2` window backend, and `network` / `EUI_HAS_CURL`. Linux tray is a +compiled no-op. See the sections above for why. + +### Feature verification (both directions) + +- **negative** — `tests/examples/eui-neo` does not request `markdown`, and asserts + `parseMarkdownBlocks("# Heading\n\nBody text.\n")` returns the degenerate single + Paragraph. Feature on by accident ⇒ this member fails. +- **positive** — `tests/examples/eui-neo-markdown` requests it long-form and asserts an h1 + block with text `Heading` plus ≥2 blocks. Interface define failing to propagate ⇒ this + member fails (it `#if !defined(EUI_HAS_MD4C)`s to an explicit failure first). + +## CN mirror + +Published to gitcode `mcpp-res` per `docs/cn-mirror.md`, so the `url` is a +`{ GLOBAL, CN }` table on all three platforms: + +``` +repo https://gitcode.com/mcpp-res/eui-neo +CN https://gitcode.com/mcpp-res/eui-neo/releases/download/0.5.3/eui-neo-0.5.3.tar.gz +``` + +Closed-loop verified — the asset is the byte-identical GLOBAL tarball, not a repack: + +``` +CN http=200 +GLOBAL=6951ac330d0307c633bafe720b7888bf32785103eb16973adb4ee05ef06e64d1 +CN =6951ac330d0307c633bafe720b7888bf32785103eb16973adb4ee05ef06e64d1 +BYTE-IDENTICAL +``` + +## What the first revision got wrong + +Recorded because the failure mode is subtle and CI did not catch it. "It" here is the +descriptor this PR originally proposed, before the rewrite. + +1. **The package compiled nothing, and CI was green.** Its `install()` hook guessed the + tarball's wrap directory as `main` / `EUI-NEO-` / `EUI-NEO-main`; the actual + name was `EUI-NEO-M-main` (it pointed at a personal fork's branch archive). All three + guesses missed, `os.tryrm(install_dir())` then removed the install dir, `os.mv` failed, + and the hook `return true`d anyway — so mcpp recorded a successful install over a + directory that did not exist. Every source glob then matched zero files. The smoke test + was `import std; println(...)` referencing no EUI symbol, so even the link succeeded. + Measured: `0 objs` for `compat.eui-neo`, against 29/15/23 for freetype/libpng/glfw. +2. **The `install()` hook should not exist.** House style (`docs/package-types.md`, + `compat.md4c`, `compat.libpng`) absorbs the wrap layer with a `*/` glob prefix; the first + revision removed those prefixes to compensate for its own hook. +3. **"Form B → Form A include propagation not supported" was a misdiagnosis.** + `tests/examples/freetype` does `#include ` against a Form B package and + passes on main. Headers were unreachable because the verdir did not exist. +4. **`sha256 = ""`** disabled integrity verification on all three platforms, against a + **moving branch head** (`refs/heads/main`) of a personal fork, while `repo` pointed at + upstream. Upstream tags `v0.5.3`; this descriptor pins it with a real digest. +5. **Dead paths.** `3rd/yyjson-0.12.0/src/yyjson.c` and `3rd/tray` do not exist in that + fork's archive, and `compat.yyjson` / `compat.tray` / `compat.glad` — three of the six + packages #131 added *for this framework* — were not declared as deps at all. +6. **Unconditional `-DEUI_TRAY_APPINDICATOR=1` on Linux**, which upstream only sets when + GTK3 + libappindicator are present. It would have required GTK3 headers this index does + not carry. +7. **Features with no resolvable deps** (`vulkan`, `sdl2`, `network`) — defines only, no + packages behind them. + +## Windows: `-fno-char8_t` + +`parseWindowsSelection()` in `core/platform/platform.cpp` pushes `path::u8string()` into a +`std::vector`. C++20 changed that return type to `std::u8string`, so the line +does not compile at this index's c++23 floor — upstream builds at `CMAKE_CXX_STANDARD 17` +and never sees it. It sits inside `#if defined(_WIN32)`, so Linux and macOS do not either. + +`language = "c++17"` is not an option (mcpp accepts c++23 and up). The root cause is +`char8_t` rather than the standard level — every STL selects the `u8string()` return type +on `__cpp_char8_t` — so the Windows profile carries `cxxflags = { "-fno-char8_t" }` and the +package stays at c++23 everywhere. + +Worth fixing upstream: `wideToUtf8()` already sits eight lines above and does the right +thing. Until then this is what keeps the descriptor on a real upstream release tag instead +of a fork carrying the patch (which is what the first revision did). + +Note this only became visible once `platform.cpp` was actually being compiled — the +mcpp#233 collision above had been silently dropping the TU, so no compiler ever saw the +line. + +## Follow-up + +- Upstream PR for the `u8string()` line, after which `-fno-char8_t` can go. +- A GUI smoke test that CI can actually run — Xvfb plus Mesa llvmpipe would make the + harness above reproducible on a runner. +- `compat.vulkan` / `compat.sdl2` / `compat.curl` would unlock the corresponding backends. +- The C++23 module layer (`import eui;`) remains open, and now has a working header-compat + base to build on. diff --git a/.agents/docs/2026-07-29-add-gui-backend-packages-plan.md b/.agents/docs/2026-07-29-add-gui-backend-packages-plan.md new file mode 100644 index 00000000..6a8d7fdb --- /dev/null +++ b/.agents/docs/2026-07-29-add-gui-backend-packages-plan.md @@ -0,0 +1,193 @@ +# Design doc: five packages behind the GUI/network backends + +Date: 2026-07-29 + +| package | version | what it is | +|---|---|---| +| `compat.vulkan-headers` | 1.4.357.0 | Khronos Vulkan headers | +| `compat.vulkan` | 1.4.357.0 | Khronos Vulkan loader, built from source | +| `compat.curl` | 8.21.0 | libcurl, OpenSSL on unix / Schannel on Windows | +| `compat.sdl2` | 2.32.10 | SDL2 | +| `compat.glx-headers` | 1.7.0 | libglvnd's `GL/glx.h` | + +They land together because they are what an alternate-backend GUI stack needs, and because +each one exercised the same class of problem: an upstream that generates its build +configuration, where this index wants a plain source list. They are independently useful — +nothing here depends on any consumer. + +All five build from plain source lists: no CMake, no autotools, no `install()` hook. + +## `compat.vulkan-headers` + `compat.vulkan` + +Split along upstream's own repository split. Headers are header-only (the `compat.opengl` +shape: include root plus an anchor TU). The loader is what a Vulkan program actually +links — `vkCreateInstance` and friends are loader trampolines dispatching into whatever ICD +the system advertises. + +**A plain source list works because two things happen to be true:** + +- `loader/generated/` (`vk_loader_extensions.c`, `vk_object_types.h`, …) is checked in + upstream, so CMake's codegen step is unnecessary. +- The assembly path is optional. Upstream compiles `dev_ext_trampoline.c` + + `phys_dev_ext.c` against hand-written GAS/MASM plus a `gen_defines.asm` that requires + building *and running* `asm_offset`, then scraping its output with Python. That chain is + gated on `UNKNOWN_FUNCTIONS_SUPPORTED`; upstream itself degrades when no assembler is + found and `unknown_function_handling.c` compiles a pure-C fallback. We take the fallback + deliberately — the cost is that unknown *device* extension entry points get no + trampoline, which nothing in this index uses. + +Two requirements only a real build surfaces: + +- `VK_ENABLE_BETA_EXTENSIONS` is mandatory despite the name: the checked-in + `vk_object_types.h` references `VK_OBJECT_TYPE_CUDA_MODULE_NV`, which the headers only + declare under it. Without it the loader does not compile at all. +- `SYSCONFDIR` / `FALLBACK_*_DIRS` must arrive as string literals, and + `-DSYSCONFDIR="/etc"` does not survive mcpp's flag splitting (mcpp#234): `loader.c` sees + a bare `/etc` and fails with *expected expression before '/' token*. They ride in a + force-included generated header instead. + +WSI is enabled per platform — Xlib + XCB on Linux (which makes `compat.x11` / `compat.xcb` +/ `compat.xorgproto` a **compile** dependency, since `vulkan_xlib.h` includes +``), Metal + MVK on macOS. Search paths are the FHS/XDG defaults because the +package must find the *host's* ICDs. + +### Windows is deferred + +A statically linked loader is not something upstream supports there: the only static option +in its CMake is `APPLE_STATIC_LOADER`, gated to macOS with the warning that it "will only +work on MacOS and is not supported" elsewhere. Built anyway, the Windows loader links and +then faults at the first entry point (0xC0000005 out of `vkEnumerateInstanceVersion`). +Linux is not covered by that option either, but a static loader is the ordinary case there +and works. Rather than ship a package that crashes, this follows `compat.openssl` and +declares no windows xpm entry; consumers gate with `[target.'cfg(...)']`. + +## `compat.curl` + +Full-source direct build, which works because curl compiles every unselected protocol and +TLS backend to an EMPTY translation unit (`vtls/gtls.c` is `#ifdef USE_GNUTLS` end to end). +The source list is plain globs; only `lib/dllmain.c` is excluded. + +The config header is the awkward part, and only on unix: `lib/config-win32.h` is checked in +and `curl_setup.h` selects it automatically when `HAVE_CONFIG_H` is absent, so Windows needs +nothing generated. linux/macosx get a generated `curl_config.h` branching on `__linux__` / +`__APPLE__`, with the Apple branch deliberately the conservative subset — an omitted +`HAVE_*` costs curl a fallback path, never correctness. + +Generating it against the *right* compiler mattered: a first pass with the host `cc` +produced a config asserting `ssize_t` did not exist (its probe failed for an unrelated +sysroot reason), and curl then failed to compile against its own config. + +TLS is OpenSSL on linux/macosx via `compat.openssl`, and Schannel on Windows — built into +the OS, and necessary because `compat.openssl` has no Windows build. + +### `CURL_STATICLIB`, and a use for an mcpp quirk + +`` declares everything `__declspec(dllimport)` unless `CURL_STATICLIB` is set, +so every Windows consumer of a static libcurl needs it — and no consumer should have to +know that. The define has to be **always on AND consumer-visible**, and mcpp has no single +key for that: `cflags` is always on but package-private, while a feature's `defines` reach +the consumer but need naming. + +`default = { implies = … }` is the combination that gives both. A default feature's own +`defines` are inert on mcpp (verified on 0.0.109 and 2026.7.29.1), but what it *implies* is +applied unconditionally — including when the consumer names some other feature. That is a +liability for expressing an exclusive choice; here "unconditionally on" is exactly right: + +```lua +features = { + ["default"] = { implies = { "staticlib" } }, + ["staticlib"] = { defines = { "CURL_STATICLIB" } }, +} +``` + +`tests/examples/curl` deliberately does **not** define it, and `static_assert`s that it +arrived from the package. Verified in both directions — deleting the `default` line makes +the assertion fire. + +## `compat.sdl2` and `compat.glx-headers` + +SDL is the mirror image of curl: upstream **checks in** `SDL_config_windows.h` and +`SDL_config_macosx.h` and dispatches to them from `include/SDL_config.h`; only Linux falls +through to `SDL_config_minimal.h`, which builds an SDL that can do essentially nothing. So +the generated header reproduces upstream's dispatch for windows/macosx and carries CMake's +Linux output inline. + +`compat.glx-headers` exists because SDL's X11 driver includes ``, and **no +package in this index had it** — `GL/glx.h` is not part of the Khronos OpenGL-Registry, so +`compat.opengl` cannot supply it. libglvnd is the canonical provider and is already named +as a wanted package in `2026-06-03-gl-runtime-packages-plan.md`. Headers only; +`compat.glx-runtime` still owns the runtime side. It overlaps `compat.opengl` on +`GL/gl.h`, so depend on one or the other, never both. + +## Things only CI could find + +Linux passed on the first try; every one of these was silent or misleading locally. + +- **SDL2's tag archive cannot be extracted on Windows.** Two POSIX symlinks under + `android-project-ant/`. The package then "installs" empty: reports success, compiles + nothing, exports no include dirs, and every consumer fails with `'SDL.h' file not found` + while the package itself never errors. `chriskohlhoff.asio` hit this and set the + precedent, so GLOBAL points at a symlink-free repack on `xlings-res/sdl2` with the + identical bytes mirrored to `mcpp-res`. Only the two symlink entries are removed. +- **SDL on Apple requires ARC.** Its cocoa classes declare `__weak SDL_WindowData *_data`, + and a `__weak` ivar without `-fobjc-arc` is rejected outright (`'_data' is unavailable`). + Upstream's CMake hard-fails when the compiler cannot do ARC, for this reason. +- **SDL on Windows needs `HAVE_LIBC`.** Upstream's config only sets it for `_MSC_VER`, and + this index builds with clang++, so SDL compiled its own `memcpy` and collided with + libvcruntime. +- **SDL's Windows condvar falls back to `thread/generic/SDL_syscond.c`** — but the rest of + `thread/generic` shares basenames with `thread/windows`, so only that one file can be + added. mcpp keys objects by basename in one flat per-link directory (mcpp#233), and + pulling the whole directory would have them silently displace each other. +- **SDL's macOS config also enables the X11 driver**, dynamically loaded from `/opt/X11`. + That would make `src/video/x11` a compile dependency on Apple for a driver nobody reaches + with Cocoa present; the generated header `#undef`s it. +- **`src/joystick/iphoneos` serves macOS too** — `SDL_JOYSTICK_MFI` is on in upstream's + macosx config and `SDL_gamecontroller.c` calls into it. +- **curl must be told which `strerror_r` it has** and refuses to build otherwise. Apple + ships the POSIX one; glibc's returns `char*`. +- **curl needs `secur32` on Windows** (`InitSecurityInterfaceA`, for the SSPI auth Schannel + builds on) and `CoreFoundation` + `SystemConfiguration` on macOS (proxy discovery). +- **The Linux SDL config's X11 had to be switched on by hand** — the CMake probe ran on a + host without system X11 development headers — and libudev switched off, since it is a + host library this index does not package. +- **`src/core/linux` is the one directory where a glob over-reaches**: `SDL_dbus.c` / + `SDL_ibus.c` / `SDL_fcitx.c` / `SDL_ime.c` are not internally guarded and simply fail + without dbus-1 headers. + +## Verification + +Cold, mcpp 0.0.109 (the `validate.yml` pin). All three platforms in CI, every member built +and run: + +| | linux | macos | windows | +|---|---|---|---| +| `compat.vulkan` | `loader api 1.4.357, WSI trampolines linked` | same | skipped (deferred) | +| `compat.curl` | `ssl=OpenSSL/3.5.1` | `ssl=OpenSSL/3.5.1` | `ssl=Schannel` | +| `compat.sdl2` | `driver=dummy, 320x240 window, events round-tripped` | same | same | + +### What the tests can and cannot assert + +- **SDL2 genuinely runs.** Its `dummy` video driver is a real driver, so the test executes + `SDL_Init`, `SDL_CreateWindow`, `SDL_GetWindowSize` and a full event round-trip. It is + the only one here that can be exercised rather than merely linked. +- **Vulkan is driverless by construction.** The loader advertises WSI *surface* extensions + only when an ICD supports them, so `VK_KHR_surface` is legitimately absent on a runner + with no GPU — an early draft asserted on it and failed, which is exactly the "testing the + runner's hardware" trap. Asserted instead: `vkEnumerateInstanceVersion` (answerable only + by the loader), `VK_EXT_debug_utils` (loader-implemented, driver-independent), and a + reference to `vkDestroySurfaceKHR` as link-time proof that `wsi.c` is in the library. +- **curl never opens a connection.** Asserted: init succeeds, `CURL_VERSION_SSL` with a + non-null `ssl_version` (a curl built without TLS still links and runs — it just cannot do + https), http+https in the protocol list, and that a handle accepts a real option set. + +## CN mirrors + +All five published to gitcode `mcpp-res`, byte-identical to GLOBAL, 200 on `curl`. SDL2's +GLOBAL is the `xlings-res/sdl2` repack described above rather than the upstream tag +archive. + +## Follow-up + +- `compat.vulkan` on Windows, if a supported static-loader path appears upstream. +- `compat.sdl2` currently has no Wayland backend; X11 only on Linux. diff --git a/README.md b/README.md index cb7d7e1f..cae6c1d4 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,13 @@ mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上 | 原生模块库(Form A) | [`mcpplibs.xpkg`](pkgs/x/xpkg.lua) · [`mcpplibs.tinyhttps`](pkgs/t/tinyhttps.lua) · [`tensorvia-cpu`](pkgs/t/tensorvia-cpu.lua) · [`ffmpeg`](pkgs/f/ffmpeg.lua)(模块层,源码经 `compat.ffmpeg` 直编) · [`opencv`](pkgs/o/opencv.lua)(单仓库:模块层与 OpenCV 5 全源码构建同在包内,索引侧只留本描述符) | | C 源码 compat(含 `features`) | [`compat.cjson`](pkgs/c/compat.cjson.lua) · [`compat.zlib`](pkgs/c/compat.zlib.lua) | | header-only(含 `features`) | [`compat.eigen`](pkgs/c/compat.eigen.lua) | +| 运行时 loader compat(纯源码,绕开上游 codegen/asm) | [`compat.vulkan`](pkgs/c/compat.vulkan.lua)(Khronos loader:`loader/generated/` 已签入,汇编路径经 `UNKNOWN_FUNCTIONS_SUPPORTED` 降级为纯 C,故无需 CMake/Python/汇编器;windows 延后)· [`compat.vulkan-headers`](pkgs/c/compat.vulkan-headers.lua) | +| 全源码直编 + 生成 config(仅缺口平台) | [`compat.curl`](pkgs/c/compat.curl.lua)(win32 用上游签入 config,unix 生成) · [`compat.sdl2`](pkgs/c/compat.sdl2.lua)(win/mac 用上游签入 config,linux 生成 + 手工开 X11) | +| 补索引空缺的头文件包 | [`compat.glx-headers`](pkgs/c/compat.glx-headers.lua)(libglvnd 的 `GL/glx.h`,Khronos registry 不含,SDL 的 X11 后端必需) | +| C++ 应用框架 compat(依赖复用索引内既有包) | [`compat.eui-neo`](pkgs/e/compat.eui-neo.lua)(上游 `3rd/` 自带 8 个 vendored 依赖,此处一个不编,全部改指索引内同版本 `compat.*`) | +| 互斥后端(同包多后端二选一) | [`compat.eui-neo`](pkgs/e/compat.eui-neo.lua) 的 `opengl`/`vulkan` 与 `glfw`/`sdl2`。**`default` feature 在 mcpp 上不可用**(带 `defines/sources/deps` 完全不生效;带 `implies` 反而恒生效),可行解是用 `-DMCPP_FEATURE_` 在强制包含头里做前置判定。另注意 `cflags` 只作用于 C TU,C++ 需 `cxxflags` | +| 宿主运行时适配(不 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 —— `library_dirs` 同时进链接行) | +| 恒开的 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) | | 全源码直编(config 快照 + 源列表,零外部构建系统) | [`compat.ffmpeg`](pkgs/c/compat.ffmpeg.lua)(2281 TU 含 NASM 汇编,28 个目录 glob 声明) | diff --git a/mcpp.toml b/mcpp.toml index 33720562..e762991a 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -17,7 +17,12 @@ members = [ "tests/examples/catch2-v2-main", "tests/examples/cjson", "tests/examples/core", + "tests/examples/curl", "tests/examples/eigen", + "tests/examples/eui-neo", + "tests/examples/eui-neo-markdown", + "tests/examples/eui-neo-sdl2", + "tests/examples/eui-neo-vulkan", "tests/examples/ffmpeg", "tests/examples/ffmpeg-module", "tests/examples/fmtlib.fmt", @@ -32,6 +37,7 @@ members = [ "tests/examples/opencv-module", "tests/examples/opencv-module-dnn", "tests/examples/opencv-module-unifont", + "tests/examples/sdl2", "tests/examples/spdlog", "tests/examples/freetype", "tests/examples/glad", @@ -39,6 +45,7 @@ members = [ "tests/examples/md4c", "tests/examples/spdlog-compiled", "tests/examples/tinyhttps", + "tests/examples/vulkan", "tests/examples/tray", "tests/examples/yyjson", ] diff --git a/pkgs/c/compat.curl.lua b/pkgs/c/compat.curl.lua new file mode 100644 index 00000000..1b818cfa --- /dev/null +++ b/pkgs/c/compat.curl.lua @@ -0,0 +1,330 @@ +-- compat.curl — libcurl 8.21.0, built from source as a static library. +-- +-- Full-source direct build (the `compat.ffmpeg` shape), not an install() hook +-- around autotools/CMake. What makes that practical here is curl's own +-- convention that every unused protocol and TLS backend compiles to an EMPTY +-- translation unit: `vtls/gtls.c` is `#ifdef USE_GNUTLS` from top to bottom, +-- `vquic/*.c` needs USE_HTTP3, `vssh/*.c` needs libssh. So the source list is +-- plain globs over lib/ and its subdirectories, and the configuration is +-- carried entirely by defines. Only `lib/dllmain.c` is excluded — it is the +-- DLL entry point, meaningless in a static build. +-- +-- THE CONFIG HEADER is the part upstream normally generates. curl_setup.h +-- picks it up as `#include "curl_config.h"` under HAVE_CONFIG_H, and ships +-- ready-made variants for some platforms but not for unix: +-- +-- * windows — `lib/config-win32.h` is checked in and curl_setup.h selects it +-- automatically when HAVE_CONFIG_H is absent. So Windows gets NO generated +-- config; it just must not define HAVE_CONFIG_H. +-- * linux / macosx — nothing checked in. The `mcpp_generated/curl_config.h` +-- below was produced by running curl's CMake configure against this +-- index's own gcc toolchain, then reduced to the entries that matter and +-- branched on `__linux__` / `__APPLE__`. Anything omitted merely costs curl +-- a fallback path, never correctness — which is why the Apple branch is +-- deliberately the conservative subset. +-- +-- Generating that config against the RIGHT compiler turned out to matter: a +-- first pass using the host `cc` produced a config claiming `ssize_t` did not +-- exist (its probe failed for an unrelated sysroot reason), and curl then +-- failed to compile against its own config. +-- +-- TLS: OpenSSL on linux/macosx through this index's `compat.openssl`; Schannel +-- on Windows, which is built into the OS — `compat.openssl` has no Windows +-- build, and Schannel is what upstream uses there anyway. +-- +-- All `mcpp` paths are GLOBS relative to the verdir; the leading `*/` absorbs +-- the GitHub tarball's `curl-curl-8_21_0/` wrap layer. +package = { + spec = "1", + namespace = "compat", + name = "curl", + description = "libcurl — client-side URL transfer library (static, OpenSSL/Schannel TLS)", + licenses = {"curl"}, + repo = "https://github.com/curl/curl", + type = "package", + + xpm = { + linux = { + ["8.21.0"] = { + url = { + GLOBAL = "https://github.com/curl/curl/archive/refs/tags/curl-8_21_0.tar.gz", + CN = "https://gitcode.com/mcpp-res/curl/releases/download/8.21.0/curl-8.21.0.tar.gz", + }, + sha256 = "ec753aa6f408a3ca9f0d6d5f7a77417aecd1544db13c03ae5d443612bf367364", + }, + }, + macosx = { + ["8.21.0"] = { + url = { + GLOBAL = "https://github.com/curl/curl/archive/refs/tags/curl-8_21_0.tar.gz", + CN = "https://gitcode.com/mcpp-res/curl/releases/download/8.21.0/curl-8.21.0.tar.gz", + }, + sha256 = "ec753aa6f408a3ca9f0d6d5f7a77417aecd1544db13c03ae5d443612bf367364", + }, + }, + windows = { + ["8.21.0"] = { + url = { + GLOBAL = "https://github.com/curl/curl/archive/refs/tags/curl-8_21_0.tar.gz", + CN = "https://gitcode.com/mcpp-res/curl/releases/download/8.21.0/curl-8.21.0.tar.gz", + }, + sha256 = "ec753aa6f408a3ca9f0d6d5f7a77417aecd1544db13c03ae5d443612bf367364", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c11", + + include_dirs = { "*/include", "*/lib", "mcpp_generated" }, + + sources = { + "*/lib/*.c", + "!*/lib/dllmain.c", -- DLL entry point; nothing to do in a static lib + "*/lib/curlx/*.c", + "*/lib/vauth/*.c", + "*/lib/vquic/*.c", -- empty TUs without USE_HTTP3 + "*/lib/vssh/*.c", -- empty TUs without libssh + "*/lib/vtls/*.c", -- one backend compiles, the rest are empty TUs + }, + + targets = { ["curl"] = { kind = "lib" } }, + deps = {}, + + -- decorates its declarations with __declspec(dllimport) + -- unless CURL_STATICLIB is defined, so a consumer that does not define + -- it links against import stubs that do not exist. It therefore has to + -- be an ALWAYS-ON, CONSUMER-VISIBLE define — and mcpp has no single key + -- for that: `cflags` is always on but package-private, while a feature's + -- `defines` reach the consumer but need naming. + -- + -- `default = { implies = ... }` is the combination that gives both. A + -- default feature's own `defines` are inert, but what it IMPLIES is + -- applied unconditionally — including when the consumer names some + -- other feature. Verified on 0.0.109 and on 2026.7.29.1; see + -- .agents/docs/2026-07-29-add-gui-backend-packages-plan.md. That quirk + -- makes `default` useless for expressing a mutually exclusive choice — + -- but here "unconditionally on" is exactly what is wanted. + features = { + ["default"] = { implies = { "staticlib" } }, + ["staticlib"] = { defines = { "CURL_STATICLIB" } }, + }, + + cflags = { + "-DBUILDING_LIBCURL", + "-DCURL_STATICLIB", + -- LDAP needs a system client library on every platform and no + -- consumer in this index speaks it. + "-DCURL_DISABLE_LDAP", + "-DCURL_DISABLE_LDAPS", + }, + + generated_files = { + ["mcpp_generated/curl_config.h"] = [==[ +/* curl_config.h — generated for mcpp-index from curl's CMake configure output. + * + * Windows never reaches this file: curl_setup.h selects the checked-in + * lib/config-win32.h when HAVE_CONFIG_H is undefined, and the Windows profile + * in the descriptor deliberately leaves it undefined. + * + * The common block is plain POSIX and holds on both linux and macOS. The + * per-platform blocks below carry only what genuinely differs. */ +#pragma once + +#define STDC_HEADERS 1 +#define _FILE_OFFSET_BITS 64 +#define CURL_EXTERN_SYMBOL __attribute__((__visibility__("default"))) + +/* sizes — both supported platforms are LP64 */ +#define SIZEOF_INT 4 +#define SIZEOF_LONG 8 +#define SIZEOF_OFF_T 8 +#define SIZEOF_SIZE_T 8 +#define SIZEOF_TIME_T 8 +#define SIZEOF_CURL_OFF_T 8 +#define SIZEOF_CURL_SOCKET_T 4 + +/* headers */ +#define HAVE_ARPA_INET_H 1 +#define HAVE_DIRENT_H 1 +#define HAVE_FCNTL_H 1 +#define HAVE_IFADDRS_H 1 +#define HAVE_LIBGEN_H 1 +#define HAVE_LOCALE_H 1 +#define HAVE_NETDB_H 1 +#define HAVE_NETINET_IN_H 1 +#define HAVE_NETINET_TCP_H 1 +#define HAVE_NETINET_UDP_H 1 +#define HAVE_NET_IF_H 1 +#define HAVE_POLL_H 1 +#define HAVE_PWD_H 1 +#define HAVE_STDATOMIC_H 1 +#define HAVE_STDBOOL_H 1 +#define HAVE_STRINGS_H 1 +#define HAVE_SYS_IOCTL_H 1 +#define HAVE_SYS_PARAM_H 1 +#define HAVE_SYS_POLL_H 1 +#define HAVE_SYS_RESOURCE_H 1 +#define HAVE_SYS_SELECT_H 1 +#define HAVE_SYS_TYPES_H 1 +#define HAVE_SYS_UN_H 1 +#define HAVE_TERMIOS_H 1 +#define HAVE_UNISTD_H 1 +#define HAVE_UTIME_H 1 + +/* types */ +#define HAVE_ATOMIC 1 +#define HAVE_BOOL_T 1 +#define HAVE_SA_FAMILY_T 1 +#define HAVE_SUSECONDS_T 1 +#define HAVE_STRUCT_SOCKADDR_STORAGE 1 +#define HAVE_STRUCT_TIMEVAL 1 +#define HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID 1 + +/* functions */ +#define HAVE_ALARM 1 +#define HAVE_BASENAME 1 +#define HAVE_CLOCK_GETTIME_MONOTONIC 1 +#define HAVE_DECL_FSEEKO 1 +#define HAVE_FCNTL 1 +#define HAVE_FCNTL_O_NONBLOCK 1 +#define HAVE_FNMATCH 1 +#define HAVE_FREEADDRINFO 1 +#define HAVE_FSEEKO 1 +#define HAVE_GETADDRINFO 1 +#define HAVE_GETADDRINFO_THREADSAFE 1 +#define HAVE_GETEUID 1 +#define HAVE_GETHOSTNAME 1 +#define HAVE_GETIFADDRS 1 +#define HAVE_GETPEERNAME 1 +#define HAVE_GETPPID 1 +#define HAVE_GETPWUID 1 +#define HAVE_GETPWUID_R 1 +#define HAVE_GETRLIMIT 1 +#define HAVE_GETSOCKNAME 1 +#define HAVE_GETTIMEOFDAY 1 +#define HAVE_GMTIME_R 1 +#define HAVE_IF_NAMETOINDEX 1 +#define HAVE_INET_NTOP 1 +#define HAVE_INET_PTON 1 +#define HAVE_IOCTL_FIONBIO 1 +#define HAVE_IOCTL_SIOCGIFADDR 1 +#define HAVE_LOCALTIME_R 1 +#define HAVE_OPENDIR 1 +#define HAVE_PIPE 1 +#define HAVE_POLL 1 +#define HAVE_REALPATH 1 +#define HAVE_RECV 1 +#define HAVE_SCHED_YIELD 1 +#define HAVE_SELECT 1 +#define HAVE_SEND 1 +#define HAVE_SENDMSG 1 +#define HAVE_SETLOCALE 1 +#define HAVE_SETRLIMIT 1 +#define HAVE_SIGACTION 1 +#define HAVE_SIGINTERRUPT 1 +#define HAVE_SIGNAL 1 +#define HAVE_SIGSETJMP 1 +#define HAVE_SOCKET 1 +#define HAVE_SOCKETPAIR 1 +#define HAVE_STRCASECMP 1 +#define HAVE_STRERROR_R 1 +#define HAVE_THREADS_POSIX 1 +#define HAVE_UTIME 1 +#define HAVE_UTIMES 1 +#define HAVE_WRITABLE_ARGV 1 + +/* TLS + resolver */ +#define USE_OPENSSL 1 +#define USE_TLS_SRP 1 +#define HAVE_OPENSSL_SRP 1 +#define HAVE_SSL_SET0_WBIO 1 +#define HAVE_DES_ECB_ENCRYPT 1 +#define USE_IPV6 1 +#define USE_RESOLV_THREADED 1 + +#if defined(__linux__) + +#define CURL_OS "Linux" +/* glibc's strerror_r returns char*, not int — curl needs to know which */ +#define HAVE_GLIBC_STRERROR_R 1 +#define HAVE_GETHOSTBYNAME_R 1 +#define HAVE_GETHOSTBYNAME_R_6 1 +#define HAVE_ACCEPT4 1 +#define HAVE_PIPE2 1 +#define HAVE_EVENTFD 1 +#define HAVE_SYS_EVENTFD_H 1 +#define HAVE_SENDMMSG 1 +#define HAVE_MEMRCHR 1 +#define HAVE_FSETXATTR 1 +#define HAVE_FSETXATTR_5 1 +#define HAVE_LINUX_TCP_H 1 +#define HAVE_TERMIO_H 1 +#define HAVE_CLOCK_GETTIME_MONOTONIC_RAW 1 +/* Debian/Fedora layout; overridable at runtime with CURLOPT_CAINFO or the + * SSL_CERT_FILE environment variable. */ +#define CURL_CA_BUNDLE "/etc/ssl/certs/ca-certificates.crt" +#define CURL_CA_PATH "/etc/ssl/certs" + +#elif defined(__APPLE__) + +#define CURL_OS "Darwin" +/* Conservative on purpose — every Linux-only entry above is simply omitted + * rather than guessed at, which costs curl a fallback path at worst. + * fsetxattr takes six arguments here, not five. */ +#define HAVE_FSETXATTR 1 +#define HAVE_FSETXATTR_6 1 +#define HAVE_MACH_ABSOLUTE_TIME 1 +/* curl requires knowing WHICH strerror_r it has and refuses to build otherwise + * ("strerror_r MUST be either POSIX, glibc style"). Apple ships the POSIX one + * that returns int; glibc's returns char*, which is the linux branch above. */ +#define HAVE_POSIX_STRERROR_R 1 +/* macOS ships no /etc/ssl/certs directory; LibreSSL's bundle is the one file + * that is reliably present. An OpenSSL-backed curl needs SOME bundle. */ +#define CURL_CA_BUNDLE "/etc/ssl/cert.pem" + +#else +#error "compat.curl: no curl_config.h branch for this platform" +#endif +]==], + }, + + -- ── Platform-specific ────────────────────────────────────────────── + + linux = { + cflags = { "-DHAVE_CONFIG_H", "-D_GNU_SOURCE" }, + deps = { ["compat.openssl"] = "3.5.1" }, + ldflags = { "-lpthread" }, + }, + + macosx = { + cflags = { "-DHAVE_CONFIG_H", "-D_GNU_SOURCE" }, + deps = { ["compat.openssl"] = "3.5.1" }, + -- curl reaches into the system for proxy configuration on Apple: + -- SystemConfiguration for SCDynamicStoreCopyProxies, and + -- CoreFoundation for the CF objects that hands back. + ldflags = { + "-lpthread", + "-framework", "CoreFoundation", + "-framework", "SystemConfiguration", + }, + }, + + windows = { + -- No HAVE_CONFIG_H on purpose: that is what makes curl_setup.h + -- reach for the checked-in lib/config-win32.h. + -- Schannel is the OS TLS stack; USE_WINDOWS_SSPI is what turns on + -- the SSPI-based auth code paths it needs. + cflags = { "-DUSE_SCHANNEL", "-DUSE_WINDOWS_SSPI" }, + ldflags = { + "-lws2_32", "-lcrypt32", "-lbcrypt", + "-ladvapi32", "-lnormaliz", + -- secur32 carries InitSecurityInterfaceA, which curl_sspi.c + -- needs for the SSPI auth Schannel builds on. + "-lsecur32", + }, + }, + }, +} diff --git a/pkgs/c/compat.glx-headers.lua b/pkgs/c/compat.glx-headers.lua new file mode 100644 index 00000000..b8923cb8 --- /dev/null +++ b/pkgs/c/compat.glx-headers.lua @@ -0,0 +1,80 @@ +-- compat.glx-headers — GL and GLX API headers, from libglvnd. +-- +-- Fills a real hole: `GL/glx.h` is NOT part of the Khronos OpenGL-Registry, so +-- `compat.opengl` cannot supply it (that package's `*/api` carries glcorearb.h, +-- glext.h, glxext.h, wgl.h — no glx.h). On Linux the canonical provider is +-- libglvnd, which is also the package the GL runtime plan already names as the +-- wanted dispatch provider (.agents/docs/2026-06-03-gl-runtime-packages-plan.md). +-- Only its headers are taken here; the dispatch libraries remain out of scope, +-- and `compat.glx-runtime` continues to own the runtime side. +-- +-- `compat.sdl2` needs this: SDL's X11 video driver includes from +-- SDL_x11opengl.h whenever SDL_VIDEO_OPENGL_GLX is on, and without it the whole +-- X11 backend fails to compile. (compat.glfw does not, because GLFW vendors its +-- own minimal GLX declarations in src/glx_context.h.) +-- +-- Header-only, the `compat.opengl` shape: one include root plus an anchor TU so +-- the package still produces a buildable lib target. +-- +-- NOTE ON OVERLAP: this include root also carries `GL/gl.h`, `GL/glcorearb.h` +-- and `GL/glext.h`, which `compat.opengl` provides too. Depend on ONE of the +-- two, not both, or the winner depends on include-dir order. Consumers wanting +-- GLX should take this package; consumers wanting only core GL should keep +-- taking `compat.opengl`. +package = { + spec = "1", + namespace = "compat", + name = "glx-headers", + description = "GL and GLX API headers (libglvnd) — provides GL/glx.h", + licenses = {"MIT"}, + repo = "https://github.com/NVIDIA/libglvnd", + type = "package", + + xpm = { + linux = { + ["1.7.0"] = { + url = { + GLOBAL = "https://github.com/NVIDIA/libglvnd/archive/refs/tags/v1.7.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/glx-headers/releases/download/1.7.0/glx-headers-1.7.0.tar.gz", + }, + sha256 = "073e7292788d4d3eeb45ea6c7bdcce9bfdb3b3eef8d7dbd47f2f30dce046ef98", + }, + }, + macosx = { + ["1.7.0"] = { + url = { + GLOBAL = "https://github.com/NVIDIA/libglvnd/archive/refs/tags/v1.7.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/glx-headers/releases/download/1.7.0/glx-headers-1.7.0.tar.gz", + }, + sha256 = "073e7292788d4d3eeb45ea6c7bdcce9bfdb3b3eef8d7dbd47f2f30dce046ef98", + }, + }, + windows = { + ["1.7.0"] = { + url = { + GLOBAL = "https://github.com/NVIDIA/libglvnd/archive/refs/tags/v1.7.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/glx-headers/releases/download/1.7.0/glx-headers-1.7.0.tar.gz", + }, + sha256 = "073e7292788d4d3eeb45ea6c7bdcce9bfdb3b3eef8d7dbd47f2f30dce046ef98", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c11", + include_dirs = { "*/include" }, + generated_files = { + ["mcpp_generated/glx_headers_anchor.c"] = + "int mcpp_compat_glx_headers_anchor(void) { return 0; }\n", + }, + sources = { "mcpp_generated/glx_headers_anchor.c" }, + targets = { ["glx-headers"] = { kind = "lib" } }, + -- GL/glx.h includes and . + deps = { + ["compat.x11"] = "1.8.13", + ["compat.xorgproto"] = "2025.1", + }, + }, +} diff --git a/pkgs/c/compat.sdl2.lua b/pkgs/c/compat.sdl2.lua new file mode 100644 index 00000000..3b768064 --- /dev/null +++ b/pkgs/c/compat.sdl2.lua @@ -0,0 +1,923 @@ +-- compat.sdl2 — SDL 2.32.10, built from source as a static library. +-- +-- Cross-platform window, input and audio layer. Full-source direct build, the +-- `compat.ffmpeg` / +-- `compat.curl` shape: SDL guards every backend it did not select +-- (`src/video/windows/*.c` opens with `#if SDL_VIDEO_DRIVER_WINDOWS`, the +-- direct3d/psp/ps2 renderers likewise), so unselected files compile to EMPTY +-- translation units and the source list can be plain directory globs with the +-- configuration carried entirely by SDL_config.h. +-- +-- THE CONFIG HEADER is the only genuinely awkward part, and it is awkward on +-- exactly one platform: +-- +-- * windows / macosx — upstream CHECKS IN `include/SDL_config_windows.h` and +-- `include/SDL_config_macosx.h`, and `include/SDL_config.h` is a dispatcher +-- to them. Nothing to generate. +-- * linux — upstream's dispatcher falls through to `SDL_config_minimal.h`, +-- which builds an SDL that can do essentially nothing. This is the gap the +-- generated header below fills. +-- +-- So `mcpp_generated/SDL_config.h` shadows upstream's dispatcher and +-- reproduces it for windows/macosx, while carrying CMake's generated Linux +-- configuration inline. X11 is switched on by hand at the end of that block: +-- the CMake probe ran on a host without system X11 development headers and +-- disabled it, but this index supplies X11 through the same packages +-- `compat.glfw` links. +-- +-- SOURCE IS A REPACK, not the upstream tag archive. SDL's GitHub archive carries +-- two POSIX symlinks under `android-project-ant/`, and a tag archive containing +-- symlinks fails to extract on Windows — the package then "installs" with +-- nothing in it, publishes no include dirs, and every consumer fails with +-- `'SDL.h' file not found` while the package itself reports success. That is +-- exactly what CI showed before this change. `chriskohlhoff.asio` hit the same +-- wall and set the precedent: host a symlink-free repack on xlings-res +-- (GLOBAL) and mirror the identical bytes to mcpp-res (CN). Only the two +-- symlink entries are removed; everything else is the upstream content. +-- +-- All `mcpp` paths are GLOBS relative to the verdir; the leading `*/` absorbs +-- the tarball's `SDL-release-2.32.10/` wrap layer. +package = { + spec = "1", + namespace = "compat", + name = "sdl2", + description = "SDL2 — cross-platform window, input and audio layer", + licenses = {"Zlib"}, + repo = "https://github.com/libsdl-org/SDL", + type = "package", + + xpm = { + linux = { + ["2.32.10"] = { + url = { + GLOBAL = "https://github.com/xlings-res/sdl2/releases/download/2.32.10/sdl2-2.32.10-nosymlinks.tar.gz", + CN = "https://gitcode.com/mcpp-res/sdl2/releases/download/2.32.10-nosymlinks/sdl2-2.32.10-nosymlinks.tar.gz", + }, + sha256 = "8823b81a7ecec5c1785dfd3c1fdb6260899d4eaa07574365dd4b8da9e2830385", + }, + }, + macosx = { + ["2.32.10"] = { + url = { + GLOBAL = "https://github.com/xlings-res/sdl2/releases/download/2.32.10/sdl2-2.32.10-nosymlinks.tar.gz", + CN = "https://gitcode.com/mcpp-res/sdl2/releases/download/2.32.10-nosymlinks/sdl2-2.32.10-nosymlinks.tar.gz", + }, + sha256 = "8823b81a7ecec5c1785dfd3c1fdb6260899d4eaa07574365dd4b8da9e2830385", + }, + }, + windows = { + ["2.32.10"] = { + url = { + GLOBAL = "https://github.com/xlings-res/sdl2/releases/download/2.32.10/sdl2-2.32.10-nosymlinks.tar.gz", + CN = "https://gitcode.com/mcpp-res/sdl2/releases/download/2.32.10-nosymlinks/sdl2-2.32.10-nosymlinks.tar.gz", + }, + sha256 = "8823b81a7ecec5c1785dfd3c1fdb6260899d4eaa07574365dd4b8da9e2830385", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c99", + + -- mcpp_generated FIRST so our SDL_config.h shadows upstream's + -- dispatcher; */include carries the public SDL headers and the + -- per-platform configs our header defers to. + -- */src/video/khronos carries SDL's vendored EGL and GLES headers, + -- which SDL_VIDEO_OPENGL_EGL / _ES2 reach for. + include_dirs = { "mcpp_generated", "*/include", "*/src", "*/src/video/khronos" }, + + -- Backend-independent core plus every backend directory. SDL's own + -- guards decide what survives the preprocessor, which is what lets one + -- list serve all three platforms. + sources = { + "*/src/*.c", + "*/src/atomic/*.c", + "*/src/audio/*.c", + "*/src/audio/disk/*.c", + "*/src/audio/dummy/*.c", + "*/src/cpuinfo/*.c", + "*/src/dynapi/*.c", + "*/src/events/*.c", + "*/src/file/*.c", + "*/src/haptic/*.c", + -- Only the top level: SDL_hidapi.c is SDL's own wrapper and is + -- always needed (SDL_hid_* is public API). The subdirectories under + -- it are the vendored hidapi backends, which SDL_HIDAPI gates and + -- which this build does not use. + "*/src/hidapi/*.c", + "*/src/joystick/*.c", + -- SDL_JOYSTICK_HIDAPI is off in the generated Linux config but ON in + -- upstream's checked-in macosx/windows ones, and SDL_gamecontroller.c + -- calls into it unconditionally there + -- (HIDAPI_GetGameControllerTypeFromGUID). Guarded internally, so it + -- costs nothing where the config disables it. + "*/src/joystick/hidapi/*.c", + "*/src/joystick/steam/*.c", + "*/src/joystick/virtual/*.c", + "*/src/libm/*.c", + "*/src/locale/*.c", + "*/src/misc/*.c", + "*/src/power/*.c", + "*/src/render/*.c", + "*/src/render/direct3d/*.c", + "*/src/render/direct3d11/*.c", + "*/src/render/direct3d12/*.c", + "*/src/render/opengl/*.c", + "*/src/render/opengles/*.c", + "*/src/render/opengles2/*.c", + "*/src/render/ps2/*.c", + "*/src/render/psp/*.c", + "*/src/render/software/*.c", + "*/src/render/vitagxm/*.c", + "*/src/sensor/*.c", + "*/src/sensor/dummy/*.c", + "*/src/stdlib/*.c", + "*/src/thread/*.c", + "*/src/timer/*.c", + "*/src/video/*.c", + "*/src/video/dummy/*.c", + "*/src/video/offscreen/*.c", + "*/src/video/yuv2rgb/*.c", + }, + + targets = { ["sdl2"] = { kind = "lib" } }, + deps = {}, + + generated_files = { + ["mcpp_generated/SDL_config.h"] = [==[ +/* SDL_config.h — supplied by mcpp-index. + * + * Only LINUX needs this. Upstream ships ready-made configs for the other two + * platforms (include/SDL_config_windows.h, include/SDL_config_macosx.h) and its + * own include/SDL_config.h is just a dispatcher to them; the branches below + * reproduce that dispatch. Linux is the gap — upstream's dispatcher falls + * through to SDL_config_minimal.h there, which builds an SDL that can do + * essentially nothing. + * + * The Linux body is CMake's generated output against this index's gcc + * toolchain, verbatim, plus the X11 block at the end. */ +#ifndef SDL_config_h_ +#define SDL_config_h_ + +#include "SDL_platform.h" + +#if defined(__WIN32__) +/* SDL_config_windows.h only turns HAVE_LIBC on for _MSC_VER, and this index + * builds with clang++ rather than clang-cl. Left off, SDL compiles its own + * memcpy/memset in src/stdlib/SDL_mslibc.c and the link fails with + * "memcpy already defined in SDL_mslibc.o" against libvcruntime. */ +#define HAVE_LIBC 1 +#include "SDL_config_windows.h" +#elif defined(__MACOSX__) +#include "SDL_config_macosx.h" +/* Upstream's macOS config also enables the X11 video driver, dynamically loaded + * from /opt/X11. That would make src/video/x11 a compile dependency on Apple — + * X11 headers and all — for a driver nobody reaches on a Mac with Cocoa + * present. Turned off here; Cocoa and the dummy driver remain. */ +#undef SDL_VIDEO_DRIVER_X11 +#undef SDL_VIDEO_DRIVER_X11_DYNAMIC +#undef SDL_VIDEO_DRIVER_X11_DYNAMIC_XEXT +#undef SDL_VIDEO_DRIVER_X11_DYNAMIC_XINPUT2 +#undef SDL_VIDEO_DRIVER_X11_DYNAMIC_XRANDR +#undef SDL_VIDEO_DRIVER_X11_DYNAMIC_XSS +#undef SDL_VIDEO_DRIVER_X11_XDBE +#undef SDL_VIDEO_DRIVER_X11_XRANDR +#undef SDL_VIDEO_DRIVER_X11_XSCRNSAVER +#undef SDL_VIDEO_DRIVER_X11_XSHAPE +#undef SDL_VIDEO_DRIVER_X11_HAS_XKBKEYCODETOKEYSYM +#undef SDL_VIDEO_DRIVER_X11_XINPUT2 +#undef SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS +#elif defined(__linux__) + + +/** + * \file SDL_config.h.in + * + * This is a set of defines to configure the SDL features + */ + +/* General platform specific identifiers */ +#include "SDL_platform.h" + +/* C language features */ +/* #undef const */ +/* #undef inline */ +/* #undef volatile */ + +/* C datatypes */ +/* Define SIZEOF_VOIDP for 64/32 architectures */ +#if defined(__LP64__) || defined(_LP64) || defined(_WIN64) +#define SIZEOF_VOIDP 8 +#else +#define SIZEOF_VOIDP 4 +#endif + +#define HAVE_GCC_ATOMICS 1 +/* #undef HAVE_GCC_SYNC_LOCK_TEST_AND_SET */ + +/* Comment this if you want to build without any C library requirements */ +#define HAVE_LIBC 1 +#ifdef HAVE_LIBC + +/* Useful headers */ +#define STDC_HEADERS 1 +#define HAVE_ALLOCA_H 1 +#define HAVE_CTYPE_H 1 +#define HAVE_FLOAT_H 1 +#define HAVE_ICONV_H 1 +#define HAVE_INTTYPES_H 1 +#define HAVE_LIMITS_H 1 +#define HAVE_MALLOC_H 1 +#define HAVE_MATH_H 1 +#define HAVE_MEMORY_H 1 +#define HAVE_SIGNAL_H 1 +#define HAVE_STDARG_H 1 +#define HAVE_STDDEF_H 1 +#define HAVE_STDINT_H 1 +#define HAVE_STDIO_H 1 +#define HAVE_STDLIB_H 1 +#define HAVE_STRINGS_H 1 +#define HAVE_STRING_H 1 +#define HAVE_SYS_TYPES_H 1 +#define HAVE_WCHAR_H 1 +#define HAVE_LINUX_INPUT_H 1 +/* #undef HAVE_PTHREAD_NP_H */ +/* #undef HAVE_LIBUNWIND_H */ + +/* C library functions */ +#define HAVE_DLOPEN 1 +#define HAVE_MALLOC 1 +#define HAVE_CALLOC 1 +#define HAVE_REALLOC 1 +#define HAVE_FREE 1 +#define HAVE_ALLOCA 1 +#ifndef __WIN32__ /* Don't use C runtime versions of these on Windows */ +#define HAVE_GETENV 1 +#define HAVE_SETENV 1 +#define HAVE_PUTENV 1 +#define HAVE_UNSETENV 1 +#endif +#define HAVE_QSORT 1 +#define HAVE_BSEARCH 1 +#define HAVE_ABS 1 +#define HAVE_BCOPY 1 +#define HAVE_MEMSET 1 +#define HAVE_MEMCPY 1 +#define HAVE_MEMMOVE 1 +#define HAVE_MEMCMP 1 +#define HAVE_WCSLEN 1 +#define HAVE_WCSLCPY 1 +#define HAVE_WCSLCAT 1 +/* #undef HAVE__WCSDUP */ +#define HAVE_WCSDUP 1 +#define HAVE_WCSSTR 1 +#define HAVE_WCSCMP 1 +#define HAVE_WCSNCMP 1 +#define HAVE_WCSCASECMP 1 +/* #undef HAVE__WCSICMP */ +#define HAVE_WCSNCASECMP 1 +/* #undef HAVE__WCSNICMP */ +#define HAVE_STRLEN 1 +#define HAVE_STRLCPY 1 +#define HAVE_STRLCAT 1 +/* #undef HAVE__STRREV */ +/* #undef HAVE__STRUPR */ +/* #undef HAVE__STRLWR */ +#define HAVE_INDEX 1 +#define HAVE_RINDEX 1 +#define HAVE_STRCHR 1 +#define HAVE_STRRCHR 1 +#define HAVE_STRSTR 1 +#define HAVE_STRTOK_R 1 +/* #undef HAVE_ITOA */ +/* #undef HAVE__LTOA */ +/* #undef HAVE__UITOA */ +/* #undef HAVE__ULTOA */ +#define HAVE_STRTOL 1 +#define HAVE_STRTOUL 1 +/* #undef HAVE__I64TOA */ +/* #undef HAVE__UI64TOA */ +#define HAVE_STRTOLL 1 +#define HAVE_STRTOULL 1 +#define HAVE_STRTOD 1 +#define HAVE_ATOI 1 +#define HAVE_ATOF 1 +#define HAVE_STRCMP 1 +#define HAVE_STRNCMP 1 +/* #undef HAVE__STRICMP */ +#define HAVE_STRCASECMP 1 +/* #undef HAVE__STRNICMP */ +#define HAVE_STRNCASECMP 1 +#define HAVE_STRCASESTR 1 +#define HAVE_SSCANF 1 +#define HAVE_VSSCANF 1 +#define HAVE_VSNPRINTF 1 +#define HAVE_M_PI 1 +#define HAVE_ACOS 1 +#define HAVE_ACOSF 1 +#define HAVE_ASIN 1 +#define HAVE_ASINF 1 +#define HAVE_ATAN 1 +#define HAVE_ATANF 1 +#define HAVE_ATAN2 1 +#define HAVE_ATAN2F 1 +#define HAVE_CEIL 1 +#define HAVE_CEILF 1 +#define HAVE_COPYSIGN 1 +#define HAVE_COPYSIGNF 1 +#define HAVE_COS 1 +#define HAVE_COSF 1 +#define HAVE_EXP 1 +#define HAVE_EXPF 1 +#define HAVE_FABS 1 +#define HAVE_FABSF 1 +#define HAVE_FLOOR 1 +#define HAVE_FLOORF 1 +#define HAVE_FMOD 1 +#define HAVE_FMODF 1 +#define HAVE_LOG 1 +#define HAVE_LOGF 1 +#define HAVE_LOG10 1 +#define HAVE_LOG10F 1 +#define HAVE_LROUND 1 +#define HAVE_LROUNDF 1 +#define HAVE_POW 1 +#define HAVE_POWF 1 +#define HAVE_ROUND 1 +#define HAVE_ROUNDF 1 +#define HAVE_SCALBN 1 +#define HAVE_SCALBNF 1 +#define HAVE_SIN 1 +#define HAVE_SINF 1 +#define HAVE_SQRT 1 +#define HAVE_SQRTF 1 +#define HAVE_TAN 1 +#define HAVE_TANF 1 +#define HAVE_TRUNC 1 +#define HAVE_TRUNCF 1 +#define HAVE_FOPEN64 1 +#define HAVE_FSEEKO 1 +#define HAVE_FSEEKO64 1 +#define HAVE_MEMFD_CREATE 1 +#define HAVE_POSIX_FALLOCATE 1 +#define HAVE_SIGACTION 1 +#define HAVE_SIGTIMEDWAIT 1 +#define HAVE_SA_SIGACTION 1 +#define HAVE_SETJMP 1 +#define HAVE_NANOSLEEP 1 +#define HAVE_SYSCONF 1 +/* #undef HAVE_SYSCTLBYNAME */ +#define HAVE_CLOCK_GETTIME 1 +/* #undef HAVE_GETPAGESIZE */ +#define HAVE_MPROTECT 1 +#define HAVE_ICONV 1 +/* #undef SDL_USE_LIBICONV */ +#define HAVE_PTHREAD_SETNAME_NP 1 +/* #undef HAVE_PTHREAD_SET_NAME_NP */ +#define HAVE_SEM_TIMEDWAIT 1 +#define HAVE_GETAUXVAL 1 +/* #undef HAVE_ELF_AUX_INFO */ +#define HAVE_POLL 1 +#define HAVE__EXIT 1 + +#else +#define HAVE_STDARG_H 1 +#define HAVE_STDDEF_H 1 +#define HAVE_STDINT_H 1 +#define HAVE_FLOAT_H 1 +#endif /* HAVE_LIBC */ + +/* #undef HAVE_ALTIVEC_H */ +/* #undef HAVE_DBUS_DBUS_H */ +/* #undef HAVE_FCITX */ +/* #undef HAVE_IBUS_IBUS_H */ +#define HAVE_SYS_INOTIFY_H 1 +#define HAVE_INOTIFY_INIT 1 +#define HAVE_INOTIFY_INIT1 1 +#define HAVE_INOTIFY 1 +/* #undef HAVE_LIBUSB */ +#define HAVE_O_CLOEXEC 1 + +/* Apple platforms might be building universal binaries, where Intel builds + can use immintrin.h but other architectures can't. */ +#ifdef __APPLE__ +# if defined(__has_include) && (defined(__i386__) || defined(__x86_64)) +# if __has_include() +# define HAVE_IMMINTRIN_H 1 +# endif +# endif +#else /* non-Apple platforms can use the normal CMake check for this. */ +#define HAVE_IMMINTRIN_H 1 +#endif + +/* libudev is deliberately OFF: it is a host system library this index does not + * package, and SDL only uses it to enumerate input devices. Without it SDL + * falls back to scanning /dev/input directly, which is the same path it takes + * on any udev-less system. Leaving the CMake probe's value in would make + * src/joystick/linux and src/haptic/linux fail on . */ +/* #undef HAVE_LIBUDEV_H */ +/* #undef HAVE_LIBSAMPLERATE_H */ +/* #undef HAVE_LIBDECOR_H */ + +/* #undef HAVE_D3D_H */ +/* #undef HAVE_D3D11_H */ +/* #undef HAVE_D3D12_H */ +/* #undef HAVE_DDRAW_H */ +/* #undef HAVE_DSOUND_H */ +/* #undef HAVE_DINPUT_H */ +/* #undef HAVE_XINPUT_H */ +/* #undef HAVE_WINDOWS_GAMING_INPUT_H */ +/* #undef HAVE_DXGI_H */ + +/* #undef HAVE_MMDEVICEAPI_H */ +/* #undef HAVE_AUDIOCLIENT_H */ +/* #undef HAVE_TPCSHRD_H */ +/* #undef HAVE_SENSORSAPI_H */ +/* #undef HAVE_ROAPI_H */ +/* #undef HAVE_SHELLSCALINGAPI_H */ + +/* #undef USE_POSIX_SPAWN */ + +/* SDL internal assertion support */ +#if 0 +/* #undef SDL_DEFAULT_ASSERT_LEVEL */ +#endif + +/* Allow disabling of core subsystems */ +/* #undef SDL_ATOMIC_DISABLED */ +/* #undef SDL_AUDIO_DISABLED */ +/* #undef SDL_CPUINFO_DISABLED */ +/* #undef SDL_EVENTS_DISABLED */ +/* #undef SDL_FILE_DISABLED */ +/* #undef SDL_JOYSTICK_DISABLED */ +/* #undef SDL_HAPTIC_DISABLED */ +#define SDL_HIDAPI_DISABLED 1 +/* #undef SDL_SENSOR_DISABLED */ +/* #undef SDL_LOADSO_DISABLED */ +/* #undef SDL_RENDER_DISABLED */ +/* #undef SDL_THREADS_DISABLED */ +/* #undef SDL_TIMERS_DISABLED */ +/* #undef SDL_VIDEO_DISABLED */ +/* #undef SDL_POWER_DISABLED */ +/* #undef SDL_FILESYSTEM_DISABLED */ +/* #undef SDL_LOCALE_DISABLED */ +/* #undef SDL_MISC_DISABLED */ + +/* Enable various audio drivers */ +/* #undef SDL_AUDIO_DRIVER_ALSA */ +/* #undef SDL_AUDIO_DRIVER_ALSA_DYNAMIC */ +/* #undef SDL_AUDIO_DRIVER_ANDROID */ +/* #undef SDL_AUDIO_DRIVER_OPENSLES */ +/* #undef SDL_AUDIO_DRIVER_AAUDIO */ +/* #undef SDL_AUDIO_DRIVER_ARTS */ +/* #undef SDL_AUDIO_DRIVER_ARTS_DYNAMIC */ +/* #undef SDL_AUDIO_DRIVER_COREAUDIO */ +#define SDL_AUDIO_DRIVER_DISK 1 +/* #undef SDL_AUDIO_DRIVER_DSOUND */ +#define SDL_AUDIO_DRIVER_DUMMY 1 +/* #undef SDL_AUDIO_DRIVER_EMSCRIPTEN */ +/* #undef SDL_AUDIO_DRIVER_ESD */ +/* #undef SDL_AUDIO_DRIVER_ESD_DYNAMIC */ +/* #undef SDL_AUDIO_DRIVER_FUSIONSOUND */ +/* #undef SDL_AUDIO_DRIVER_FUSIONSOUND_DYNAMIC */ +/* #undef SDL_AUDIO_DRIVER_HAIKU */ +/* #undef SDL_AUDIO_DRIVER_JACK */ +/* #undef SDL_AUDIO_DRIVER_JACK_DYNAMIC */ +/* #undef SDL_AUDIO_DRIVER_NAS */ +/* #undef SDL_AUDIO_DRIVER_NAS_DYNAMIC */ +/* #undef SDL_AUDIO_DRIVER_NETBSD */ +/* #undef SDL_AUDIO_DRIVER_OSS */ +/* #undef SDL_AUDIO_DRIVER_PAUDIO */ +/* #undef SDL_AUDIO_DRIVER_PIPEWIRE */ +/* #undef SDL_AUDIO_DRIVER_PIPEWIRE_DYNAMIC */ +/* #undef SDL_AUDIO_DRIVER_PULSEAUDIO */ +/* #undef SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC */ +/* #undef SDL_AUDIO_DRIVER_QSA */ +/* #undef SDL_AUDIO_DRIVER_SNDIO */ +/* #undef SDL_AUDIO_DRIVER_SNDIO_DYNAMIC */ +/* #undef SDL_AUDIO_DRIVER_SUNAUDIO */ +/* #undef SDL_AUDIO_DRIVER_WASAPI */ +/* #undef SDL_AUDIO_DRIVER_WINMM */ +/* #undef SDL_AUDIO_DRIVER_OS2 */ +/* #undef SDL_AUDIO_DRIVER_VITA */ +/* #undef SDL_AUDIO_DRIVER_PSP */ +/* #undef SDL_AUDIO_DRIVER_PS2 */ +/* #undef SDL_AUDIO_DRIVER_N3DS */ + +/* Enable various input drivers */ +#define SDL_INPUT_LINUXEV 1 +#define SDL_INPUT_LINUXKD 1 +/* #undef SDL_INPUT_FBSDKBIO */ +/* #undef SDL_INPUT_WSCONS */ +/* #undef SDL_JOYSTICK_ANDROID */ +/* #undef SDL_JOYSTICK_HAIKU */ +/* #undef SDL_JOYSTICK_WGI */ +/* #undef SDL_JOYSTICK_DINPUT */ +/* #undef SDL_JOYSTICK_XINPUT */ +/* #undef SDL_JOYSTICK_DUMMY */ +/* #undef SDL_JOYSTICK_IOKIT */ +/* #undef SDL_JOYSTICK_MFI */ +#define SDL_JOYSTICK_LINUX 1 +/* #undef SDL_JOYSTICK_OS2 */ +/* #undef SDL_JOYSTICK_USBHID */ +/* #undef SDL_HAVE_MACHINE_JOYSTICK_H */ +/* #undef SDL_JOYSTICK_HIDAPI */ +/* #undef SDL_JOYSTICK_RAWINPUT */ +/* #undef SDL_JOYSTICK_EMSCRIPTEN */ +/* #undef SDL_JOYSTICK_VIRTUAL */ +/* #undef SDL_JOYSTICK_VITA */ +/* #undef SDL_JOYSTICK_PSP */ +/* #undef SDL_JOYSTICK_PS2 */ +/* #undef SDL_JOYSTICK_N3DS */ +/* #undef SDL_HAPTIC_DUMMY */ +#define SDL_HAPTIC_LINUX 1 +/* #undef SDL_HAPTIC_IOKIT */ +/* #undef SDL_HAPTIC_DINPUT */ +/* #undef SDL_HAPTIC_XINPUT */ +/* #undef SDL_HAPTIC_ANDROID */ +/* #undef SDL_LIBUSB_DYNAMIC */ +/* #undef SDL_UDEV_DYNAMIC */ + +/* Enable various sensor drivers */ +/* #undef SDL_SENSOR_ANDROID */ +/* #undef SDL_SENSOR_COREMOTION */ +/* #undef SDL_SENSOR_WINDOWS */ +#define SDL_SENSOR_DUMMY 1 +/* #undef SDL_SENSOR_VITA */ +/* #undef SDL_SENSOR_N3DS */ + +/* Enable various shared object loading systems */ +#define SDL_LOADSO_DLOPEN 1 +/* #undef SDL_LOADSO_DUMMY */ +/* #undef SDL_LOADSO_LDG */ +/* #undef SDL_LOADSO_WINDOWS */ +/* #undef SDL_LOADSO_OS2 */ + +/* Enable various threading systems */ +/* #undef SDL_THREAD_GENERIC_COND_SUFFIX */ +#define SDL_THREAD_PTHREAD 1 +#define SDL_THREAD_PTHREAD_RECURSIVE_MUTEX 1 +/* #undef SDL_THREAD_PTHREAD_RECURSIVE_MUTEX_NP */ +/* #undef SDL_THREAD_WINDOWS */ +/* #undef SDL_THREAD_OS2 */ +/* #undef SDL_THREAD_VITA */ +/* #undef SDL_THREAD_PSP */ +/* #undef SDL_THREAD_PS2 */ +/* #undef SDL_THREAD_N3DS */ + +/* Enable various timer systems */ +/* #undef SDL_TIMER_HAIKU */ +/* #undef SDL_TIMER_DUMMY */ +#define SDL_TIMER_UNIX 1 +/* #undef SDL_TIMER_WINDOWS */ +/* #undef SDL_TIMER_OS2 */ +/* #undef SDL_TIMER_VITA */ +/* #undef SDL_TIMER_PSP */ +/* #undef SDL_TIMER_PS2 */ +/* #undef SDL_TIMER_N3DS */ + +/* Enable various video drivers */ +/* #undef SDL_VIDEO_DRIVER_ANDROID */ +/* #undef SDL_VIDEO_DRIVER_EMSCRIPTEN */ +/* #undef SDL_VIDEO_DRIVER_HAIKU */ +/* #undef SDL_VIDEO_DRIVER_COCOA */ +/* #undef SDL_VIDEO_DRIVER_UIKIT */ +/* #undef SDL_VIDEO_DRIVER_DIRECTFB */ +/* #undef SDL_VIDEO_DRIVER_DIRECTFB_DYNAMIC */ +#define SDL_VIDEO_DRIVER_DUMMY 1 +#define SDL_VIDEO_DRIVER_OFFSCREEN 1 +/* #undef SDL_VIDEO_DRIVER_WINDOWS */ +/* #undef SDL_VIDEO_DRIVER_WINRT */ +/* #undef SDL_VIDEO_DRIVER_WAYLAND */ +/* #undef SDL_VIDEO_DRIVER_RPI */ +/* #undef SDL_VIDEO_DRIVER_VIVANTE */ +/* #undef SDL_VIDEO_DRIVER_VIVANTE_VDK */ +/* #undef SDL_VIDEO_DRIVER_OS2 */ +/* #undef SDL_VIDEO_DRIVER_QNX */ +/* #undef SDL_VIDEO_DRIVER_RISCOS */ +/* #undef SDL_VIDEO_DRIVER_PSP */ +/* #undef SDL_VIDEO_DRIVER_PS2 */ + +/* #undef SDL_VIDEO_DRIVER_KMSDRM */ +/* #undef SDL_VIDEO_DRIVER_KMSDRM_DYNAMIC */ +/* #undef SDL_VIDEO_DRIVER_KMSDRM_DYNAMIC_GBM */ + +/* #undef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH */ +/* #undef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC */ +/* #undef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_EGL */ +/* #undef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_CURSOR */ +/* #undef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_XKBCOMMON */ +/* #undef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_LIBDECOR */ + +/* #undef SDL_VIDEO_DRIVER_X11 */ +/* #undef SDL_VIDEO_DRIVER_X11_DYNAMIC */ +/* #undef SDL_VIDEO_DRIVER_X11_DYNAMIC_XEXT */ +/* #undef SDL_VIDEO_DRIVER_X11_DYNAMIC_XCURSOR */ +/* #undef SDL_VIDEO_DRIVER_X11_DYNAMIC_XINPUT2 */ +/* #undef SDL_VIDEO_DRIVER_X11_DYNAMIC_XFIXES */ +/* #undef SDL_VIDEO_DRIVER_X11_DYNAMIC_XRANDR */ +/* #undef SDL_VIDEO_DRIVER_X11_DYNAMIC_XSS */ +/* #undef SDL_VIDEO_DRIVER_X11_XCURSOR */ +/* #undef SDL_VIDEO_DRIVER_X11_XDBE */ +/* #undef SDL_VIDEO_DRIVER_X11_XINPUT2 */ +/* #undef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH */ +/* #undef SDL_VIDEO_DRIVER_X11_XFIXES */ +/* #undef SDL_VIDEO_DRIVER_X11_XRANDR */ +/* #undef SDL_VIDEO_DRIVER_X11_XSCRNSAVER */ +/* #undef SDL_VIDEO_DRIVER_X11_XSHAPE */ +/* #undef SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS */ +/* #undef SDL_VIDEO_DRIVER_X11_HAS_XKBKEYCODETOKEYSYM */ +/* #undef SDL_VIDEO_DRIVER_VITA */ +/* #undef SDL_VIDEO_DRIVER_N3DS */ + +/* #undef SDL_VIDEO_RENDER_D3D */ +/* #undef SDL_VIDEO_RENDER_D3D11 */ +/* #undef SDL_VIDEO_RENDER_D3D12 */ +#define SDL_VIDEO_RENDER_OGL 1 +#define SDL_VIDEO_RENDER_OGL_ES 1 +#define SDL_VIDEO_RENDER_OGL_ES2 1 +/* #undef SDL_VIDEO_RENDER_DIRECTFB */ +/* #undef SDL_VIDEO_RENDER_METAL */ +/* #undef SDL_VIDEO_RENDER_VITA_GXM */ +/* #undef SDL_VIDEO_RENDER_PS2 */ +/* #undef SDL_VIDEO_RENDER_PSP */ + +/* Enable OpenGL support */ +#define SDL_VIDEO_OPENGL 1 +#define SDL_VIDEO_OPENGL_ES 1 +#define SDL_VIDEO_OPENGL_ES2 1 +/* #undef SDL_VIDEO_OPENGL_BGL */ +/* #undef SDL_VIDEO_OPENGL_CGL */ +#define SDL_VIDEO_OPENGL_GLX 1 +/* #undef SDL_VIDEO_OPENGL_WGL */ +#define SDL_VIDEO_OPENGL_EGL 1 +/* #undef SDL_VIDEO_OPENGL_OSMESA */ +/* #undef SDL_VIDEO_OPENGL_OSMESA_DYNAMIC */ + +/* Enable Vulkan support */ +#define SDL_VIDEO_VULKAN 1 + +/* Enable Metal support */ +/* #undef SDL_VIDEO_METAL */ + +/* Enable system power support */ +/* #undef SDL_POWER_ANDROID */ +#define SDL_POWER_LINUX 1 +/* #undef SDL_POWER_WINDOWS */ +/* #undef SDL_POWER_WINRT */ +/* #undef SDL_POWER_MACOSX */ +/* #undef SDL_POWER_UIKIT */ +/* #undef SDL_POWER_HAIKU */ +/* #undef SDL_POWER_EMSCRIPTEN */ +/* #undef SDL_POWER_HARDWIRED */ +/* #undef SDL_POWER_VITA */ +/* #undef SDL_POWER_PSP */ +/* #undef SDL_POWER_N3DS */ + +/* Enable system filesystem support */ +/* #undef SDL_FILESYSTEM_ANDROID */ +/* #undef SDL_FILESYSTEM_HAIKU */ +/* #undef SDL_FILESYSTEM_COCOA */ +/* #undef SDL_FILESYSTEM_DUMMY */ +/* #undef SDL_FILESYSTEM_RISCOS */ +#define SDL_FILESYSTEM_UNIX 1 +/* #undef SDL_FILESYSTEM_WINDOWS */ +/* #undef SDL_FILESYSTEM_EMSCRIPTEN */ +/* #undef SDL_FILESYSTEM_OS2 */ +/* #undef SDL_FILESYSTEM_VITA */ +/* #undef SDL_FILESYSTEM_PSP */ +/* #undef SDL_FILESYSTEM_PS2 */ +/* #undef SDL_FILESYSTEM_N3DS */ + +/* Enable misc subsystem */ +/* #undef SDL_MISC_DUMMY */ + +/* Enable locale subsystem */ +/* #undef SDL_LOCALE_DUMMY */ + +/* Enable assembly routines */ +/* #undef SDL_ALTIVEC_BLITTERS */ +/* #undef SDL_ARM_SIMD_BLITTERS */ +/* #undef SDL_ARM_NEON_BLITTERS */ + +/* Whether SDL_DYNAMIC_API needs dlopen */ +#define DYNAPI_NEEDS_DLOPEN 1 + +/* Enable dynamic libsamplerate support */ +/* #undef SDL_LIBSAMPLERATE_DYNAMIC */ + +/* Enable ime support */ +/* #undef SDL_USE_IME */ + +/* Platform specific definitions */ +/* #undef SDL_IPHONE_KEYBOARD */ +/* #undef SDL_IPHONE_LAUNCHSCREEN */ + +/* #undef SDL_VIDEO_VITA_PIB */ +/* #undef SDL_VIDEO_VITA_PVR */ +/* #undef SDL_VIDEO_VITA_PVR_OGL */ + +/* #undef SDL_HAVE_LIBDECOR_GET_MIN_MAX */ + +#if !defined(HAVE_STDINT_H) && !defined(_STDINT_H_) +/* Most everything except Visual Studio 2008 and earlier has stdint.h now */ +#if defined(_MSC_VER) && (_MSC_VER < 1600) +typedef signed __int8 int8_t; +typedef unsigned __int8 uint8_t; +typedef signed __int16 int16_t; +typedef unsigned __int16 uint16_t; +typedef signed __int32 int32_t; +typedef unsigned __int32 uint32_t; +typedef signed __int64 int64_t; +typedef unsigned __int64 uint64_t; +#ifndef _UINTPTR_T_DEFINED +#ifdef _WIN64 +typedef unsigned __int64 uintptr_t; +#else +typedef unsigned int uintptr_t; +#endif +#define _UINTPTR_T_DEFINED +#endif +#endif /* Visual Studio 2008 */ +#endif /* !_STDINT_H_ && !HAVE_STDINT_H */ +/* ── X11 video, enabled for mcpp-index ─────────────────────────────────────── + * The CMake probe that produced this file ran on a host without system X11 + * development headers, so it disabled X11 and left only the dummy/offscreen + * drivers. This index does supply X11 — the same set compat.glfw links — so the + * driver is switched on here explicitly. + * + * NOT dynamic: SDL_VIDEO_DRIVER_X11_DYNAMIC would make SDL dlopen libX11.so at + * runtime, but compat.x11 is a static library, so the symbols must be linked. + * + * XSCRNSAVER is absent on purpose — the index has no compat.xscrnsaver. Its + * only effect is that SDL falls back to its own screensaver inhibition path. */ +#define SDL_VIDEO_DRIVER_X11 1 +#define SDL_VIDEO_DRIVER_X11_XCURSOR 1 +#define SDL_VIDEO_DRIVER_X11_XDBE 1 +#define SDL_VIDEO_DRIVER_X11_XFIXES 1 +#define SDL_VIDEO_DRIVER_X11_XINPUT2 1 +#define SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH 1 +#define SDL_VIDEO_DRIVER_X11_XRANDR 1 +#define SDL_VIDEO_DRIVER_X11_XSHAPE 1 +#define SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS 1 +#define SDL_VIDEO_DRIVER_X11_HAS_XKBKEYCODETOKEYSYM 1 + +#else +#error "compat.sdl2: no SDL_config.h branch for this platform" +#endif + +#endif /* SDL_config_h_ */ +]==], + }, + + linux = { + sources = { + -- The ONE directory where a glob over-reaches: SDL_dbus.c, + -- SDL_ibus.c, SDL_fcitx.c and SDL_ime.c are the DBus-based IME + -- stack and are NOT guarded by an internal #if — they simply + -- fail to compile without dbus-1 headers, which this index does + -- not package. Upstream's CMake omits them under + -- -DSDL_DBUS=OFF; this list does the same. + "*/src/core/linux/SDL_evdev.c", + "*/src/core/linux/SDL_evdev_capabilities.c", + "*/src/core/linux/SDL_evdev_kbd.c", + "*/src/core/linux/SDL_sandbox.c", + "*/src/core/linux/SDL_threadprio.c", + "*/src/core/linux/SDL_udev.c", + "*/src/core/unix/*.c", + "*/src/filesystem/unix/*.c", + "*/src/haptic/linux/*.c", + "*/src/joystick/linux/*.c", + "*/src/loadso/dlopen/*.c", + "*/src/locale/unix/*.c", + "*/src/misc/unix/*.c", + "*/src/power/linux/*.c", + "*/src/thread/pthread/*.c", + "*/src/timer/unix/*.c", + "*/src/video/x11/*.c", + }, + -- The X11 set compat.glfw already proves out. xorgproto carries + -- ; the rest are the extensions the config block above + -- switches on (XCursor, Xext for XDBE/XShape, XFixes, XInput2, + -- XRandR) plus Xinerama, which SDL probes for. + deps = { + -- glx-headers, not compat.opengl: SDL's X11 driver includes + -- , which the Khronos registry does not carry. The two + -- packages overlap on GL/gl.h, so exactly one of them belongs + -- here — see the note in compat.glx-headers. + ["compat.glx-headers"] = "1.7.0", + ["compat.x11"] = "1.8.13", + ["compat.xcursor"] = "1.2.3", + ["compat.xext"] = "1.3.7", + ["compat.xfixes"] = "6.0.2", + ["compat.xi"] = "1.8.3", + ["compat.xinerama"] = "1.1.6", + ["compat.xorgproto"] = "2025.1", + ["compat.xrandr"] = "1.5.5", + ["compat.xrender"] = "0.9.12", + }, + ldflags = { "-lpthread", "-ldl", "-lm" }, + runtime = { + capabilities = { "x11.display" }, + }, + }, + + macosx = { + -- Cocoa, CoreAudio and the IOKit joystick/haptic backends are + -- Objective-C, hence the .m globs alongside the .c ones. + sources = { + "*/src/audio/coreaudio/*.m", + "*/src/file/cocoa/*.m", + "*/src/filesystem/cocoa/*.m", + "*/src/haptic/darwin/*.c", + "*/src/joystick/darwin/*.c", + -- SDL_JOYSTICK_MFI is on in upstream's macosx config, and + -- SDL_gamecontroller.c calls into it (IOS_SupportedHIDDevice, + -- IOS_GameControllerGetAppleSFSymbolsName*). The directory is + -- named iphoneos but serves macOS too. + "*/src/joystick/iphoneos/*.m", + "*/src/loadso/dlopen/*.c", + "*/src/locale/macosx/*.m", + "*/src/misc/macosx/*.m", + "*/src/power/macosx/*.c", + "*/src/render/metal/*.m", + "*/src/thread/pthread/*.c", + "*/src/timer/unix/*.c", + "*/src/video/cocoa/*.m", + }, + -- ARC is not optional on Apple: SDL's cocoa classes declare + -- `__weak SDL_WindowData *_data`, and a __weak ivar without ARC is + -- rejected outright ("'_data' is unavailable"). Upstream's CMake + -- hard-fails when the compiler cannot do -fobjc-arc, for this + -- reason. It applies to the .m files, which mcpp routes through + -- cflags along with the .c ones. + cflags = { "-fobjc-arc" }, + ldflags = { + "-framework", "Cocoa", + "-framework", "CoreFoundation", + "-framework", "CoreAudio", + "-framework", "AudioToolbox", + "-framework", "CoreVideo", + "-framework", "IOKit", + "-framework", "ForceFeedback", + "-framework", "Carbon", + "-framework", "Metal", + "-framework", "QuartzCore", + "-framework", "CoreHaptics", + "-framework", "GameController", + -- Foundation/CoreFoundation for CF* (CFRelease and friends); + -- SystemConfiguration for SCDynamicStoreCopyProxies, which + -- SDL's URL/misc code uses. + "-framework", "Foundation", + "-framework", "SystemConfiguration", + "-lpthread", "-lm", + }, + }, + + windows = { + sources = { + "*/src/audio/directsound/*.c", + "*/src/audio/wasapi/*.c", + "*/src/audio/winmm/*.c", + "*/src/core/windows/*.c", + "*/src/filesystem/windows/*.c", + "*/src/haptic/windows/*.c", + "*/src/joystick/windows/*.c", + "*/src/loadso/windows/*.c", + "*/src/locale/windows/*.c", + "*/src/misc/windows/*.c", + "*/src/power/windows/*.c", + "*/src/sensor/windows/*.c", + -- thread/windows, plus exactly one file from thread/generic. + -- The windows condition-variable backend (SDL_syscond_cv.c) + -- falls back to the generic implementation, so + -- SDL_CreateCond_generic & co. have to be linked. Only that one + -- file: generic's SDL_sysmutex.c / SDL_syssem.c / + -- SDL_systhread.c / SDL_systls.c share basenames with the + -- windows set, and mcpp keys objects by basename in one flat + -- per-link directory (mcpp#233), so pulling the whole directory + -- would have them silently displace each other. SDL_syscond.c + -- has no windows twin, so it is safe. + "*/src/thread/generic/SDL_syscond.c", + "*/src/thread/windows/*.c", + "*/src/timer/windows/*.c", + "*/src/video/windows/*.c", + }, + -- SDL_audiocvt.c carries runtime-dispatched SSE3 converters, and + -- clang refuses to inline `_mm_hadd_ps` into a function compiled + -- without the target feature (gcc is lenient here, which is why + -- Linux never saw it). Upstream applies the flag per file; so do we, + -- rather than raising the ISA floor for the whole library. + flags = { + { glob = "*/src/audio/SDL_audiocvt.c", cflags = { "-msse3" } }, + }, + ldflags = { + "-luser32", "-lgdi32", "-lwinmm", "-limm32", + "-lole32", "-loleaut32", "-lshell32", "-lsetupapi", + "-lversion", "-luuid", "-ladvapi32", + }, + }, + }, +} diff --git a/pkgs/c/compat.vulkan-headers.lua b/pkgs/c/compat.vulkan-headers.lua new file mode 100644 index 00000000..64c4420a --- /dev/null +++ b/pkgs/c/compat.vulkan-headers.lua @@ -0,0 +1,68 @@ +-- compat.vulkan-headers — Khronos Vulkan API headers. +-- +-- Header-only, the same shape as `compat.opengl`: expose `include/` and carry a +-- trivial anchor TU so the package still produces a buildable lib target. +-- Split out from `compat.vulkan` (the loader) because they are separate +-- upstream repositories on separate release cadences, and because a consumer +-- that only needs the types — a renderer compiled against a loader it does not +-- link, say — should not drag the loader in. +-- +-- `vk_video/` ships alongside `vulkan/` and is included by `vulkan_video.h`, so +-- one include root covers both. +-- +-- Versioning follows the Vulkan SDK release the tag belongs to +-- (`vulkan-sdk-1.4.357.0` → `1.4.357.0`), which is how Khronos ties the header, +-- loader and validation-layer repos together. +package = { + spec = "1", + namespace = "compat", + name = "vulkan-headers", + description = "Khronos Vulkan API headers", + licenses = {"Apache-2.0"}, + repo = "https://github.com/KhronosGroup/Vulkan-Headers", + type = "package", + + xpm = { + linux = { + ["1.4.357.0"] = { + url = { + GLOBAL = "https://github.com/KhronosGroup/Vulkan-Headers/archive/refs/tags/vulkan-sdk-1.4.357.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/vulkan-headers/releases/download/1.4.357.0/vulkan-headers-1.4.357.0.tar.gz", + }, + sha256 = "e87dce08116151f6b6d7de6b6faf41498e87e6cf848ff16fa3bd5402190ad4a3", + }, + }, + macosx = { + ["1.4.357.0"] = { + url = { + GLOBAL = "https://github.com/KhronosGroup/Vulkan-Headers/archive/refs/tags/vulkan-sdk-1.4.357.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/vulkan-headers/releases/download/1.4.357.0/vulkan-headers-1.4.357.0.tar.gz", + }, + sha256 = "e87dce08116151f6b6d7de6b6faf41498e87e6cf848ff16fa3bd5402190ad4a3", + }, + }, + windows = { + ["1.4.357.0"] = { + url = { + GLOBAL = "https://github.com/KhronosGroup/Vulkan-Headers/archive/refs/tags/vulkan-sdk-1.4.357.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/vulkan-headers/releases/download/1.4.357.0/vulkan-headers-1.4.357.0.tar.gz", + }, + sha256 = "e87dce08116151f6b6d7de6b6faf41498e87e6cf848ff16fa3bd5402190ad4a3", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c11", + include_dirs = { "*/include" }, + generated_files = { + ["mcpp_generated/vulkan_headers_anchor.c"] = + "int mcpp_compat_vulkan_headers_anchor(void) { return 0; }\n", + }, + sources = { "mcpp_generated/vulkan_headers_anchor.c" }, + targets = { ["vulkan-headers"] = { kind = "lib" } }, + deps = {}, + }, +} diff --git a/pkgs/c/compat.vulkan-runtime.lua b/pkgs/c/compat.vulkan-runtime.lua new file mode 100644 index 00000000..e67b5328 --- /dev/null +++ b/pkgs/c/compat.vulkan-runtime.lua @@ -0,0 +1,199 @@ +-- compat.vulkan-runtime — host Vulkan ICD adapter for mcpp Linux applications. +-- +-- The exact counterpart of `compat.glx-runtime`, for the same reason and in the +-- same shape. A GPU driver cannot be a package: the ICD has to match the kernel +-- driver on the machine it runs on, so the GL runtime plan +-- (.agents/docs/2026-06-03-gl-runtime-packages-plan.md) settled on modelling it +-- as a HOST CAPABILITY rather than "silently pretending vendor drivers are +-- normal redistributable packages". Nothing is vendored here either — this is a +-- symlink farm plus the metadata that makes it reachable. +-- +-- WHAT IT FIXES. `compat.vulkan` builds the Khronos loader, and the loader finds +-- every ICD manifest on the host correctly. It then fails to dlopen a single +-- driver: +-- +-- DRIVER: Found the following files: /usr/share/vulkan/icd.d/lvp_icd.json … +-- ERROR: libvulkan_lvp.so: cannot open shared object file +-- +-- The libraries are right there in /usr/lib/x86_64-linux-gnu. What cannot reach +-- them is the process: an mcpp-built binary runs under mcpp's OWN glibc +-- +-- interp: …/xpkgs/xim-x-glibc/2.39/lib64/ld-linux-x86-64.so.2 +-- rpath : …/xim-x-glibc/2.39/lib64:…/xim-x-gcc/…/lib64:$ORIGIN +-- +-- so a bare-soname dlopen from inside the sandbox does not search the host's +-- library path at all. `runtime.library_dirs` below puts a package-owned +-- directory of symlinks on that path, which is precisely how `compat.glx-runtime` +-- makes host OpenGL work — and why the OpenGL backends already run while Vulkan +-- did not. +-- +-- THE PATTERN LIST covers the ICDs plus their transitive dependencies, because +-- the whole chain has to resolve through the same directory. Mesa's software +-- rasterizer pulls LLVM; NVIDIA pulls its own family. `libstdc++` is in the list +-- and that is not an oversight: mcpp links libstdc++ STATICALLY (it is absent +-- from a built binary's NEEDED), so a dlopen'd C++ ICD like lavapipe has nothing +-- to resolve against unless the host copy is provided here. +-- +-- NOTHING IS REQUIRED. Unlike `compat.glx-runtime`, which errors when libGL is +-- missing, a machine with no Vulkan driver at all is a legitimate configuration +-- — every CI runner in this repo is one. The farm is then simply empty and the +-- loader reports its own four extensions, which is what +-- `tests/examples/vulkan` asserts. +package = { + spec = "1", + namespace = "compat", + name = "vulkan-runtime", + description = "Host Vulkan ICD runtime adapter for mcpp Linux applications", + licenses = {"Apache-2.0"}, + repo = "https://github.com/KhronosGroup/Vulkan-Loader", + type = "package", + + xpm = { + linux = { + ["2026.07.29"] = { + -- Nothing is downloaded that matters: the package's content is + -- the symlink farm install() builds from the host. This is just + -- a stable, tiny anchor so the xpm entry is well-formed, the + -- same trick compat.glx-runtime uses with an OpenGL-Registry + -- README. + url = "https://raw.githubusercontent.com/KhronosGroup/Vulkan-Loader/vulkan-sdk-1.4.357.0/README.md", + sha256 = "21ec0987a05bd680ecd11f8be747e27744d7558f7318736f6cb8a5c5ec1b8ba8", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c11", + sources = { "mcpp_generated/vulkan_runtime_empty.c" }, + targets = { ["vulkan_runtime"] = { kind = "lib" } }, + deps = {}, + runtime = { + library_dirs = { "mcpp_generated/vulkan_runtime/lib" }, + capabilities = { "vulkan.icd.driver" }, + provides = { "vulkan.icd.driver" }, + }, + }, +} + +import("xim.libxpkg.pkginfo") +import("xim.libxpkg.log") + +local function sh_quote(value) + return "'" .. tostring(value):gsub("'", "'\\''") .. "'" +end + +local function split_paths(value) + local out = {} + if not value or value == "" then + return out + end + for item in tostring(value):gmatch("[^:]+") do + if item ~= "" then + table.insert(out, item) + end + end + return out +end + +local function candidate_dirs() + local out = {} + local seen = {} + local function add(dir) + if dir and dir ~= "" and not seen[dir] and os.isdir(dir) then + seen[dir] = true + table.insert(out, dir) + end + end + + for _, dir in ipairs(split_paths(os.getenv("MCPP_HOST_VULKAN_LIBRARY_PATH"))) do + add(dir) + end + add("/lib/x86_64-linux-gnu") + add("/usr/lib/x86_64-linux-gnu") + add("/lib64") + add("/usr/lib64") + add("/usr/lib") + return out +end + +-- ICDs first, then the transitive set they pull in — the whole chain has to +-- resolve through this one directory. Verified against Mesa's lavapipe (LLVM, +-- drm, expat, xcb, wayland, zstd) and NVIDIA's ICD, which is libGLX_nvidia.so.0 +-- and drags the libnvidia* family. +-- +-- EVERY DEPENDENCY PATTERN IS VERSIONED (`lib*.so.*`), deliberately. mcpp puts +-- `runtime.library_dirs` on the LINK line as well as the runtime path, so a bare +-- `libxcb.so` harvested here would shadow this index's own `compat.xcb` and the +-- link fails with `undefined reference to XauDisposeAuth`. Versioned sonames are +-- invisible to the linker (it resolves `-lxcb` through `libxcb.so`/`libxcb.a`) +-- and are exactly what dlopen asks for, so the split is not a workaround so much +-- as the correct spelling. `compat.glx-runtime` never hit this only because the +-- GL family it harvests is not otherwise linked from the index. +-- +-- The Mesa ICDs themselves are genuinely named `libvulkan_lvp.so` with no +-- version, which is safe: nothing links `-lvulkan_lvp`. +-- +-- The host's own `libvulkan.so*` is deliberately NOT harvested: `compat.vulkan` +-- builds the loader itself, as a shared object with the canonical +-- `libvulkan.so.1` soname, and a second one on the path would be resolved by +-- SDL2's `dlopen` instead. One loader per process is the whole point. +local host_vulkan_patterns = { + -- Mesa ICDs: lavapipe, intel, radeon, nouveau, virtio, asahi, gfxstream + "libvulkan_*.so", + -- NVIDIA's ICD and its family + "libGLX_nvidia.so.*", + "libnvidia*.so.*", + -- transitive dependencies, versioned only + "libLLVM*.so.*", + "libdrm*.so.*", + "libexpat.so.*", + -- The X client stack, including its own auth dependencies. Incomplete is + -- worse than absent here: a farm carrying libxcb.so.1 but not libXau.so.6 + -- shadows the host copy that would otherwise have resolved, and the + -- executable fails to start. + "libxcb*.so.*", + "libX11-xcb.so.*", + "libXau.so.*", + "libXdmcp.so.*", + "libbsd.so.*", + "libmd.so.*", + "libxshmfence.so.*", + "libwayland-client.so.*", + "libz.so.*", + "libzstd.so.*", + "libelf.so.*", + "libffi.so.*", + "libedit.so.*", + "libtinfo.so.*", + "libxml2.so.*", + "libstdc++.so.*", +} + +local function link_runtime_libs(outdir) + os.mkdir(outdir) + for _, dir in ipairs(candidate_dirs()) do + for _, pattern in ipairs(host_vulkan_patterns) do + os.exec( + "for lib in " .. sh_quote(dir) .. "/" .. pattern .. + "; do [ -e \"$lib\" ] || continue; " .. + "ln -sf \"$lib\" " .. sh_quote(outdir) .. "/\"$(basename \"$lib\")\"; " .. + "done" + ) + end + end + return true +end + +function install() + os.tryrm(pkginfo.install_dir()) + os.mkdir(pkginfo.install_dir()) + + local generated = path.join(pkginfo.install_dir(), "mcpp_generated") + os.mkdir(generated) + io.writefile(path.join(generated, "vulkan_runtime_empty.c"), + "int mcpp_compat_vulkan_runtime_anchor(void) { return 0; }\n") + + return link_runtime_libs(path.join(generated, "vulkan_runtime", "lib")) +end diff --git a/pkgs/c/compat.vulkan.lua b/pkgs/c/compat.vulkan.lua new file mode 100644 index 00000000..ed8996cc --- /dev/null +++ b/pkgs/c/compat.vulkan.lua @@ -0,0 +1,262 @@ +-- compat.vulkan — the Khronos Vulkan loader, built from source as a static lib. +-- +-- This is the thing a Vulkan program LINKS: `vkCreateInstance` and friends are +-- trampolines the loader owns, which then dispatch into whatever ICD (GPU +-- driver) the system advertises. Headers alone are not enough, which is why +-- `compat.vulkan-headers` is a separate package this one depends on. +-- +-- Buildable as a plain source list — no CMake, no Python, no assembler: +-- +-- * `loader/generated/` (vk_loader_extensions.c, vk_object_types.h, …) is +-- CHECKED IN upstream, so the codegen step CMake would run is unnecessary. +-- * The assembly path is optional. Upstream's CMake compiles +-- `dev_ext_trampoline.c` + `phys_dev_ext.c` against hand-written GAS/MASM +-- and a generated `gen_defines.asm` (which needs building and RUNNING +-- asm_offset, then a Python script to scrape its output). That whole chain +-- is gated on `UNKNOWN_FUNCTIONS_SUPPORTED`; upstream itself degrades +-- gracefully when no working assembler is found, and +-- `unknown_function_handling.c` compiles a pure-C fallback instead. We take +-- that fallback deliberately: the cost is that unknown DEVICE extension +-- entry points (ones this loader version has never heard of) get no +-- trampoline, which no consumer in this index uses. +-- +-- WINDOWS TAKES A DIFFERENT SHAPE: an import library, not a built loader. +-- +-- A statically linked loader cannot work there, and the reason is in upstream's +-- own source rather than just its docs. `vk_loader_platform.h` says the Windows +-- build "does initialization in the first API call made, using +-- InitOnceExecuteOnce, EXCEPT for initialization primitives which must be done +-- in DllMain" — and `loader_windows.c`'s DllMain is what creates `loader_lock` +-- and `loader_preload_icd_lock`. A static library never gets a DllMain, so the +-- first API call takes an uninitialized CRITICAL_SECTION and faults +-- (0xC0000005 out of vkEnumerateInstanceVersion, observed in CI). macOS escapes +-- this through `APPLE_STATIC_LOADER` + pthread_once; Linux through +-- `__attribute__((constructor))`. Windows has neither. +-- +-- The supported Windows arrangement is the ordinary one every Vulkan +-- application uses: link `vulkan-1.lib` and let the system `vulkan-1.dll`, +-- installed by any GPU driver, do the ICD loading. The windows xpm entry is +-- therefore a small artifact carrying that import library — symbol stubs, no +-- code — generated from Khronos' own `loader/vulkan-1.def` (shipped in this +-- very loader tarball) with a single reproducible command: +-- +-- llvm-dlltool -d vulkan-1.def -l lib/vulkan-1.lib -m i386:x86-64 +-- +-- Deliberately NOT an install() hook running that command at build time: the +-- hook would have to locate llvm-dlltool inside the resolved toolchain, and the +-- output is a fixed function of an upstream text file. Prebuilt Windows +-- artifacts on xlings-res are the pattern `compat.openssl` already anticipates. +-- +-- SYSCONFDIR / FALLBACK_*_DIRS are the ICD and layer manifest search paths. +-- Upstream's CMake derives them from the install prefix; the values below are +-- the FHS/XDG defaults, which is what a system-installed driver actually uses — +-- this package must find the HOST's ICDs, not any path of its own. +-- +-- All `mcpp` paths are GLOBS relative to the verdir; the leading `*/` absorbs +-- the GitHub tarball's `Vulkan-Loader-vulkan-sdk-1.4.357.0/` wrap layer. +package = { + spec = "1", + namespace = "compat", + name = "vulkan", + description = "Khronos Vulkan loader — static ICD-dispatch library", + licenses = {"Apache-2.0"}, + repo = "https://github.com/KhronosGroup/Vulkan-Loader", + type = "package", + + xpm = { + linux = { + ["1.4.357.0"] = { + url = { + GLOBAL = "https://github.com/KhronosGroup/Vulkan-Loader/archive/refs/tags/vulkan-sdk-1.4.357.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/vulkan/releases/download/1.4.357.0/vulkan-1.4.357.0.tar.gz", + }, + sha256 = "54f2537df22313768da0317dda2abdaaab7711b4081c48c869a79db343d0ae70", + }, + }, + macosx = { + ["1.4.357.0"] = { + url = { + GLOBAL = "https://github.com/KhronosGroup/Vulkan-Loader/archive/refs/tags/vulkan-sdk-1.4.357.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/vulkan/releases/download/1.4.357.0/vulkan-1.4.357.0.tar.gz", + }, + sha256 = "54f2537df22313768da0317dda2abdaaab7711b4081c48c869a79db343d0ae70", + }, + }, + windows = { + ["1.4.357.0"] = { + url = { + GLOBAL = "https://github.com/xlings-res/vulkan-import/releases/download/1.4.357.1/vulkan-import-1.4.357.1.tar.gz", + CN = "https://gitcode.com/mcpp-res/vulkan-import/releases/download/1.4.357.1/vulkan-import-1.4.357.1.tar.gz", + }, + sha256 = "37a206f866f75f54a56bdb428e4767c9926acd3f8abc8e1b9539853bb45acbf9", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c11", + + -- `*/loader*` simply match nothing in the windows artifact, which + -- carries only lib/ and the .def. + include_dirs = { "*/loader", "*/loader/generated", "mcpp_generated" }, + + -- SYSCONFDIR / FALLBACK_*_DIRS have to reach the compiler as STRING + -- literals, and `-DSYSCONFDIR="/etc"` does not survive the trip: mcpp + -- splits flags without honouring the quotes (mcpp#234), so loader.c + -- ends up seeing a bare `/etc` and fails with "expected expression + -- before '/' token". Carrying them in a force-included header sidesteps + -- the command line entirely — the same move `compat.opencv5` made for + -- its space-bearing defines. + generated_files = { + ["mcpp_generated/vulkan_import_anchor.c"] = + "int mcpp_compat_vulkan_import_anchor(void) { return 0; }\n", + ["mcpp_generated/mcpp_vulkan_paths.h"] = [==[ +/* Manifest search paths for the Vulkan loader — see the descriptor note. */ +#pragma once +#define SYSCONFDIR "/etc" +#define FALLBACK_CONFIG_DIRS "/etc/xdg" +#define FALLBACK_DATA_DIRS "/usr/local/share:/usr/share" +]==], + }, + + -- Upstream NORMAL_LOADER_SRCS, minus the OPT_LOADER_SRCS pair that only + -- builds with the assembly path (see the header note). + sources = { + "*/loader/allocation.c", + "*/loader/cJSON.c", + "*/loader/debug_utils.c", + "*/loader/extension_manual.c", + "*/loader/gpa_helper.c", + "*/loader/loader.c", + "*/loader/loader_environment.c", + "*/loader/loader_json.c", + "*/loader/log.c", + "*/loader/settings.c", + "*/loader/terminator.c", + "*/loader/trampoline.c", + "*/loader/unknown_function_handling.c", + "*/loader/wsi.c", + }, + + -- SHARED, with the canonical soname — not a static lib, and the choice + -- is load-bearing rather than stylistic. + -- + -- The Vulkan loader is designed to be the one shared object in a + -- process. SDL2 insists on that: `SDL_CreateWindow(SDL_WINDOW_VULKAN)` + -- calls `SDL_Vulkan_LoadLibrary(NULL)`, which dlopens `libvulkan.so.1` + -- and resolves surface creation through whatever it finds. Built + -- static, an application ends up with TWO loaders — its own for + -- `vkCreateInstance`, SDL's for `vkCreateXlibSurfaceKHR` — and + -- `createSurface` fails on an instance the second loader never saw. + -- Measured, not assumed. Shared, everyone (the application, GLFW via + -- glfwInitVulkanLoader, SDL via dlopen) converges on this one object. + -- + -- The soname is what makes SDL's bare `dlopen("libvulkan.so.1")` land + -- here, so it is not optional either. Same shape the X11 family in this + -- index already uses. + targets = { ["vulkan"] = { kind = "lib" } }, + deps = { ["compat.vulkan-headers"] = "1.4.357.0" }, + + -- VK_ENABLE_BETA_EXTENSIONS is not optional despite the name: the + -- checked-in generated/vk_object_types.h references + -- VK_OBJECT_TYPE_CUDA_MODULE_NV, which the headers only declare under + -- this macro. Without it the loader does not compile at all. + cflags = { "-DVK_ENABLE_BETA_EXTENSIONS" }, + + linux = { + -- LOADER_ENABLE_LINUX_SORT is what upstream sets alongside + -- loader_linux.c: it sorts physical devices by PCI bus info so + -- device 0 is the discrete GPU rather than whichever ICD replied + -- first. + -- SHARED here, and the choice is load-bearing: SDL2's + -- SDL_CreateWindow(SDL_WINDOW_VULKAN) dlopens libvulkan.so.1 and + -- resolves surface creation through whatever it finds. Static, an + -- application ends up with two loaders and createSurface fails on + -- an instance the second never saw. + targets = { ["vulkan"] = { kind = "shared", soname = "libvulkan.so.1" } }, + sources = { "*/loader/loader_linux.c" }, + cflags = { + "-D_GNU_SOURCE", + "-DHAVE_ALLOCA_H", + "-DLOADER_ENABLE_LINUX_SORT", + "-DVK_USE_PLATFORM_XLIB_KHR", + "-DVK_USE_PLATFORM_XCB_KHR", + "-include", "mcpp_vulkan_paths.h", + }, + -- The two VK_USE_PLATFORM_X*_KHR defines make vulkan_xlib.h / + -- vulkan_xcb.h pull in and , so the X + -- headers are a COMPILE dependency of the loader here, not just of + -- whoever creates the surface. xorgproto carries , which + -- Xlib.h includes. Only headers are needed — the loader never links + -- against Xlib; the ICD does. + deps = { + ["compat.x11"] = "1.8.13", + ["compat.xcb"] = "1.17.0", + ["compat.xorgproto"] = "2025.1", + -- Without this the loader finds every ICD manifest and then + -- fails to dlopen a single driver: an mcpp binary runs under + -- mcpp's own glibc, whose search path does not include the + -- host's. See the note at the top of compat.vulkan-runtime. + ["compat.vulkan-runtime"] = "2026.07.29", + }, + -- dlopen for the ICDs and layers; pthread for the loader's locks. + ldflags = { "-ldl", "-lpthread", "-lm" }, + runtime = { + -- The loader itself is linked statically, but a Vulkan program + -- is still useless without an installed ICD. Model that as a + -- capability rather than pretending a vendor driver is a + -- redistributable package — same call `compat.glfw` makes for + -- `opengl.glx.driver` (see the GL runtime plan doc). + capabilities = { "vulkan.icd.driver" }, + }, + }, + + macosx = { + -- No loader_linux.c and no LINUX_SORT. VK_USE_PLATFORM_METAL_EXT + -- costs nothing here: vulkan_metal.h typedefs CAMetalLayer to void + -- outside an Objective-C TU, so it needs no Metal SDK to compile. + cflags = { + "-D_GNU_SOURCE", + -- Both surface platforms, not just Metal: wsi.c `#error`s with + -- "VK_USE_PLATFORM_MACOS_MVK not defined!" when only one of the + -- pair is present on Apple. + "-DVK_USE_PLATFORM_METAL_EXT", + "-DVK_USE_PLATFORM_MACOS_MVK", + -- Upstream's own switch for a statically linked loader; it is + -- what makes the entry points resolve without the DLL-style + -- export table. + "-DAPPLE_STATIC_LOADER", + "-include", "mcpp_vulkan_paths.h", + }, + -- CoreFoundation for CFRelease & friends: the loader reads bundle + -- paths when it looks for ICDs. + ldflags = { "-framework", "CoreFoundation", "-lpthread", "-lm" }, + runtime = { + -- macOS has no native Vulkan; the ICD is MoltenVK, layered over + -- Metal. The loader loads it exactly like any other ICD. + capabilities = { "vulkan.icd.driver" }, + }, + }, + + windows = { + -- Nothing to compile: the artifact is the import library plus the + -- .def it came from. The anchor keeps a buildable target, the same + -- shape `compat.opengl` uses for a headers-only package. + -- + -- The artifact is packed FLAT — lib/ at the archive root, no wrap + -- directory — because `-L` is not glob-expanded the way + -- include_dirs and sources are. With a wrap layer the relative + -- `-Llib` below misses and the link fails with + -- "LNK1181: cannot open input file 'vulkan-1.lib'". + sources = { "mcpp_generated/vulkan_import_anchor.c" }, + ldflags = { "-Llib", "-lvulkan-1" }, + runtime = { + -- vulkan-1.dll ships with the GPU driver, not with us. + dlopen_libs = { "vulkan-1.dll" }, + capabilities = { "vulkan.icd.driver" }, + }, + }, + }, +} diff --git a/pkgs/e/compat.eui-neo.lua b/pkgs/e/compat.eui-neo.lua new file mode 100644 index 00000000..bf31c2f9 --- /dev/null +++ b/pkgs/e/compat.eui-neo.lua @@ -0,0 +1,347 @@ +-- compat.eui-neo — EUI-NEO, a declarative retained-mode C++17 UI framework. +-- +-- Header-compat shape (Form B, `import_std = false`): the ~20 core TUs are +-- compiled into one lib and the public headers are exposed through +-- `include_dirs`, so a consumer writes `#include `. The C++23 +-- module surface (`import eui;`) is deliberately NOT modelled here — upstream +-- ships no module interface units, and wrapping 40+ component headers is a +-- separate piece of work. +-- +-- Upstream vendors its third-party libraries under `3rd/` (freetype, glfw, +-- libpng, zlib, glad, tray, yyjson, md4c). NONE of those are built here: each +-- one already exists in this index as its own `compat.*` package at the same +-- upstream version, and building them once for the whole ecosystem is the +-- point of having them. `3rd/` is still on the include path because three +-- genuinely vendored single-file headers live at its root (stb_image, +-- nanosvg, nanosvgrast) and the sources include them as `"3rd/stb_image.h"`. +-- +-- The build recipe below tracks upstream `CMakeLists.txt` (v0.5.3): CORE_SOURCES +-- plus the OpenGL backend and, for the glfw window backend, `ime_bridge.c`. +-- +-- All `mcpp` paths are GLOBS relative to the verdir; the leading `*/` absorbs +-- the GitHub tarball's `EUI-NEO-0.5.3/` wrap layer. +package = { + spec = "1", + namespace = "compat", + name = "eui-neo", + description = "EUI-NEO — declarative retained-mode C++17 UI framework (GLFW + OpenGL)", + licenses = {"Apache-2.0"}, + repo = "https://github.com/sudoevolve/EUI-NEO", + type = "package", + + xpm = { + linux = { + ["0.5.3"] = { + url = { GLOBAL = "https://github.com/sudoevolve/EUI-NEO/archive/refs/tags/v0.5.3.tar.gz", + CN = "https://gitcode.com/mcpp-res/eui-neo/releases/download/0.5.3/eui-neo-0.5.3.tar.gz" }, + sha256 = "6951ac330d0307c633bafe720b7888bf32785103eb16973adb4ee05ef06e64d1", + }, + }, + macosx = { + ["0.5.3"] = { + url = { GLOBAL = "https://github.com/sudoevolve/EUI-NEO/archive/refs/tags/v0.5.3.tar.gz", + CN = "https://gitcode.com/mcpp-res/eui-neo/releases/download/0.5.3/eui-neo-0.5.3.tar.gz" }, + sha256 = "6951ac330d0307c633bafe720b7888bf32785103eb16973adb4ee05ef06e64d1", + }, + }, + windows = { + ["0.5.3"] = { + url = { GLOBAL = "https://github.com/sudoevolve/EUI-NEO/archive/refs/tags/v0.5.3.tar.gz", + CN = "https://gitcode.com/mcpp-res/eui-neo/releases/download/0.5.3/eui-neo-0.5.3.tar.gz" }, + sha256 = "6951ac330d0307c633bafe720b7888bf32785103eb16973adb4ee05ef06e64d1", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c99", + + -- `*/include` carries the umbrella `eui_neo.h` and `eui/*.h`; `*` is the + -- verdir root, which is what makes the `"components/…"`, `"core/…"` and + -- `"3rd/stb_image.h"` quoted includes resolve. Upstream marks both PUBLIC. + include_dirs = { "*/include", "*", "mcpp_generated" }, + + -- mcpp#233/#240: every package in a link emits its objects into ONE + -- flat obj/ dir keyed by source basename. Upstream's + -- `core/platform/platform.cpp` and `compat.glfw`'s `src/platform.c` + -- both want `platform.o`, and the collision drops BOTH — verified on a + -- cold 646-object link where neither `core::platform::` nor + -- `_glfwSelectPlatform` reached the binary. Nothing in the minimal test + -- referenced them, so it linked green anyway; a real application would + -- not. Route the TU through a uniquely named stub, the same technique + -- `compat.opencv5` uses for its `modules/*/src` collisions. Renaming + -- only this side is enough: with `platform.o` no longer contested, + -- glfw's own object survives too. + generated_files = { + -- Resolves the two exclusive backend choices from the feature flags + -- mcpp hands us. Force-included into every TU of this package via + -- the `cflags` below, so it runs before any upstream header looks + -- at EUI_RENDER_BACKEND_* / EUI_WINDOW_BACKEND_SDL2. + ["mcpp_generated/mcpp_eui_backends.h"] = [==[ +/* Backend selection for compat.eui-neo — see the descriptor's note. */ +#pragma once + +/* Render backend: vulkan when asked for, OpenGL otherwise. Exactly one. */ +#if defined(MCPP_FEATURE_VULKAN) +# define EUI_RENDER_BACKEND_VULKAN 1 +#else +# define EUI_RENDER_BACKEND_OPENGL 1 +#endif + +/* Window backend: SDL2 when asked for, GLFW otherwise (GLFW is the absence of + * the SDL2 define, which is how upstream spells it too). */ +#if defined(MCPP_FEATURE_SDL2) +# define EUI_WINDOW_BACKEND_SDL2 1 +#endif +]==], + ["mcpp_generated/eui_neo_platform_tu.cpp"] = [==[ +/* Uniquely named forwarding TU — see the mcpp#233 note in the descriptor. */ +#include "core/platform/platform.cpp" +]==], + }, + + -- CMake CORE_SOURCES + the OpenGL render backend + glfw's ime_bridge. + sources = { + -- Platform layer + "*/core/platform/async.cpp", + -- ime_bridge.c is glfw-specific and rides with the glfw feature. + "*/core/platform/json.cpp", + "*/core/platform/native_bridge.c", + "*/core/platform/network.cpp", + "*/core/platform/performance_stats.cpp", + -- core/platform/platform.cpp enters through the generated stub above. + "mcpp_generated/eui_neo_platform_tu.cpp", + "*/core/platform/tray_bridge.c", + -- Render layer (backend-agnostic) + "*/core/render/image.cpp", + "*/core/render/image_facade.cpp", + "*/core/render/image_source.cpp", + "*/core/render/primitive.cpp", + "*/core/render/render_backend.cpp", + "*/core/render/stb_image_impl.cpp", + "*/core/render/text.cpp", + -- OpenGL backend and the GLFW IME bridge are UNCONDITIONAL sources. + -- Which of them the preprocessor keeps is decided by the generated + -- backend header below, not by whether they were compiled. + "*/core/render/opengl/opengl_backend.cpp", + "*/core/render/opengl/opengl_image.cpp", + "*/core/render/opengl/opengl_primitives.cpp", + "*/core/render/opengl/opengl_text.cpp", + "*/core/platform/ime_bridge.c", + -- Window layer + "*/core/window/window_backend.cpp", + }, + + targets = { ["eui-neo"] = { kind = "lib" } }, + + -- Every entry replaces a directory upstream vendors under `3rd/`, at the + -- same version upstream pins: + -- freetype 2.13.3, libpng 1.6.43, zlib (3rd/zlib-1.3.1), glfw 3.4, + -- glad 651a425 (the exact commit 3rd/dependencies.cmake fetches), + -- yyjson 0.12.0, tray 8dd1358. + -- `tray` is a dep on all three platforms for uniformity even though + -- `tray_bridge.c` only reaches `tray.h` under EUI_TRAY_WINAPI (see below). + deps = { + ["compat.freetype"] = "2.13.3", + ["compat.libpng"] = "1.6.43", + ["compat.zlib"] = "1.3.2", + ["compat.yyjson"] = "0.12.0", + -- The DEFAULT backends' packages live in the base dep set, not in + -- the `default` feature: mcpp applies a default feature's `defines` + -- and `sources` but IGNORES its `deps` (verified — a default member + -- resolved only freetype/libpng/tray/yyjson and then failed on + -- ). Non-default features' `deps` do work, which is + -- why `vulkan` and `sdl2` can carry theirs. + -- + -- Consequence: a consumer picking `vulkan` or `sdl2` still builds + -- these. They are cheap — compat.opengl is header-only plus an + -- anchor, compat.glad is one TU — and correctness beats saving + -- compat.glfw's 23 TUs. + ["compat.opengl"] = "2026.05.31", + ["compat.glad"] = "0.0.0-651a425", + ["compat.glfw"] = "3.4", + ["compat.tray"] = "0.0.0-8dd1358", + }, + + -- ── Backend selection ────────────────────────────────────────────── + -- + -- The render and window backends are mutually exclusive build-time + -- choices: core/render/render_backend.cpp is + -- `#if defined(EUI_RENDER_BACKEND_OPENGL) … #elif defined(…VULKAN)`, + -- and core/window/window_backend.cpp is `#if EUI_WINDOW_BACKEND_SDL2` + -- / else-GLFW. Define both halves of either pair and the first one + -- silently wins, ignoring what the consumer asked for. + -- + -- mcpp features are purely additive and there is no + -- `default-features = false` (mcpp#242). The obvious encodings all + -- fail on 0.0.109, each in its own way — all three verified with + -- probes, because each failure is silent: + -- + -- * `default = { defines/sources/deps = … }` is INERT. Not + -- "suppressed when features are named" — never applied at all. A + -- member depending on the package plainly built with no render + -- backend and still passed its smoke test, because nothing in the + -- test reached one. + -- * `default = { implies = { … } }` is the opposite: ALWAYS applied, + -- including when the consumer names a different feature. Routed + -- this way, asking for `vulkan` keeps OpenGL enabled too. + -- * a plain package-level define cannot be turned off by a feature, + -- since features only add. + -- + -- What does work is that mcpp passes `-DMCPP_FEATURE_` for every + -- enabled feature, to the package's own translation units. So the + -- exclusivity is resolved in the preprocessor, by a force-included + -- header, and the features themselves only need to carry sources and + -- dependencies. Consumers get the same answer through the features' + -- interface `defines`. + -- + -- eui-neo = "0.5.3" -> opengl + glfw + -- eui-neo = { …, features = ["vulkan"] } -> vulkan + glfw + -- eui-neo = { …, features = ["sdl2"] } -> opengl + SDL2 + -- eui-neo = { …, features = ["vulkan", "sdl2"] } -> vulkan + SDL2 + -- eui-neo = { …, features = ["markdown"] } -> opengl + glfw + -- + -- Note the last line: unlike an encoding built on `default`, naming an + -- unrelated feature no longer silently drops the backends. + -- BOTH lists, and that is not redundant: mcpp routes `cflags` to C + -- translation units and `cxxflags` to C++ ones. A define placed only in + -- `cflags` reaches ime_bridge.c / native_bridge.c / tray_bridge.c and + -- NOTHING else — which is exactly how an earlier revision of this + -- descriptor shipped `-DEUI_RENDER_BACKEND_OPENGL=1` that + -- render_backend.cpp never saw, leaving createRenderBackend() on its + -- `#else` branch returning a null backend. Verified by symbol + -- inspection, since it links and runs cleanly either way. + cflags = { "-include", "mcpp_eui_backends.h" }, + cxxflags = { "-include", "mcpp_eui_backends.h" }, + + features = { + ["vulkan"] = { + defines = { "EUI_RENDER_BACKEND_VULKAN=1" }, + sources = { + "*/core/render/vulkan/vulkan_backend.cpp", + "*/core/render/vulkan/vulkan_cache.cpp", + "*/core/render/vulkan/vulkan_image.cpp", + "*/core/render/vulkan/vulkan_polygon.cpp", + "*/core/render/vulkan/vulkan_primitives.cpp", + "*/core/render/vulkan/vulkan_text.cpp", + }, + deps = { ["compat.vulkan"] = "1.4.357.0" }, + }, + -- ── Window backend ──────────────────────────────────────────── + -- Exclusive in the same way and for the same reason as the render + -- backend: core/window/window_backend.cpp is + -- `#if defined(EUI_WINDOW_BACKEND_SDL2)` / else-GLFW, and + -- ime_bridge.c is GLFW-only (upstream adds it to CORE_SOURCES only + -- when EUI_WINDOW_BACKEND is glfw). + + ["sdl2"] = { + -- The define is for the CONSUMER's translation units; this + -- package's own get it from mcpp_eui_backends.h. + defines = { "EUI_WINDOW_BACKEND_SDL2=1" }, + deps = { ["compat.sdl2"] = "2.32.10" }, + }, + + -- ── Optional capabilities ───────────────────────────────────── + + -- core/platform/network.cpp is already in the base source list and + -- compiles to stubs without this define, so the feature costs a + -- dependency and a define rather than a translation unit. + ["network"] = { + defines = { "EUI_HAS_CURL=1" }, + deps = { ["compat.curl"] = "8.21.0" }, + }, + + -- Upstream's GLFW entry point, which owns `int main()` and drives + -- the render loop. CMake adds it per-APP (EUI_APP_MAIN_SOURCE), not + -- to the lib, so it is opt-in here for the same reason + -- `compat.gtest`'s `main` feature is: a consumer that has its own + -- main() must not get a second one. A real EUI application enables + -- this and supplies only app::dslAppConfig() + app::compose(). + ["app-main"] = { sources = { "*/core/app/glfw_app_main.cpp" } }, + -- Same gate for the SDL2 window backend. Upstream picks between the + -- two by EUI_APP_MAIN_SOURCE; here the consumer picks by name, and + -- must pick the one matching its window backend. + ["app-main-sdl2"] = { sources = { "*/core/app/sdl2_app_main.cpp" } }, + -- `components/markdown.h` is header-only and guards its body on + -- EUI_HAS_MD4C, so markdown lives entirely on the CONSUMER side — + -- the lib itself gains no translation unit from it. That is why + -- the define goes in `defines` (an INTERFACE define, propagated to + -- the consumer's TUs) rather than `cflags` (package-private): + -- without it reaching the consumer, md4c would link but the + -- component would still compile out. + ["markdown"] = { + defines = { "EUI_HAS_MD4C=1" }, + deps = { ["compat.md4c"] = "0.5.3" }, + }, + }, + + -- ── Platform-specific ────────────────────────────────────────────── + + windows = { + -- Upstream: EUI_TRAY_WINAPI + NOMINMAX, winmm/urlmon/shell32/ + -- user32/imm32/pdh. ole32 comes with urlmon's COM entry points. + -- NOMINMAX is needed by the C++ TUs too (windows.h reaches them + -- through eui_neo.h), hence both lists; EUI_TRAY_WINAPI only gates + -- tray_bridge.c, but keeping the pair symmetrical is cheaper than + -- re-deriving which is which. + cflags = { "-DEUI_TRAY_WINAPI=1", "-DNOMINMAX" }, + -- Upstream builds at CMAKE_CXX_STANDARD 17; this index's floor is + -- c++23, and one Windows-only line does not survive the move: + -- `parseWindowsSelection()` in core/platform/platform.cpp pushes + -- `path::u8string()` into a std::vector, and C++20 + -- changed that return type to std::u8string. + -- + -- The root cause is char8_t, not the standard level, so turn off + -- exactly that: every STL's selects the u8string() + -- return type on `__cpp_char8_t`, which -fno-char8_t undefines. + -- The rest of the package stays at c++23 on every platform. + -- + -- Linux and macOS never see this — the code is inside + -- `#if defined(_WIN32)`. Worth fixing upstream (`wideToUtf8()` + -- already sits eight lines above and does the right thing); until + -- then this keeps us on a real upstream release tag rather than a + -- fork carrying the patch. + cxxflags = { "-DEUI_TRAY_WINAPI=1", "-DNOMINMAX", "-fno-char8_t" }, + -- Upstream lists winmm/urlmon/shell32/user32/imm32/pdh and stops + -- there, because CMake's MSVC default `CMAKE_C_STANDARD_LIBRARIES` + -- already drags in kernel32/user32/gdi32/shell32/ole32/comdlg32/… + -- mcpp links only what the descriptor names, so the ones + -- platform.cpp actually reaches have to be spelled out: + -- comdlg32 for GetOpenFileNameW + CommDlgExtendedError, ole32 for + -- urlmon's COM entry points. (Pdh*, Imm*, timeBeginPeriod, + -- URLDownloadToFileA and ShellExecuteA are covered by the upstream + -- list.) Like the char8_t break above, this only showed up once the + -- mcpp#233 collision stopped dropping the TU. + ldflags = { + "-lwinmm", "-lurlmon", "-lshell32", + "-luser32", "-limm32", "-lpdh", "-lole32", + "-lcomdlg32", + }, + }, + + macosx = { + -- Upstream `enable_language(OBJC)` + LANGUAGE OBJC on the three + -- bridge files; the AppKit tray path is Cocoa-native and never + -- includes tray.h. + cflags = { "-DEUI_TRAY_APPKIT=1" }, + ldflags = { "-framework", "Cocoa", "-lobjc" }, + flags = { + { glob = "*/core/platform/native_bridge.c", cflags = { "-x", "objective-c" } }, + { glob = "*/core/platform/tray_bridge.c", cflags = { "-x", "objective-c" } }, + { glob = "*/core/platform/ime_bridge.c", cflags = { "-x", "objective-c" } }, + }, + }, + + linux = { + -- No tray define on purpose. Upstream only sets + -- EUI_TRAY_APPINDICATOR when pkg-config finds GTK3 AND + -- libappindicator; this index has neither, so tray_bridge.c + -- compiles its EUI_TRAY_HAS_BACKEND=0 stub — which is exactly + -- what upstream does on a machine without those dev packages. + -- `-ldl` is glad's CMAKE_DL_LIBS. + ldflags = { "-lpthread", "-ldl" }, + }, + }, +} diff --git a/tests/examples/curl/mcpp.toml b/tests/examples/curl/mcpp.toml new file mode 100644 index 00000000..12fb073b --- /dev/null +++ b/tests/examples/curl/mcpp.toml @@ -0,0 +1,10 @@ +[package] +name = "curl-tests" +version = "0.1.0" + +# Deliberately NO -DCURL_STATICLIB here: compat.curl publishes it itself, via a +# `default` feature that implies one carrying the define. If that ever stops +# working, reverts to dllimport declarations and the Windows link +# fails — which is the point of not spelling it out on this side. +[dependencies.compat] +curl = "8.21.0" diff --git a/tests/examples/curl/tests/transfer.cpp b/tests/examples/curl/tests/transfer.cpp new file mode 100644 index 00000000..0c216445 --- /dev/null +++ b/tests/examples/curl/tests/transfer.cpp @@ -0,0 +1,89 @@ +// Behavioral test — verify compat.curl builds a usable libcurl with working +// TLS, without touching the network. +// +// CI runners have no reliable outbound network and the index's other members +// never assume one, so nothing here opens a connection. What is asserted +// instead is everything that a misconfigured build actually gets wrong: +// +// * the library initialises at all (curl_global_init walks the same setup +// path a real transfer would), +// * the TLS backend is really compiled in — a curl built with no SSL still +// links and still runs, it just silently cannot do https, which is exactly +// the failure EUI-NEO's `network` feature would hit at runtime, +// * https and http are among the supported protocols, +// * a handle can be configured with the options EUI-NEO actually sets. +#include +import std; + +// CURL_STATICLIB must arrive from the PACKAGE, not from this project. Without +// it declares everything __declspec(dllimport) and the Windows +// link fails against a static archive; on other platforms it is inert, so this +// compile-time check is the only thing that catches a regression early. +static_assert([] { +#if defined(CURL_STATICLIB) + return true; +#else + return false; +#endif +}(), "CURL_STATICLIB did not reach the consumer — compat.curl stopped publishing it"); + +int main() { + if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) { + std::println("curl_global_init failed"); + return 1; + } + + const curl_version_info_data* info = curl_version_info(CURLVERSION_NOW); + if (info == nullptr) { + std::println("curl_version_info returned null"); + curl_global_cleanup(); + return 2; + } + + // The whole point of wiring a TLS backend. `ssl_version` is null when curl + // was built without one. + if ((info->features & CURL_VERSION_SSL) == 0 || info->ssl_version == nullptr) { + std::println("libcurl built WITHOUT TLS — https would fail at runtime"); + curl_global_cleanup(); + return 3; + } + + bool haveHttps = false; + bool haveHttp = false; + for (const char* const* p = info->protocols; p != nullptr && *p != nullptr; ++p) { + const std::string_view proto{*p}; + if (proto == "https") haveHttps = true; + if (proto == "http") haveHttp = true; + } + if (!haveHttp || !haveHttps) { + std::println("missing protocol support (http={}, https={})", haveHttp, haveHttps); + curl_global_cleanup(); + return 4; + } + + // The exact option set core/platform/network.cpp configures. A handle that + // rejects any of these would break the `network` feature. + CURL* handle = curl_easy_init(); + if (handle == nullptr) { + std::println("curl_easy_init failed"); + curl_global_cleanup(); + return 5; + } + const bool optionsOk = + curl_easy_setopt(handle, CURLOPT_URL, "https://example.invalid/") == CURLE_OK && + curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1L) == CURLE_OK && + curl_easy_setopt(handle, CURLOPT_TIMEOUT, 15L) == CURLE_OK && + curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 0L) == CURLE_OK; + curl_easy_cleanup(handle); + + if (!optionsOk) { + std::println("curl_easy_setopt rejected an option EUI-NEO relies on"); + curl_global_cleanup(); + return 6; + } + + std::println("compat.curl: ok ({}, ssl={}, http+https present)", + info->version, info->ssl_version); + curl_global_cleanup(); + return 0; +} diff --git a/tests/examples/eui-neo-markdown/mcpp.toml b/tests/examples/eui-neo-markdown/mcpp.toml new file mode 100644 index 00000000..28eecc74 --- /dev/null +++ b/tests/examples/eui-neo-markdown/mcpp.toml @@ -0,0 +1,10 @@ +[package] +name = "eui-neo-markdown-tests" +version = "0.1.0" + +# Long-form declaration so the `markdown` feature is requested. It pulls +# compat.md4c and publishes EUI_HAS_MD4C=1 as an interface define, which is +# what switches components/markdown.h from its fallback to the real parser. + +[dependencies.compat] +eui-neo = { version = "0.5.3", features = ["markdown"] } diff --git a/tests/examples/eui-neo-markdown/tests/markdown.cpp b/tests/examples/eui-neo-markdown/tests/markdown.cpp new file mode 100644 index 00000000..793a4434 --- /dev/null +++ b/tests/examples/eui-neo-markdown/tests/markdown.cpp @@ -0,0 +1,55 @@ +// POSITIVE verification for compat.eui-neo's `markdown` feature. +// +// components/markdown.h is header-only and compiles one of two definitions of +// detail::parseMarkdownBlocks depending on EUI_HAS_MD4C. That makes it a clean +// probe for something the descriptor cannot otherwise prove: the feature's +// `defines` really do reach the CONSUMER's translation unit, not just the +// package's own. Without propagation this member would link md4c and still +// silently get the fallback parser. +// +// tests/examples/eui-neo asserts the negative side — same header, feature off, +// degenerate single-Paragraph result. +#include +import std; + +namespace app { + +const DslAppConfig& dslAppConfig() { + static const DslAppConfig config = DslAppConfig{}.title("markdown feature test"); + return config; +} + +void compose(eui::Ui&, const eui::Screen&) {} + +} // namespace app + +int main() { +#if !defined(EUI_HAS_MD4C) + std::println("EUI_HAS_MD4C not defined — the feature's interface define did not propagate"); + return 1; +#else + namespace md = components::detail; + + const auto blocks = md::parseMarkdownBlocks("# Heading\n\nBody text.\n"); + + // The real parser splits heading from paragraph; the fallback returns one + // Paragraph holding the raw source. + if (blocks.size() < 2) { + std::println("expected >= 2 blocks from the md4c parser, got {}", blocks.size()); + return 2; + } + if (blocks[0].kind != md::MarkdownBlockKind::Heading || blocks[0].headingLevel != 1) { + std::println("expected an h1 first block, got kind={} level={}", + static_cast(blocks[0].kind), blocks[0].headingLevel); + return 3; + } + if (md::plainText(blocks[0].runs) != "Heading") { + std::println("unexpected heading text: '{}'", md::plainText(blocks[0].runs)); + return 4; + } + + std::println("compat.eui-neo[markdown]: ok ({} blocks, h1 = '{}')", + blocks.size(), md::plainText(blocks[0].runs)); + return 0; +#endif +} diff --git a/tests/examples/eui-neo-sdl2/mcpp.toml b/tests/examples/eui-neo-sdl2/mcpp.toml new file mode 100644 index 00000000..345a8341 --- /dev/null +++ b/tests/examples/eui-neo-sdl2/mcpp.toml @@ -0,0 +1,10 @@ +[package] +name = "eui-neo-sdl2-tests" +version = "0.1.0" + +# Covers the two remaining alternates in one member: the SDL2 window backend +# replacing GLFW, and `network` turning core/platform/network.cpp from stubs +# into a real libcurl-backed implementation. The render backend stays OpenGL, +# unnamed — naming a feature no longer costs you the defaults. +[dependencies.compat] +eui-neo = { version = "0.5.3", features = ["sdl2", "network"] } diff --git a/tests/examples/eui-neo-sdl2/tests/backends.cpp b/tests/examples/eui-neo-sdl2/tests/backends.cpp new file mode 100644 index 00000000..2ac6aa7c --- /dev/null +++ b/tests/examples/eui-neo-sdl2/tests/backends.cpp @@ -0,0 +1,91 @@ +// Verify compat.eui-neo's `sdl2` window backend and `network` feature. +// +// Both are asserted at COMPILE time first, because both fail silently at +// runtime otherwise: a window backend that did not switch just uses GLFW, and +// `network` that did not switch leaves core/platform/network.cpp compiled as +// stubs that return failure. Neither shows up without a display or a network. +#include +// SDL_MAIN_HANDLED before : on Windows SDL_main.h does +// `#define main SDL_main` and expects the real entry point to come from +// SDL2main (src/main/windows/SDL_windows_main.c). This package does not ship +// that — a library consumer should not be handed an entry point — so the test +// takes SDL's documented alternative and keeps its own main, pairing it with +// SDL_SetMainReady() below. Without this the link fails with +// "LNK1561: entry point must be defined". +#define SDL_MAIN_HANDLED +#include +#include +import std; + +#if !defined(EUI_WINDOW_BACKEND_SDL2) +#error "sdl2 feature requested but EUI_WINDOW_BACKEND_SDL2 is not defined" +#endif +#if !defined(EUI_HAS_CURL) +#error "network feature requested but EUI_HAS_CURL is not defined" +#endif + +namespace app { + +const DslAppConfig& dslAppConfig() { + static const DslAppConfig config = DslAppConfig{}.title("sdl2 + network test"); + return config; +} + +void compose(eui::Ui&, const eui::Screen&) {} + +} // namespace app + +int main() { + // SDL's dummy driver is a real driver, so unlike the GL/Vulkan backends + // this actually runs: init, create, query, tear down. + SDL_SetMainReady(); + SDL_SetHint(SDL_HINT_VIDEODRIVER, "dummy"); + if (SDL_Init(SDL_INIT_VIDEO) != 0) { + std::println("SDL_Init failed: {}", SDL_GetError()); + return 1; + } + SDL_Window* window = SDL_CreateWindow("eui-neo", SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, 200, 100, + SDL_WINDOW_HIDDEN); + if (window == nullptr) { + std::println("SDL_CreateWindow failed: {}", SDL_GetError()); + SDL_Quit(); + return 2; + } + const char* driver = SDL_GetCurrentVideoDriver(); + SDL_DestroyWindow(window); + SDL_Quit(); + + // libcurl came in through the feature's dependency, and it has TLS — the + // thing EUI-NEO's downloader needs and the thing a misconfigured curl + // silently lacks. No connection is made. + if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) { + std::println("curl_global_init failed"); + return 3; + } + const curl_version_info_data* curlInfo = curl_version_info(CURLVERSION_NOW); + const bool haveTls = curlInfo != nullptr && + (curlInfo->features & CURL_VERSION_SSL) != 0 && + curlInfo->ssl_version != nullptr; + curl_global_cleanup(); + if (!haveTls) { + std::println("libcurl reached us without TLS"); + return 4; + } + + // Still eui-neo underneath — a headless facade from the base source set. + eui::json::Document doc; + if (!doc.parse(R"({"window": "sdl2"})")) { + std::println("parse failed: {}", doc.error().message); + return 5; + } + std::string windowBackend; + if (!doc.stringAt("/window", windowBackend) || windowBackend != "sdl2") { + std::println("unexpected /window: '{}'", windowBackend); + return 6; + } + + std::println("compat.eui-neo[sdl2,network]: ok (SDL driver={}, curl {} ssl={})", + driver, curlInfo->version, curlInfo->ssl_version); + return 0; +} diff --git a/tests/examples/eui-neo-vulkan/mcpp.toml b/tests/examples/eui-neo-vulkan/mcpp.toml new file mode 100644 index 00000000..2acb7fb8 --- /dev/null +++ b/tests/examples/eui-neo-vulkan/mcpp.toml @@ -0,0 +1,12 @@ +[package] +name = "eui-neo-vulkan-tests" +version = "0.1.0" + +# `vulkan` REPLACES the default OpenGL backend rather than adding to it: the +# package resolves the exclusive choice in its own preprocessor from the +# MCPP_FEATURE_* flags. The window backend stays GLFW, unnamed. +[dependencies.compat] +eui-neo = { version = "0.5.3", features = ["vulkan"] } + +[build] +cxxflags = ["-DHAVE_EUI_VULKAN=1"] diff --git a/tests/examples/eui-neo-vulkan/tests/backend.cpp b/tests/examples/eui-neo-vulkan/tests/backend.cpp new file mode 100644 index 00000000..67e5b0ab --- /dev/null +++ b/tests/examples/eui-neo-vulkan/tests/backend.cpp @@ -0,0 +1,90 @@ +// Verify compat.eui-neo's `vulkan` feature actually swaps the render backend. +// +// Two things have to hold, and only one of them is about Vulkan: +// +// 1. The backend define really flipped. EUI_RENDER_BACKEND_VULKAN must be +// visible here and EUI_RENDER_BACKEND_OPENGL must NOT — if `default` had +// leaked through alongside the explicit feature, both would be set and +// core/render/render_backend.cpp would have silently compiled the OpenGL +// path (its dispatch is `#if OPENGL … #elif VULKAN`, so OpenGL wins). +// That failure is invisible at runtime without a GPU, so it is asserted at +// COMPILE time below. +// +// 2. The Vulkan translation units are linked. compat.vulkan's loader entry +// points must resolve from a consumer that reached them through eui-neo's +// feature-scoped dependency, not through a direct one. +// +// Everything else about the Vulkan backend needs a device, a surface and a +// window, none of which exist on a CI runner — see the design doc. +// +// HAVE_EUI_VULKAN is set by THIS project's own [target.'cfg(...)'.build] +// cxxflags: compat.vulkan has no windows build, so on windows the member takes +// the default OpenGL configuration instead and asserts THAT. +#include +#if defined(HAVE_EUI_VULKAN) +#include +#endif +import std; + +#if defined(HAVE_EUI_VULKAN) +# if !defined(EUI_RENDER_BACKEND_VULKAN) +# error "vulkan feature requested but EUI_RENDER_BACKEND_VULKAN is not defined" +# endif +# if defined(EUI_RENDER_BACKEND_OPENGL) +# error "EUI_RENDER_BACKEND_OPENGL leaked in alongside the vulkan feature" +# endif +#else +// The default (OpenGL) build publishes NO interface define — the package +// resolves it in its own preprocessor, which the consumer never sees. So the +// only thing assertable from here is the absence of the vulkan one. +# if defined(EUI_RENDER_BACKEND_VULKAN) +# error "EUI_RENDER_BACKEND_VULKAN present without the vulkan feature" +# endif +#endif + +namespace app { + +const DslAppConfig& dslAppConfig() { + static const DslAppConfig config = DslAppConfig{}.title("vulkan backend test"); + return config; +} + +void compose(eui::Ui&, const eui::Screen&) {} + +} // namespace app + +int main() { + // The loader answers this without any ICD, so it is safe on a driverless + // runner while still proving compat.vulkan came along with the feature. +#if defined(HAVE_EUI_VULKAN) + std::uint32_t apiVersion = 0; + if (vkEnumerateInstanceVersion(&apiVersion) != VK_SUCCESS) { + std::println("vkEnumerateInstanceVersion failed"); + return 1; + } +#endif + + // Still the eui-neo library underneath: assert on a headless facade that + // lives in core/platform/json.cpp, so a build that dropped the base + // sources while chasing the feature fails here. + eui::json::Document doc; + if (!doc.parse(R"({"backend": "vulkan"})")) { + std::println("parse failed: {}", doc.error().message); + return 2; + } + std::string backend; + if (!doc.stringAt("/backend", backend) || backend != "vulkan") { + std::println("unexpected /backend: '{}'", backend); + return 3; + } + +#if defined(HAVE_EUI_VULKAN) + std::println("compat.eui-neo[vulkan]: ok (backend={}, loader api {}.{}.{})", + backend, VK_VERSION_MAJOR(apiVersion), VK_VERSION_MINOR(apiVersion), + VK_VERSION_PATCH(apiVersion)); +#else + std::println("compat.eui-neo[vulkan]: skipped, default opengl build asserted instead (parsed {})", + backend); +#endif + return 0; +} diff --git a/tests/examples/eui-neo/mcpp.toml b/tests/examples/eui-neo/mcpp.toml new file mode 100644 index 00000000..3cdf7e5e --- /dev/null +++ b/tests/examples/eui-neo/mcpp.toml @@ -0,0 +1,6 @@ +[package] +name = "eui-neo-tests" +version = "0.1.0" + +[dependencies.compat] +eui-neo = "0.5.3" diff --git a/tests/examples/eui-neo/tests/header.cpp b/tests/examples/eui-neo/tests/header.cpp new file mode 100644 index 00000000..923b4e22 --- /dev/null +++ b/tests/examples/eui-neo/tests/header.cpp @@ -0,0 +1,91 @@ +// Behavioral test — verify compat.eui-neo builds, exposes its umbrella header +// to a Form A consumer, and links against real symbols from the built lib. +// +// `#include ` is the whole point of the header-compat shape. The +// umbrella pulls in eui/detail/dsl_app_impl.h, which emits app::update() / +// app::render() into THIS translation unit and leaves two symbols for the +// application to supply — app::dslAppConfig() and app::compose(). Defining +// them here is exactly what upstream's examples/*.cpp do, so this test is a +// faithful minimal consumer. +// +// The assertion itself runs on eui::json::Document: it lives in +// core/platform/json.cpp, so a package that compiled zero translation units +// fails at LINK time instead of silently passing — which is how an earlier +// revision of this descriptor shipped an empty lib behind a green CI. It is +// also the cheapest entry point that is genuinely headless (no window, no GL +// context), which matters because CI runners have no display. +#include +import std; + +namespace app { + +const DslAppConfig& dslAppConfig() { + static const DslAppConfig config = DslAppConfig{} + .title("compat.eui-neo smoke test") + .windowSize(320, 240); + return config; +} + +// Never invoked: the test asserts on the headless JSON facade instead of +// entering the render loop. It exists so the DSL app skeleton links. +void compose(eui::Ui&, const eui::Screen&) {} + +} // namespace app + +int main() { + // The umbrella header really did reach us, with the DSL app config intact. + if (app::dslAppConfig().windowWidthValue != 320) { + std::println("dslAppConfig() not wired: width={}", app::dslAppConfig().windowWidthValue); + return 1; + } + + eui::json::Document doc; + if (!doc.parse(R"({"framework": {"name": "eui-neo", "version": 3}})")) { + std::println("parse failed: {}", doc.error().message); + return 2; + } + + std::string name; + if (!doc.stringAt("/framework/name", name) || name != "eui-neo") { + std::println("unexpected /framework/name: '{}'", name); + return 3; + } + + // number() rather than signedInteger(): yyjson tags a positive literal as + // an UNSIGNED integer, so yyjson_is_sint() — and therefore + // Value::signedInteger() — is false for `3`. + double version = 0.0; + if (!doc.atPointer("/framework/version").number(version) || version != 3.0) { + std::println("unexpected /framework/version: {}", version); + return 4; + } + + // Forces core/platform/platform.cpp into the link. That TU emits + // `platform.o`, which collides with compat.glfw's src/platform.c in mcpp's + // flat per-link obj/ directory (mcpp#233/#240) and used to be dropped + // silently — the descriptor now routes it through a uniquely named + // generated stub. Referencing a symbol only that TU defines turns a + // regression into an undefined reference instead of a passing test. + // Read-only on purpose: requestFrame() would reach glfwPostEmptyEvent() + // with no window, and CI runners are headless. Both flags start clear. + if (core::platform::consumeFrameRequest() || core::platform::consumeUiUpdate()) { + std::println("platform frame flags did not start clear"); + return 5; + } + + // NEGATIVE verification for the `markdown` feature, which is NOT requested + // by this member. components/markdown.h ships two definitions of + // parseMarkdownBlocks: the md4c one, and — when EUI_HAS_MD4C is absent — a + // degenerate fallback that wraps the entire source in a single Paragraph. + // Getting the fallback here proves the feature's interface define really is + // gated off by default. tests/examples/eui-neo-markdown asserts the other + // side of the same switch. + const auto blocks = components::detail::parseMarkdownBlocks("# Heading\n\nBody text.\n"); + if (blocks.size() != 1 || blocks[0].kind != components::detail::MarkdownBlockKind::Paragraph) { + std::println("markdown feature leaked into the default build: {} block(s)", blocks.size()); + return 6; + } + + std::println("compat.eui-neo smoke test: ok (parsed {} v{}, markdown gated off)", name, version); + return 0; +} diff --git a/tests/examples/sdl2/mcpp.toml b/tests/examples/sdl2/mcpp.toml new file mode 100644 index 00000000..3c90ad29 --- /dev/null +++ b/tests/examples/sdl2/mcpp.toml @@ -0,0 +1,6 @@ +[package] +name = "sdl2-tests" +version = "0.1.0" + +[dependencies.compat] +sdl2 = "2.32.10" diff --git a/tests/examples/sdl2/tests/video.cpp b/tests/examples/sdl2/tests/video.cpp new file mode 100644 index 00000000..c2c65e1d --- /dev/null +++ b/tests/examples/sdl2/tests/video.cpp @@ -0,0 +1,95 @@ +// Behavioral test — verify compat.sdl2 builds an SDL that can actually run a +// video subsystem, create a window and pump events. +// +// SDL is the one backend in this family that CAN be exercised headlessly: +// its `dummy` video driver is a real, fully functional driver that just never +// touches a display. So unlike the OpenGL and Vulkan backends, this is not a +// link-only smoke test — SDL_Init, SDL_CreateWindow, SDL_GetWindowSize and the +// event pump all execute. +// +// The driver is selected through SDL_HINT_VIDEODRIVER rather than the +// SDL_VIDEODRIVER environment variable so the test is self-contained and does +// not depend on how CI invokes it. +// SDL_MAIN_HANDLED before : on Windows SDL_main.h does +// `#define main SDL_main` and expects the real entry point to come from +// SDL2main (src/main/windows/SDL_windows_main.c). This package does not ship +// that — a library consumer should not be handed an entry point — so the test +// takes SDL's documented alternative and keeps its own main, pairing it with +// SDL_SetMainReady() below. Without this the link fails with +// "LNK1561: entry point must be defined". +#define SDL_MAIN_HANDLED +#include +import std; + +int main() { + SDL_SetMainReady(); + SDL_SetHint(SDL_HINT_VIDEODRIVER, "dummy"); + + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) != 0) { + std::println("SDL_Init failed: {}", SDL_GetError()); + return 1; + } + + // A build whose video subsystem never registered a driver fails here, which + // is the failure mode a bad SDL_config.h produces — upstream's Linux + // fallback config (SDL_config_minimal.h) builds exactly that. + const char* driver = SDL_GetCurrentVideoDriver(); + if (driver == nullptr) { + std::println("no video driver active: {}", SDL_GetError()); + SDL_Quit(); + return 2; + } + + SDL_Window* window = SDL_CreateWindow( + "compat.sdl2", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, + 320, 240, SDL_WINDOW_HIDDEN); + if (window == nullptr) { + std::println("SDL_CreateWindow failed: {}", SDL_GetError()); + SDL_Quit(); + return 3; + } + + int width = 0; + int height = 0; + SDL_GetWindowSize(window, &width, &height); + if (width != 320 || height != 240) { + std::println("unexpected window size: {}x{}", width, height); + SDL_DestroyWindow(window); + SDL_Quit(); + return 4; + } + + // Pumping events exercises the event subsystem end to end; SDL_QUIT is + // pushed and read back rather than waited for, so nothing here can hang. + SDL_Event pushed{}; + pushed.type = SDL_QUIT; + if (SDL_PushEvent(&pushed) < 0) { + std::println("SDL_PushEvent failed: {}", SDL_GetError()); + SDL_DestroyWindow(window); + SDL_Quit(); + return 5; + } + SDL_PumpEvents(); + + bool sawQuit = false; + SDL_Event event{}; + while (SDL_PollEvent(&event) != 0) { + if (event.type == SDL_QUIT) sawQuit = true; + } + if (!sawQuit) { + std::println("pushed SDL_QUIT never came back out of the event queue"); + SDL_DestroyWindow(window); + SDL_Quit(); + return 6; + } + + SDL_version linked{}; + SDL_GetVersion(&linked); + + std::println("compat.sdl2: ok (SDL {}.{}.{}, driver={}, {}x{} window, events round-tripped)", + linked.major, linked.minor, linked.patch, driver, width, height); + + SDL_DestroyWindow(window); + SDL_Quit(); + return 0; +} diff --git a/tests/examples/vulkan/mcpp.toml b/tests/examples/vulkan/mcpp.toml new file mode 100644 index 00000000..40056d82 --- /dev/null +++ b/tests/examples/vulkan/mcpp.toml @@ -0,0 +1,12 @@ +[package] +name = "vulkan-tests" +version = "0.1.0" + +[dependencies.compat] +vulkan = "1.4.357.0" + +# All three platforms now carry compat.vulkan — linux/macOS build the loader +# from source, windows links the import library. HAVE_VULKAN_LOADER stays as the +# switch tests/loader.cpp keys off; it is unconditional again. +[build] +cxxflags = ["-DHAVE_VULKAN_LOADER=1"] diff --git a/tests/examples/vulkan/tests/loader.cpp b/tests/examples/vulkan/tests/loader.cpp new file mode 100644 index 00000000..88ab1936 --- /dev/null +++ b/tests/examples/vulkan/tests/loader.cpp @@ -0,0 +1,77 @@ +// Behavioral test — verify compat.vulkan builds a working loader and that a +// consumer can link its trampolines. +// +// Everything asserted here is answered by the LOADER itself, before any ICD is +// involved, so it is meaningful on a CI runner with no GPU and no driver. +// That constraint is sharper than it looks: the loader advertises the WSI +// surface extensions only when some ICD supports them, so `VK_KHR_surface` is +// legitimately ABSENT on a driverless machine and asserting on it would just be +// testing the runner's hardware. +// +// HAVE_VULKAN_LOADER is set by THIS project's own [target.'cfg(...)'.build] +// cxxflags, because compat.vulkan has no windows entry. A consumer that keys +// its source off a dependency's presence has to declare that itself. +#if defined(HAVE_VULKAN_LOADER) +#include +#endif +import std; + +#if !defined(HAVE_VULKAN_LOADER) +int main() { + std::println("compat.vulkan: skipped (no windows build — static loader unsupported upstream)"); + return 0; +} +#else +int main() { + // Only the loader can answer this — it reports the Vulkan version the + // loader itself implements. A package that failed to compile its + // trampolines fails at LINK here rather than passing quietly. + std::uint32_t apiVersion = 0; + if (vkEnumerateInstanceVersion(&apiVersion) != VK_SUCCESS) { + std::println("vkEnumerateInstanceVersion failed"); + return 1; + } + if (VK_VERSION_MAJOR(apiVersion) < 1) { + std::println("implausible loader api version: {}", apiVersion); + return 2; + } + + std::uint32_t extensionCount = 0; + if (vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr) != VK_SUCCESS) { + std::println("vkEnumerateInstanceExtensionProperties failed"); + return 3; + } + std::vector extensions(extensionCount); + if (extensionCount > 0 && + vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data()) != VK_SUCCESS) { + std::println("vkEnumerateInstanceExtensionProperties (fill) failed"); + return 4; + } + + // VK_EXT_debug_utils is implemented BY the loader, so it is present with or + // without a driver — unlike the surface extensions. Its absence would mean + // the loader's own extension table never made it into the lib. + const bool haveDebugUtils = std::ranges::any_of(extensions, [](const VkExtensionProperties& e) { + return std::string_view(e.extensionName) == VK_EXT_DEBUG_UTILS_EXTENSION_NAME; + }); + if (!haveDebugUtils) { + std::println("VK_EXT_debug_utils missing from {} loader extension(s)", extensionCount); + for (const auto& e : extensions) std::println(" {}", e.extensionName); + return 5; + } + + // Link-time proof that wsi.c is in the library. vkDestroySurfaceKHR is a + // WSI trampoline; referencing it makes a build without that translation + // unit fail to link, which is the check the extension list cannot give us + // on a driverless machine. + if (&vkDestroySurfaceKHR == nullptr) { + std::println("vkDestroySurfaceKHR unexpectedly null"); + return 6; + } + + std::println("compat.vulkan: ok (loader api {}.{}.{}, {} loader extension(s), WSI trampolines linked)", + VK_VERSION_MAJOR(apiVersion), VK_VERSION_MINOR(apiVersion), + VK_VERSION_PATCH(apiVersion), extensionCount); + return 0; +} +#endif