From 6da8452307c2bf7e5632399e119fe20c20292804 Mon Sep 17 00:00:00 2001 From: SPeak Date: Wed, 29 Jul 2026 06:40:12 +0800 Subject: [PATCH 1/2] feat: add compat.vulkan, sdl2, curl, glx-headers and vulkan-headers (#134) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five source-built packages behind the alternate GUI and network backends: the Khronos Vulkan loader + headers, libcurl (OpenSSL/Schannel), SDL2, and libglvnd's GL/glx.h. All build from plain source lists — no CMake, no autotools, no install() hook. Verified cold on all three platforms; compat.vulkan defers windows (upstream supports a static loader only on macOS). Design: .agents/docs/2026-07-29-add-gui-backend-packages-plan.md Co-authored-by: SPeak Agent <248744407+speak-agent@users.noreply.github.com> --- ...026-07-29-add-gui-backend-packages-plan.md | 193 ++++ README.md | 4 + mcpp.toml | 3 + pkgs/c/compat.curl.lua | 330 +++++++ pkgs/c/compat.glx-headers.lua | 80 ++ pkgs/c/compat.sdl2.lua | 923 ++++++++++++++++++ pkgs/c/compat.vulkan-headers.lua | 68 ++ pkgs/c/compat.vulkan.lua | 189 ++++ tests/examples/curl/mcpp.toml | 10 + tests/examples/curl/tests/transfer.cpp | 89 ++ tests/examples/sdl2/mcpp.toml | 6 + tests/examples/sdl2/tests/video.cpp | 95 ++ tests/examples/vulkan/mcpp.toml | 19 + tests/examples/vulkan/tests/loader.cpp | 77 ++ 14 files changed, 2086 insertions(+) create mode 100644 .agents/docs/2026-07-29-add-gui-backend-packages-plan.md create mode 100644 pkgs/c/compat.curl.lua create mode 100644 pkgs/c/compat.glx-headers.lua create mode 100644 pkgs/c/compat.sdl2.lua create mode 100644 pkgs/c/compat.vulkan-headers.lua create mode 100644 pkgs/c/compat.vulkan.lua create mode 100644 tests/examples/curl/mcpp.toml create mode 100644 tests/examples/curl/tests/transfer.cpp create mode 100644 tests/examples/sdl2/mcpp.toml create mode 100644 tests/examples/sdl2/tests/video.cpp create mode 100644 tests/examples/vulkan/mcpp.toml create mode 100644 tests/examples/vulkan/tests/loader.cpp 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..434a1f77 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,10 @@ 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 后端必需) | +| 恒开的 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..ad89ee51 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -17,6 +17,7 @@ members = [ "tests/examples/catch2-v2-main", "tests/examples/cjson", "tests/examples/core", + "tests/examples/curl", "tests/examples/eigen", "tests/examples/ffmpeg", "tests/examples/ffmpeg-module", @@ -32,6 +33,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 +41,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.lua b/pkgs/c/compat.vulkan.lua new file mode 100644 index 00000000..3022ab67 --- /dev/null +++ b/pkgs/c/compat.vulkan.lua @@ -0,0 +1,189 @@ +-- 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 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 and carrying the warning that it "will only work on MacOS and +-- is not supported" elsewhere. Built anyway, the Windows loader links but 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 — Chromium ships one. Rather than carry a package that +-- crashes, this follows `compat.openssl` and declares no windows xpm entry; +-- consumers gate with `[target.'cfg(...)']`. +-- +-- 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 deferred, see the note at the top of this file. + }, + + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c11", + + 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/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", + }, + + 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. + 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", + }, + -- 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" }, + }, + }, + + -- No `windows` block: see the deferral note at the top. + }, +} 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/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..5f96ae59 --- /dev/null +++ b/tests/examples/vulkan/mcpp.toml @@ -0,0 +1,19 @@ +# linux + macOS only: compat.vulkan has no windows xpm entry (a statically +# linked Vulkan loader is not supported there upstream — see the note at the top +# of pkgs/c/compat.vulkan.lua). On windows this member carries no dependency and +# tests/loader.cpp compiles to a no-op main(). +[package] +name = "vulkan-tests" +version = "0.1.0" + +[target.'cfg(linux)'.dependencies.compat] +vulkan = "1.4.357.0" + +[target.'cfg(linux)'.build] +cxxflags = ["-DHAVE_VULKAN_LOADER=1"] + +[target.'cfg(macos)'.dependencies.compat] +vulkan = "1.4.357.0" + +[target.'cfg(macos)'.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 From f529d54ef386ee2b8f02424a5860e9c81dae5445 Mon Sep 17 00:00:00 2001 From: FarnaHerry <108510510+FarnaHerry@users.noreply.github.com> Date: Wed, 29 Jul 2026 06:27:45 +0800 Subject: [PATCH 2/2] feat: add compat.eui-neo 0.5.3 with selectable render and window backends MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit EUI-NEO as a Form B compat package: the core TUs compiled into one lib, public headers exposed via include_dirs, so consumers write `#include `. The C++23 module surface is out of scope — upstream ships no module interface units. Builds on #134 (compat.vulkan / sdl2 / curl), and carries two fixes to that half that only a machine with a GPU could have found — see below. Backend selection. Upstream compiles exactly one render backend and one window backend, dispatching on `#if OPENGL ... #elif VULKAN` and `#if SDL2` / else-GLFW, so defining both halves of either pair silently picks the first. mcpp features are additive with no `default-features = false` (mcpp#242), and all three obvious encodings fail silently: a `default` feature carrying defines/sources/deps is inert, `default = { implies = ... }` always applies, and a package-level define cannot be unset. Verified with probes on 0.0.109 and 2026.7.29.1. What works is resolving the choice in the preprocessor from the -DMCPP_FEATURE_ flags mcpp already passes, via a force-included header. That exposed a second problem: mcpp routes `cflags` to C translation units and `cxxflags` to C++ ones. The first revision carried only `cflags = { "-DEUI_RENDER_BACKEND_OPENGL=1" }`, so render_backend.cpp never saw it — the package built, linked, passed its tests, and had no render backend. REAL GUI VERIFICATION, on a workstation with an X display and an RTX 4080: all four backend combinations open a window, create the backend and present three frames, with no environment variables set. Getting the Vulkan half there needed two changes, both invisible to a headless test: * compat.vulkan-runtime (new) — the loader found every host ICD manifest and then failed to dlopen any driver, because an mcpp binary runs under mcpp's own glibc and a bare-soname dlopen never searches the host path. This is the counterpart of compat.glx-runtime, which is why OpenGL already worked: a symlink farm plus runtime.library_dirs, no vendored driver. Instance extensions 4 -> 22. Versioned sonames only — library_dirs lands on the link line too, so a bare libxcb.so there shadows compat.xcb. * compat.vulkan is now kind="shared" with soname libvulkan.so.1. SDL2's SDL_CreateWindow(SDL_WINDOW_VULKAN) dlopens libvulkan.so.1 and resolves surface creation through whatever it finds; against a static loader the application ends up with two, and createSurface gets an instance the second one never saw. Shared, the application, GLFW (via glfwInitVulkanLoader) and SDL all converge on one object — which is what the loader is designed to be. Also: glfwInitVulkanLoader must be called before glfwInit. Upstream's glfw_app_main.cpp gets this right; a consumer writing its own entry point must too. Verified cold on all three platforms with mcpp 0.0.109, plus the local GUI harness on Linux. Design: .agents/docs/2026-07-29-add-eui-neo-plan.md Co-authored-by: SPeak Agent <248744407+speak-agent@users.noreply.github.com> --- .agents/docs/2026-07-29-add-eui-neo-plan.md | 482 ++++++++++++++++++ README.md | 3 + mcpp.toml | 4 + pkgs/c/compat.vulkan-runtime.lua | 199 ++++++++ pkgs/c/compat.vulkan.lua | 95 +++- pkgs/e/compat.eui-neo.lua | 347 +++++++++++++ tests/examples/eui-neo-markdown/mcpp.toml | 10 + .../eui-neo-markdown/tests/markdown.cpp | 55 ++ tests/examples/eui-neo-sdl2/mcpp.toml | 10 + .../examples/eui-neo-sdl2/tests/backends.cpp | 91 ++++ tests/examples/eui-neo-vulkan/mcpp.toml | 12 + .../examples/eui-neo-vulkan/tests/backend.cpp | 90 ++++ tests/examples/eui-neo/mcpp.toml | 6 + tests/examples/eui-neo/tests/header.cpp | 91 ++++ tests/examples/vulkan/mcpp.toml | 17 +- 15 files changed, 1489 insertions(+), 23 deletions(-) create mode 100644 .agents/docs/2026-07-29-add-eui-neo-plan.md create mode 100644 pkgs/c/compat.vulkan-runtime.lua create mode 100644 pkgs/e/compat.eui-neo.lua create mode 100644 tests/examples/eui-neo-markdown/mcpp.toml create mode 100644 tests/examples/eui-neo-markdown/tests/markdown.cpp create mode 100644 tests/examples/eui-neo-sdl2/mcpp.toml create mode 100644 tests/examples/eui-neo-sdl2/tests/backends.cpp create mode 100644 tests/examples/eui-neo-vulkan/mcpp.toml create mode 100644 tests/examples/eui-neo-vulkan/tests/backend.cpp create mode 100644 tests/examples/eui-neo/mcpp.toml create mode 100644 tests/examples/eui-neo/tests/header.cpp 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/README.md b/README.md index 434a1f77..cae6c1d4 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,9 @@ mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上 | 运行时 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) | diff --git a/mcpp.toml b/mcpp.toml index ad89ee51..e762991a 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -19,6 +19,10 @@ members = [ "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", 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 index 3022ab67..ed8996cc 100644 --- a/pkgs/c/compat.vulkan.lua +++ b/pkgs/c/compat.vulkan.lua @@ -20,15 +20,32 @@ -- entry points (ones this loader version has never heard of) get no -- trampoline, which no consumer in this index uses. -- --- 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 and carrying the warning that it "will only work on MacOS and --- is not supported" elsewhere. Built anyway, the Windows loader links but 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 — Chromium ships one. Rather than carry a package that --- crashes, this follows `compat.openssl` and declares no windows xpm entry; --- consumers gate with `[target.'cfg(...)']`. +-- 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 @@ -65,7 +82,15 @@ package = { sha256 = "54f2537df22313768da0317dda2abdaaab7711b4081c48c869a79db343d0ae70", }, }, - -- windows deferred, see the note at the top of this file. + 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 = { @@ -73,6 +98,8 @@ package = { 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 @@ -83,6 +110,8 @@ package = { -- 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 @@ -111,6 +140,22 @@ package = { "*/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" }, @@ -125,6 +170,12 @@ package = { -- 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", @@ -144,6 +195,11 @@ package = { ["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" }, @@ -184,6 +240,23 @@ package = { }, }, - -- No `windows` block: see the deferral note at the top. + 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/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/vulkan/mcpp.toml b/tests/examples/vulkan/mcpp.toml index 5f96ae59..40056d82 100644 --- a/tests/examples/vulkan/mcpp.toml +++ b/tests/examples/vulkan/mcpp.toml @@ -1,19 +1,12 @@ -# linux + macOS only: compat.vulkan has no windows xpm entry (a statically -# linked Vulkan loader is not supported there upstream — see the note at the top -# of pkgs/c/compat.vulkan.lua). On windows this member carries no dependency and -# tests/loader.cpp compiles to a no-op main(). [package] name = "vulkan-tests" version = "0.1.0" -[target.'cfg(linux)'.dependencies.compat] +[dependencies.compat] vulkan = "1.4.357.0" -[target.'cfg(linux)'.build] -cxxflags = ["-DHAVE_VULKAN_LOADER=1"] - -[target.'cfg(macos)'.dependencies.compat] -vulkan = "1.4.357.0" - -[target.'cfg(macos)'.build] +# 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"]