Skip to content

Commit d3541a1

Browse files
SunrisepeakFarnaHerryspeak-agent
committed
feat: add compat.eui-neo 0.5.3 (header-compat shape)
EUI-NEO as a Form B compat package: ~20 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. Sourced from upstream's v0.5.3 release tag with a real sha256, not a fork's branch head. 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. Two features: `app-main` gates upstream's glfw entry point (the compat.gtest `main` pattern), `markdown` pulls compat.md4c and publishes EUI_HAS_MD4C as an interface define so the header-only component compiles in on the consumer side. core/platform/platform.cpp goes through a uniquely named generated TU. It collides with compat.glfw's src/platform.c in mcpp's flat per-link obj/ dir (mcpp#233/#240), and the collision was dropping BOTH objects: a cold 646-object link had neither core::platform::* nor _glfwSelectPlatform in it, and still went green because nothing referenced them. With the stub the link carries 648 objects and both are back. Verified cold on linux-x86_64 with mcpp 0.0.109 (the validate.yml pin): `mcpp test -p eui-neo` and `mcpp test -p eui-neo-markdown` both pass, the latter asserting the md4c parser is really active while the former asserts it is really gated off. Design: .agents/docs/2026-07-29-add-eui-neo-plan.md Supersedes the descriptor originally proposed in #133 (previous head 3f862e8). Co-authored-by: FarnaHerry <108510510+FarnaHerry@users.noreply.github.com> Co-authored-by: SPeak Agent <248744407+speak-agent@users.noreply.github.com>
1 parent ea74cf1 commit d3541a1

8 files changed

Lines changed: 604 additions & 0 deletions

File tree

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
# Design doc: add `compat.eui-neo` (EUI-NEO 0.5.3)
2+
3+
Date: 2026-07-29
4+
5+
Follow-up to `.agents/docs/2026-07-28-add-eui-compat-deps-plan.md`, which landed the six
6+
dependency packages as PR #131 and deferred the framework itself. This is that framework.
7+
8+
Supersedes the `compat.eui-neo` descriptor proposed in PR #133 — see "What PR #133 got
9+
wrong" below, which is also why this package is rebuilt from upstream rather than rebased.
10+
11+
## Motivation
12+
13+
[EUI-NEO](https://github.com/sudoevolve/EUI-NEO) is a declarative retained-mode C++17 UI
14+
framework (GLFW window backend, OpenGL render backend). This adds it in **header-compat
15+
shape only**: a consumer writes `#include <eui_neo.h>`. The C++23 module surface
16+
(`import eui;`) is explicitly out of scope — upstream ships no module interface units, so
17+
`import` would mean hand-authoring wrappers over 40+ component headers. Header compat is a
18+
prerequisite for that work, not an alternative to it.
19+
20+
## Source and version
21+
22+
| | |
23+
|---|---|
24+
| Upstream | `https://github.com/sudoevolve/EUI-NEO` |
25+
| Version | `0.5.3` (latest release, published 2026-07-27) |
26+
| Tarball | `archive/refs/tags/v0.5.3.tar.gz` |
27+
| sha256 | `6951ac330d0307c633bafe720b7888bf32785103eb16973adb4ee05ef06e64d1` (computed twice, stable) |
28+
| Wrap dir | `EUI-NEO-0.5.3/` — absorbed by the standard `*/` glob prefix, no `install()` hook |
29+
| License | Apache-2.0 |
30+
31+
## Shape decision: C++ source compat (Form B), deps reused from the index
32+
33+
Upstream vendors its whole dependency set under `3rd/` — freetype, glfw, libpng, zlib,
34+
glad, tray, yyjson, md4c, all fully checked in (no submodules). **None of them are built
35+
here.** Each already exists in this index at the same version upstream pins, and building
36+
them once for the ecosystem is the entire point of having them:
37+
38+
| upstream `3rd/` | index package | version match |
39+
|---|---|---|
40+
| `3rd/freetype` | `compat.freetype` | 2.13.3 |
41+
| `3rd/libpng-1.6.43` | `compat.libpng` | 1.6.43 |
42+
| `3rd/zlib-1.3.1` | `compat.zlib` | 1.3.2 |
43+
| `3rd/glfw` | `compat.glfw` | 3.4 |
44+
| `3rd/glad` | `compat.glad` | 0.0.0-651a425 — the exact commit `3rd/dependencies.cmake` fetches |
45+
| `3rd/tray` | `compat.tray` | 0.0.0-8dd1358 |
46+
| `3rd/yyjson-0.12.0` | `compat.yyjson` | 0.12.0 |
47+
| `3rd/md4c` | `compat.md4c` | 0.5.3 (feature-gated) |
48+
49+
`3rd/` still sits on the include path: `stb_image.h`, `nanosvg.h` and `nanosvgrast.h` are
50+
genuinely vendored single-file headers at its root, and the sources include them as
51+
`"3rd/stb_image.h"`.
52+
53+
The build recipe tracks upstream `CMakeLists.txt` v0.5.3: `CORE_SOURCES` + the OpenGL
54+
backend + glfw's `ime_bridge.c` = 20 translation units.
55+
56+
### Backend selection is a build-time constant, not a feature
57+
58+
Upstream picks the render and window backend at configure time and compiles exactly one
59+
in. Only `opengl` + `glfw` are modelled. `vulkan`, `sdl2` and `network` would need
60+
`compat.vulkan`, `compat.sdl2` and `compat.curl`, none of which exist in this index —
61+
declaring features whose deps cannot resolve only moves the failure downstream, so they
62+
are omitted rather than stubbed.
63+
64+
### One TU goes through a generated stub (mcpp#233/#240)
65+
66+
`core/platform/platform.cpp` is **not** declared directly. mcpp emits every package's
67+
objects into a single flat per-link `obj/` directory keyed by source basename, so upstream's
68+
`core/platform/platform.cpp` and `compat.glfw`'s `src/platform.c` both want `platform.o`.
69+
70+
This is not theoretical. On a cold 646-object link with the naive declaration, `platform.o`
71+
was absent entirely and **both** packages lost their TU — neither `core::platform::*` nor
72+
`_glfwSelectPlatform` reached the binary. It still linked green, because the minimal test
73+
happened to reference neither. A real EUI application would not be so lucky, and neither
74+
would any consumer of `compat.glfw` that links a sibling package with a `platform.*`.
75+
76+
The fix is the technique `compat.opencv5` established for its `modules/*/src` collisions: a
77+
uniquely named `generated_files` stub that `#include`s the real source.
78+
79+
```lua
80+
generated_files = {
81+
["mcpp_generated/eui_neo_platform_tu.cpp"] = "#include \"core/platform/platform.cpp\"\n",
82+
},
83+
sources = { …, "mcpp_generated/eui_neo_platform_tu.cpp" },
84+
```
85+
86+
Renaming one side is enough: with `platform.o` no longer contested, glfw's object survives
87+
too. `tests/examples/eui-neo` now calls `core::platform::consumeFrameRequest()` so a
88+
regression becomes an undefined reference rather than a silent pass.
89+
90+
A scan of eui-neo's 20 sources against every transitively linked package
91+
(freetype/libpng/zlib/glfw/glad/tray/yyjson/opengl + the X11 stack) found `platform` to be
92+
the **only** collision.
93+
94+
### Linux tray is deliberately a no-op
95+
96+
Upstream sets `EUI_TRAY_APPINDICATOR=1` only when pkg-config finds **both** GTK3 and
97+
libappindicator. This index carries neither, so the Linux profile sets no tray define at
98+
all and `tray_bridge.c` compiles its `EUI_TRAY_HAS_BACKEND 0` stub — which is exactly what
99+
upstream produces on a machine without those dev packages. Windows (`EUI_TRAY_WINAPI`) and
100+
macOS (`EUI_TRAY_APPKIT`, Cocoa-native) get real tray backends.
101+
102+
## Features
103+
104+
| feature | gates | default |
105+
|---|---|---|
106+
| `app-main` | `core/app/glfw_app_main.cpp` — upstream's `int main()` and render loop | off |
107+
| `markdown` | `compat.md4c` dep + `EUI_HAS_MD4C=1` interface define | off |
108+
109+
**`app-main`** is a sources-only gate, the direct analogue of `compat.gtest`'s `main`
110+
(gtest_main.cc). CMake adds this file per-application (`EUI_APP_MAIN_SOURCE`), never to
111+
the library, for the same reason it is opt-in here: a consumer with its own `main()` must
112+
not be handed a second one. A real EUI application enables it and supplies only
113+
`app::dslAppConfig()` and `app::compose()`.
114+
115+
**`markdown`** is the more interesting one. `components/markdown.h` is header-only and
116+
compiles one of *two* definitions of `detail::parseMarkdownBlocks` depending on
117+
`EUI_HAS_MD4C` — the md4c parser, or a fallback that wraps the entire source in one
118+
Paragraph. The library itself gains no translation unit either way, so the whole feature
119+
lives on the consumer side. That is why the define goes in `defines` (an INTERFACE define,
120+
propagated to the consumer's TUs) rather than `cflags` (package-private): with `cflags`,
121+
md4c would link and the component would still silently compile out.
122+
123+
Note the skill doc's "features 仅能门控 sources" reflects mcpp 0.0.68. On the pinned
124+
0.0.109, `defines` / `deps` / `implies` / `requires` / `provides` are all accepted — see
125+
`compat.eigen`, `chriskohlhoff.asio`, `compat.spdlog`.
126+
127+
## Consumer contract (worth knowing before using this package)
128+
129+
`eui_neo.h` pulls in `eui/detail/dsl_app_impl.h`, which emits `app::update()` /
130+
`app::render()` into the *consumer's* translation unit and leaves two symbols for the
131+
application to define:
132+
133+
```cpp
134+
namespace app {
135+
const DslAppConfig& dslAppConfig();
136+
void compose(eui::Ui& ui, const eui::Screen& screen);
137+
}
138+
```
139+
140+
Omitting them is a link error, not a compile error. This mirrors upstream's
141+
`examples/*.cpp`, all of which define exactly these two. Both test members do the same.
142+
143+
## Verification
144+
145+
Local, mcpp **0.0.109** (matching `validate.yml` `env.MCPP_VERSION`), linux-x86_64, gcc 16.1.0.
146+
147+
Both workspace members pass:
148+
149+
```
150+
$ mcpp test -p eui-neo
151+
compat.eui-neo smoke test: ok (parsed eui-neo v3, markdown gated off)
152+
header ... ok
153+
test result ok. 1 passed; 0 failed
154+
155+
$ mcpp test -p eui-neo-markdown
156+
compat.eui-neo[markdown]: ok (2 blocks, h1 = 'Heading')
157+
markdown ... ok
158+
test result ok. 1 passed; 0 failed
159+
```
160+
161+
### The library really is built
162+
163+
PR #133's descriptor passed CI while compiling **zero** translation units (see below), so
164+
this is checked against the objects rather than inferred from a green test. Per-package
165+
counts from the build cache:
166+
167+
```
168+
compat.eui-neo@0.5.3 20 objs <- exactly the 20 declared sources
169+
compat.freetype@2.13.3 29 objs
170+
compat.glfw@3.4 23 objs
171+
compat.libpng@1.6.43 15 objs
172+
compat.zlib@1.3.2 15 objs
173+
compat.x11@1.8.13 406 objs
174+
compat.yyjson@0.12.0 1 obj
175+
compat.glad@… 1 obj
176+
compat.tray@… 1 obj
177+
```
178+
179+
The default member's assertions run on `eui::json::Document` (`core/platform/json.cpp`)
180+
and `core::platform::consumeFrameRequest()` (`core/platform/platform.cpp`) — an empty or
181+
partial library fails at **link** time instead of silently passing.
182+
183+
### The mcpp#233 collision fix, measured
184+
185+
Cold link of `tests/examples/eui-neo`, before vs after routing platform.cpp through the
186+
generated stub:
187+
188+
| | objects in the link | `core::platform::*` | `_glfwSelectPlatform` |
189+
|---|---|---|---|
190+
| before | 646 | absent | absent |
191+
| after | 648 | `eui_neo_platform_tu.o` | `platform.o` |
192+
193+
Two objects recovered: eui-neo's TU, and `compat.glfw`'s `platform.o` that the contested
194+
name had been taking down with it.
195+
196+
### Feature verification (both directions)
197+
198+
- **negative** — `tests/examples/eui-neo` does not request `markdown`, and asserts
199+
`parseMarkdownBlocks("# Heading\n\nBody text.\n")` returns the degenerate single
200+
Paragraph. Feature on by accident ⇒ this member fails.
201+
- **positive** — `tests/examples/eui-neo-markdown` requests it long-form and asserts an h1
202+
block with text `Heading` plus ≥2 blocks. Interface define failing to propagate ⇒ this
203+
member fails (it `#if !defined(EUI_HAS_MD4C)`s to an explicit failure first).
204+
205+
## CN mirror
206+
207+
No `mcpp-res` write access, so per `docs/cn-mirror.md` the fallback applies: plain-string
208+
upstream `url`, no `{ GLOBAL, CN }` table. CN users fall back to the GitHub source.
209+
Maintainers can add the mirror later without touching `sha256`.
210+
211+
## What PR #133 got wrong
212+
213+
Recorded because the failure mode is subtle and CI did not catch it.
214+
215+
1. **The package compiled nothing, and CI was green.** Its `install()` hook guessed the
216+
tarball's wrap directory as `main` / `EUI-NEO-<version>` / `EUI-NEO-main`; the actual
217+
name was `EUI-NEO-M-main` (it pointed at a personal fork's branch archive). All three
218+
guesses missed, `os.tryrm(install_dir())` then removed the install dir, `os.mv` failed,
219+
and the hook `return true`d anyway — so mcpp recorded a successful install over a
220+
directory that did not exist. Every source glob then matched zero files. The smoke test
221+
was `import std; println(...)` referencing no EUI symbol, so even the link succeeded.
222+
Measured: `0 objs` for `compat.eui-neo`, against 29/15/23 for freetype/libpng/glfw.
223+
2. **The `install()` hook should not exist.** House style (`docs/package-types.md`,
224+
`compat.md4c`, `compat.libpng`) absorbs the wrap layer with a `*/` glob prefix. PR #133
225+
removed those prefixes to compensate for its own hook.
226+
3. **"Form B → Form A include propagation not supported" was a misdiagnosis.**
227+
`tests/examples/freetype` does `#include <ft2build.h>` against a Form B package and
228+
passes on main. Headers were unreachable because the verdir did not exist.
229+
4. **`sha256 = ""`** disabled integrity verification on all three platforms, against a
230+
**moving branch head** (`refs/heads/main`) of a personal fork, while `repo` pointed at
231+
upstream. Upstream tags `v0.5.3`; this descriptor pins it with a real digest.
232+
5. **Dead paths.** `3rd/yyjson-0.12.0/src/yyjson.c` and `3rd/tray` do not exist in that
233+
fork's archive, and `compat.yyjson` / `compat.tray` / `compat.glad` — three of the six
234+
packages #131 added *for this framework* — were not declared as deps at all.
235+
6. **Unconditional `-DEUI_TRAY_APPINDICATOR=1` on Linux**, which upstream only sets when
236+
GTK3 + libappindicator are present. It would have required GTK3 headers this index does
237+
not carry.
238+
7. **Features with no resolvable deps** (`vulkan`, `sdl2`, `network`) — defines only, no
239+
packages behind them.
240+
241+
## Follow-up
242+
243+
- CN mirror once `mcpp-res` write access is available.
244+
- `compat.vulkan` / `compat.sdl2` / `compat.curl` would unlock the corresponding backends.
245+
- The C++23 module layer (`import eui;`) remains open, and now has a working header-compat
246+
base to build on.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上
3535
| 原生模块库(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 全源码构建同在包内,索引侧只留本描述符) |
3636
| C 源码 compat(含 `features`) | [`compat.cjson`](pkgs/c/compat.cjson.lua) · [`compat.zlib`](pkgs/c/compat.zlib.lua) |
3737
| header-only(含 `features`) | [`compat.eigen`](pkgs/c/compat.eigen.lua) |
38+
| C++ 应用框架 compat(依赖复用索引内既有包) | [`compat.eui-neo`](pkgs/e/compat.eui-neo.lua)(上游 `3rd/` 自带 8 个 vendored 依赖,此处一个不编,全部改指索引内同版本 `compat.*`;`app-main` 门控上游 `main()`,`markdown` 经 interface define 打开消费侧 header) |
3839
| 单包多 major(形态随版本切换) | [`compat.catch2`](pkgs/c/compat.catch2.lua)(3.x 编 `src/catch2/` 出静态库;2.x 走 `single_include/` header-only) |
3940
| 外部构建系统(`install()` 从源码构建) | [`compat.openblas`](pkgs/c/compat.openblas.lua)(Make) · [`compat.openssl`](pkgs/c/compat.openssl.lua)(Perl Configure + Make,静态 libssl/libcrypto) |
4041
| 全源码直编(config 快照 + 源列表,零外部构建系统) | [`compat.ffmpeg`](pkgs/c/compat.ffmpeg.lua)(2281 TU 含 NASM 汇编,28 个目录 glob 声明) |

mcpp.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ members = [
1818
"tests/examples/cjson",
1919
"tests/examples/core",
2020
"tests/examples/eigen",
21+
"tests/examples/eui-neo",
22+
"tests/examples/eui-neo-markdown",
2123
"tests/examples/ffmpeg",
2224
"tests/examples/ffmpeg-module",
2325
"tests/examples/fmtlib.fmt",

0 commit comments

Comments
 (0)