Skip to content

Commit 2047c79

Browse files
FarnaHerryspeak-agent
authored andcommitted
feat: add compat.eui-neo 0.5.3 with selectable render and window backends
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 <eui_neo.h>`. The C++23 module surface is out of scope — upstream ships no module interface units. Depends on #134 for compat.vulkan, compat.sdl2 and compat.curl; the `vulkan`, `sdl2` and `network` features resolve against them. Sourced from upstream's v0.5.3 release tag with a real sha256, mirrored to gitcode mcpp-res. Upstream vendors freetype/glfw/libpng/zlib/glad/tray/yyjson/ md4c under 3rd/; none are built here — each is already in this index at the same version, which is what the six packages from #131 were for. Backend selection is the interesting part. 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 and ignores the caller — invisible in CI, since neither backend runs headless. 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 = ... }` is the opposite and always applies, and a package-level define cannot be unset by a feature. Verified with probes on both 0.0.109 and 2026.7.29.1 — a version bump does not change it. What works is resolving the choice in the preprocessor from the -DMCPP_FEATURE_<NAME> flags mcpp already passes, via a force-included header; that also means naming an unrelated feature no longer drops the backends. Doing so exposed a second problem: mcpp routes `cflags` to C translation units and `cxxflags` to C++ ones. The first 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. Four members cover the matrix: default (opengl+glfw), markdown, vulkan, and sdl2+network. Backend selection is verified structurally rather than by trusting a green test — render_backend.o and window_backend.o are checked for which backend they actually reference, including for the member that names only an unrelated feature. Verified cold on all three platforms with mcpp 0.0.109. compat.vulkan has no windows build, so the vulkan member gates with [target.'cfg(...)'] and asserts the default OpenGL configuration there instead. Design: .agents/docs/2026-07-29-add-eui-neo-plan.md Co-authored-by: SPeak Agent <248744407+speak-agent@users.noreply.github.com>
1 parent 366beb3 commit 2047c79

12 files changed

Lines changed: 1136 additions & 0 deletions

File tree

.agents/docs/2026-07-29-add-eui-neo-plan.md

Lines changed: 404 additions & 0 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上
3838
| 运行时 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) |
3939
| 全源码直编 + 生成 config(仅缺口平台) | [`compat.curl`](pkgs/c/compat.curl.lua)(win32 用上游签入 config,unix 生成) · [`compat.sdl2`](pkgs/c/compat.sdl2.lua)(win/mac 用上游签入 config,linux 生成 + 手工开 X11) |
4040
| 补索引空缺的头文件包 | [`compat.glx-headers`](pkgs/c/compat.glx-headers.lua)(libglvnd 的 `GL/glx.h`,Khronos registry 不含,SDL 的 X11 后端必需) |
41+
| C++ 应用框架 compat(依赖复用索引内既有包) | [`compat.eui-neo`](pkgs/e/compat.eui-neo.lua)(上游 `3rd/` 自带 8 个 vendored 依赖,此处一个不编,全部改指索引内同版本 `compat.*`) |
42+
| 互斥后端(同包多后端二选一) | [`compat.eui-neo`](pkgs/e/compat.eui-neo.lua)`opengl`/`vulkan``glfw`/`sdl2`**`default` feature 在 mcpp 上不可用**(带 `defines/sources/deps` 完全不生效;带 `implies` 反而恒生效),可行解是用 `-DMCPP_FEATURE_<NAME>` 在强制包含头里做前置判定。另注意 `cflags` 只作用于 C TU,C++ 需 `cxxflags` |
4143
| 恒开的 interface define | [`compat.curl`](pkgs/c/compat.curl.lua)`CURL_STATICLIB`:`cflags` 恒开但包私有,feature `defines` 可达消费端但需点名 —— `default = { implies = … }` 无条件生效,恰好两者兼得 |
4244
| 单包多 major(形态随版本切换) | [`compat.catch2`](pkgs/c/compat.catch2.lua)(3.x 编 `src/catch2/` 出静态库;2.x 走 `single_include/` header-only) |
4345
| 外部构建系统(`install()` 从源码构建) | [`compat.openblas`](pkgs/c/compat.openblas.lua)(Make) · [`compat.openssl`](pkgs/c/compat.openssl.lua)(Perl Configure + Make,静态 libssl/libcrypto) |

mcpp.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ members = [
1919
"tests/examples/core",
2020
"tests/examples/curl",
2121
"tests/examples/eigen",
22+
"tests/examples/eui-neo",
23+
"tests/examples/eui-neo-markdown",
24+
"tests/examples/eui-neo-sdl2",
25+
"tests/examples/eui-neo-vulkan",
2226
"tests/examples/ffmpeg",
2327
"tests/examples/ffmpeg-module",
2428
"tests/examples/fmtlib.fmt",

pkgs/e/compat.eui-neo.lua

Lines changed: 347 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[package]
2+
name = "eui-neo-markdown-tests"
3+
version = "0.1.0"
4+
5+
# Long-form declaration so the `markdown` feature is requested. It pulls
6+
# compat.md4c and publishes EUI_HAS_MD4C=1 as an interface define, which is
7+
# what switches components/markdown.h from its fallback to the real parser.
8+
9+
[dependencies.compat]
10+
eui-neo = { version = "0.5.3", features = ["markdown"] }
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// POSITIVE verification for compat.eui-neo's `markdown` feature.
2+
//
3+
// components/markdown.h is header-only and compiles one of two definitions of
4+
// detail::parseMarkdownBlocks depending on EUI_HAS_MD4C. That makes it a clean
5+
// probe for something the descriptor cannot otherwise prove: the feature's
6+
// `defines` really do reach the CONSUMER's translation unit, not just the
7+
// package's own. Without propagation this member would link md4c and still
8+
// silently get the fallback parser.
9+
//
10+
// tests/examples/eui-neo asserts the negative side — same header, feature off,
11+
// degenerate single-Paragraph result.
12+
#include <eui_neo.h>
13+
import std;
14+
15+
namespace app {
16+
17+
const DslAppConfig& dslAppConfig() {
18+
static const DslAppConfig config = DslAppConfig{}.title("markdown feature test");
19+
return config;
20+
}
21+
22+
void compose(eui::Ui&, const eui::Screen&) {}
23+
24+
} // namespace app
25+
26+
int main() {
27+
#if !defined(EUI_HAS_MD4C)
28+
std::println("EUI_HAS_MD4C not defined — the feature's interface define did not propagate");
29+
return 1;
30+
#else
31+
namespace md = components::detail;
32+
33+
const auto blocks = md::parseMarkdownBlocks("# Heading\n\nBody text.\n");
34+
35+
// The real parser splits heading from paragraph; the fallback returns one
36+
// Paragraph holding the raw source.
37+
if (blocks.size() < 2) {
38+
std::println("expected >= 2 blocks from the md4c parser, got {}", blocks.size());
39+
return 2;
40+
}
41+
if (blocks[0].kind != md::MarkdownBlockKind::Heading || blocks[0].headingLevel != 1) {
42+
std::println("expected an h1 first block, got kind={} level={}",
43+
static_cast<int>(blocks[0].kind), blocks[0].headingLevel);
44+
return 3;
45+
}
46+
if (md::plainText(blocks[0].runs) != "Heading") {
47+
std::println("unexpected heading text: '{}'", md::plainText(blocks[0].runs));
48+
return 4;
49+
}
50+
51+
std::println("compat.eui-neo[markdown]: ok ({} blocks, h1 = '{}')",
52+
blocks.size(), md::plainText(blocks[0].runs));
53+
return 0;
54+
#endif
55+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[package]
2+
name = "eui-neo-sdl2-tests"
3+
version = "0.1.0"
4+
5+
# Covers the two remaining alternates in one member: the SDL2 window backend
6+
# replacing GLFW, and `network` turning core/platform/network.cpp from stubs
7+
# into a real libcurl-backed implementation. The render backend stays OpenGL,
8+
# unnamed — naming a feature no longer costs you the defaults.
9+
[dependencies.compat]
10+
eui-neo = { version = "0.5.3", features = ["sdl2", "network"] }
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Verify compat.eui-neo's `sdl2` window backend and `network` feature.
2+
//
3+
// Both are asserted at COMPILE time first, because both fail silently at
4+
// runtime otherwise: a window backend that did not switch just uses GLFW, and
5+
// `network` that did not switch leaves core/platform/network.cpp compiled as
6+
// stubs that return failure. Neither shows up without a display or a network.
7+
#include <eui_neo.h>
8+
// SDL_MAIN_HANDLED before <SDL.h>: on Windows SDL_main.h does
9+
// `#define main SDL_main` and expects the real entry point to come from
10+
// SDL2main (src/main/windows/SDL_windows_main.c). This package does not ship
11+
// that — a library consumer should not be handed an entry point — so the test
12+
// takes SDL's documented alternative and keeps its own main, pairing it with
13+
// SDL_SetMainReady() below. Without this the link fails with
14+
// "LNK1561: entry point must be defined".
15+
#define SDL_MAIN_HANDLED
16+
#include <SDL.h>
17+
#include <curl/curl.h>
18+
import std;
19+
20+
#if !defined(EUI_WINDOW_BACKEND_SDL2)
21+
#error "sdl2 feature requested but EUI_WINDOW_BACKEND_SDL2 is not defined"
22+
#endif
23+
#if !defined(EUI_HAS_CURL)
24+
#error "network feature requested but EUI_HAS_CURL is not defined"
25+
#endif
26+
27+
namespace app {
28+
29+
const DslAppConfig& dslAppConfig() {
30+
static const DslAppConfig config = DslAppConfig{}.title("sdl2 + network test");
31+
return config;
32+
}
33+
34+
void compose(eui::Ui&, const eui::Screen&) {}
35+
36+
} // namespace app
37+
38+
int main() {
39+
// SDL's dummy driver is a real driver, so unlike the GL/Vulkan backends
40+
// this actually runs: init, create, query, tear down.
41+
SDL_SetMainReady();
42+
SDL_SetHint(SDL_HINT_VIDEODRIVER, "dummy");
43+
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
44+
std::println("SDL_Init failed: {}", SDL_GetError());
45+
return 1;
46+
}
47+
SDL_Window* window = SDL_CreateWindow("eui-neo", SDL_WINDOWPOS_UNDEFINED,
48+
SDL_WINDOWPOS_UNDEFINED, 200, 100,
49+
SDL_WINDOW_HIDDEN);
50+
if (window == nullptr) {
51+
std::println("SDL_CreateWindow failed: {}", SDL_GetError());
52+
SDL_Quit();
53+
return 2;
54+
}
55+
const char* driver = SDL_GetCurrentVideoDriver();
56+
SDL_DestroyWindow(window);
57+
SDL_Quit();
58+
59+
// libcurl came in through the feature's dependency, and it has TLS — the
60+
// thing EUI-NEO's downloader needs and the thing a misconfigured curl
61+
// silently lacks. No connection is made.
62+
if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) {
63+
std::println("curl_global_init failed");
64+
return 3;
65+
}
66+
const curl_version_info_data* curlInfo = curl_version_info(CURLVERSION_NOW);
67+
const bool haveTls = curlInfo != nullptr &&
68+
(curlInfo->features & CURL_VERSION_SSL) != 0 &&
69+
curlInfo->ssl_version != nullptr;
70+
curl_global_cleanup();
71+
if (!haveTls) {
72+
std::println("libcurl reached us without TLS");
73+
return 4;
74+
}
75+
76+
// Still eui-neo underneath — a headless facade from the base source set.
77+
eui::json::Document doc;
78+
if (!doc.parse(R"({"window": "sdl2"})")) {
79+
std::println("parse failed: {}", doc.error().message);
80+
return 5;
81+
}
82+
std::string windowBackend;
83+
if (!doc.stringAt("/window", windowBackend) || windowBackend != "sdl2") {
84+
std::println("unexpected /window: '{}'", windowBackend);
85+
return 6;
86+
}
87+
88+
std::println("compat.eui-neo[sdl2,network]: ok (SDL driver={}, curl {} ssl={})",
89+
driver, curlInfo->version, curlInfo->ssl_version);
90+
return 0;
91+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# `vulkan` REPLACES the default OpenGL backend rather than adding to it: the
2+
# package resolves the exclusive choice in its own preprocessor from the
3+
# MCPP_FEATURE_* flags. The window backend stays GLFW, unnamed.
4+
#
5+
# linux + macOS only, because compat.vulkan is — see the note at the top of
6+
# pkgs/c/compat.vulkan.lua. On windows this member falls back to the default
7+
# (OpenGL) build so the member still exercises something, and tests/backend.cpp
8+
# adapts via HAVE_EUI_VULKAN.
9+
[package]
10+
name = "eui-neo-vulkan-tests"
11+
version = "0.1.0"
12+
13+
[target.'cfg(linux)'.dependencies.compat]
14+
eui-neo = { version = "0.5.3", features = ["vulkan"] }
15+
16+
[target.'cfg(linux)'.build]
17+
cxxflags = ["-DHAVE_EUI_VULKAN=1"]
18+
19+
[target.'cfg(macos)'.dependencies.compat]
20+
eui-neo = { version = "0.5.3", features = ["vulkan"] }
21+
22+
[target.'cfg(macos)'.build]
23+
cxxflags = ["-DHAVE_EUI_VULKAN=1"]
24+
25+
[target.'cfg(windows)'.dependencies.compat]
26+
eui-neo = "0.5.3"
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Verify compat.eui-neo's `vulkan` feature actually swaps the render backend.
2+
//
3+
// Two things have to hold, and only one of them is about Vulkan:
4+
//
5+
// 1. The backend define really flipped. EUI_RENDER_BACKEND_VULKAN must be
6+
// visible here and EUI_RENDER_BACKEND_OPENGL must NOT — if `default` had
7+
// leaked through alongside the explicit feature, both would be set and
8+
// core/render/render_backend.cpp would have silently compiled the OpenGL
9+
// path (its dispatch is `#if OPENGL … #elif VULKAN`, so OpenGL wins).
10+
// That failure is invisible at runtime without a GPU, so it is asserted at
11+
// COMPILE time below.
12+
//
13+
// 2. The Vulkan translation units are linked. compat.vulkan's loader entry
14+
// points must resolve from a consumer that reached them through eui-neo's
15+
// feature-scoped dependency, not through a direct one.
16+
//
17+
// Everything else about the Vulkan backend needs a device, a surface and a
18+
// window, none of which exist on a CI runner — see the design doc.
19+
//
20+
// HAVE_EUI_VULKAN is set by THIS project's own [target.'cfg(...)'.build]
21+
// cxxflags: compat.vulkan has no windows build, so on windows the member takes
22+
// the default OpenGL configuration instead and asserts THAT.
23+
#include <eui_neo.h>
24+
#if defined(HAVE_EUI_VULKAN)
25+
#include <vulkan/vulkan.h>
26+
#endif
27+
import std;
28+
29+
#if defined(HAVE_EUI_VULKAN)
30+
# if !defined(EUI_RENDER_BACKEND_VULKAN)
31+
# error "vulkan feature requested but EUI_RENDER_BACKEND_VULKAN is not defined"
32+
# endif
33+
# if defined(EUI_RENDER_BACKEND_OPENGL)
34+
# error "EUI_RENDER_BACKEND_OPENGL leaked in alongside the vulkan feature"
35+
# endif
36+
#else
37+
// The default (OpenGL) build publishes NO interface define — the package
38+
// resolves it in its own preprocessor, which the consumer never sees. So the
39+
// only thing assertable from here is the absence of the vulkan one.
40+
# if defined(EUI_RENDER_BACKEND_VULKAN)
41+
# error "EUI_RENDER_BACKEND_VULKAN present without the vulkan feature"
42+
# endif
43+
#endif
44+
45+
namespace app {
46+
47+
const DslAppConfig& dslAppConfig() {
48+
static const DslAppConfig config = DslAppConfig{}.title("vulkan backend test");
49+
return config;
50+
}
51+
52+
void compose(eui::Ui&, const eui::Screen&) {}
53+
54+
} // namespace app
55+
56+
int main() {
57+
// The loader answers this without any ICD, so it is safe on a driverless
58+
// runner while still proving compat.vulkan came along with the feature.
59+
#if defined(HAVE_EUI_VULKAN)
60+
std::uint32_t apiVersion = 0;
61+
if (vkEnumerateInstanceVersion(&apiVersion) != VK_SUCCESS) {
62+
std::println("vkEnumerateInstanceVersion failed");
63+
return 1;
64+
}
65+
#endif
66+
67+
// Still the eui-neo library underneath: assert on a headless facade that
68+
// lives in core/platform/json.cpp, so a build that dropped the base
69+
// sources while chasing the feature fails here.
70+
eui::json::Document doc;
71+
if (!doc.parse(R"({"backend": "vulkan"})")) {
72+
std::println("parse failed: {}", doc.error().message);
73+
return 2;
74+
}
75+
std::string backend;
76+
if (!doc.stringAt("/backend", backend) || backend != "vulkan") {
77+
std::println("unexpected /backend: '{}'", backend);
78+
return 3;
79+
}
80+
81+
#if defined(HAVE_EUI_VULKAN)
82+
std::println("compat.eui-neo[vulkan]: ok (backend={}, loader api {}.{}.{})",
83+
backend, VK_VERSION_MAJOR(apiVersion), VK_VERSION_MINOR(apiVersion),
84+
VK_VERSION_PATCH(apiVersion));
85+
#else
86+
std::println("compat.eui-neo[vulkan]: skipped, default opengl build asserted instead (parsed {})",
87+
backend);
88+
#endif
89+
return 0;
90+
}

0 commit comments

Comments
 (0)