Skip to content

Commit 7efb2ee

Browse files
committed
feat(xpkg): 0.0.48 — subos.env declarations
Adds `xim.libxpkg.subos` with `subos.env{}`: a package can declare an environment variable its subos must export to every process entering it. Consumed by openxlings/xlings subos slice 1. GLOBAL is the GitHub tag tarball; CN is a byte-identical copy on gitcode, verified by downloading both and comparing sha256 (GET, not HEAD -- gitcode answers HEAD with 401).
1 parent 232c1b8 commit 7efb2ee

44 files changed

Lines changed: 2222 additions & 296 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
# Design doc: add Catch2 to mcpp-index
2+
3+
Date: 2026-07-26 (revised 2026-07-27 — single package, see §"Revision")
4+
5+
## Source
6+
7+
- Repo: [catchorg/Catch2](https://github.com/catchorg/Catch2)
8+
- License: BSL-1.0 (Boost Software License 1.0)
9+
10+
Catch2 has two major versions with incompatible APIs and source layouts:
11+
12+
| Version | Source layout | Resulting shape |
13+
|---------|--------------|-----------------|
14+
| 2.13.10 | `single_include/catch2/catch.hpp` (single amalgamated header) | **header-only** |
15+
| 3.15.2 | `src/catch2/**/*.{hpp,cpp}` (individual source files) | **static library** |
16+
17+
## Revision: one package, not two
18+
19+
The first draft of this design shipped **two** packages, `compat.catch2` (v3)
20+
and `compat.catch2-v2` (v2), on the reasoning that "the v2→v3 jump changed the
21+
repo layout completely, so the two versions cannot share a single `mcpp`
22+
block."
23+
24+
The premise is right; the conclusion was not. The two layouts' globs are
25+
**disjoint**, so a single `mcpp` block can carry the **union** of both and let
26+
each version light up exactly one half:
27+
28+
| glob | 2.13.10 | 3.15.2 |
29+
|---|---|---|
30+
| `*/single_include` | exists | absent |
31+
| `*/src/catch2/**/*.cpp` | **0 matches** — v2's only `src/` file is `src/catch_with_main.cpp`, *not* under `src/catch2/` | 107 |
32+
| `catch2/catch_all.hpp` | absent | exists |
33+
34+
An `include_dirs` entry whose glob matches nothing is not an error, and a
35+
`sources` glob that matches nothing simply contributes no TUs — which is what
36+
makes the union safe. At 2.13.10 the package degenerates to header-only
37+
(`single_include` + an anchor TU); at 3.15.2 it is a 106-TU static library.
38+
39+
The last row doubles as a compile-time major discriminator:
40+
`__has_include(<catch2/catch_all.hpp>)`.
41+
42+
### Why one package is the better shape regardless
43+
44+
- **A version does not belong in `name`.** `catch2-v2` smuggles a major into
45+
the atomic name segment that SPEC-001 identity reserves for the name alone —
46+
the same anti-pattern the `compat.openssl``openssl` rename removed.
47+
- **Semver stops working across the split.** With two packages a consumer
48+
cannot write `catch2 = "^3"`, and version resolution cannot see v2 and v3 as
49+
the same library.
50+
- **Discovery.** One library should not answer to two package names.
51+
- **It is a one-way door.** Once `compat.catch2-v2` ships in a published index
52+
artifact, withdrawing it is a breaking change. The decision had to be made
53+
before merge, not after.
54+
55+
### Relationship to mcpp#290
56+
57+
[mcpp-community/mcpp#290](https://github.com/mcpp-community/mcpp/issues/290)
58+
asks for per-version `mcpp` build blocks. It is **not** a prerequisite here —
59+
the merge works on mcpp 0.0.109 today.
60+
61+
Nor is Catch2 the case #290 argues from. That issue's motivating example
62+
(llama.cpp b10069 vs b10107) shares ~95% of its build rules and differs in a
63+
few source files. Catch2 v2/v3 share essentially *nothing* but boilerplate
64+
(`language`, `import_std`, `c_standard`, `targets`, `deps`); they merge only
65+
because their globs happen not to overlap.
66+
67+
That "happen not to" is the weak point, and it is exactly what #290 would fix:
68+
the disjointness is a property of two upstream trees, not something this
69+
descriptor can enforce. When #290 lands, this collapses into explicit
70+
`["2.x"]` / `["3.x"]` blocks and the implicit assumption disappears. Until
71+
then the premise is documented in the descriptor header and must be re-checked
72+
whenever a version is added.
73+
74+
## Package shape
75+
76+
- **Sources**: anchor TU + `*/src/catch2/**/*.cpp` minus
77+
`!*/src/catch2/internal/catch_main.cpp` (107 files, 106 compiled).
78+
- **include_dirs**: `{ "*/single_include", "*/src", "mcpp_generated" }`.
79+
- **Anchor** (`mcpp_generated/catch2_anchor.c`): required at v2, where the
80+
sources glob matches nothing and a lib target still needs a TU. Same shape
81+
as compat.eigen / compat.khrplatform. Inert at v3.
82+
- **catch_user_config.hpp**: materialised via `generated_files` (upstream ships
83+
only `catch_user_config.hpp.in` and lets CMake fill it). Only the two VALUE
84+
defines are mandatory — `CATCH_CONFIG_DEFAULT_REPORTER` and
85+
`CATCH_CONFIG_CONSOLE_WIDTH`; every other entry in the `.in` is a
86+
`#cmakedefine`, i.e. absent means "use the compiler-detected default". Both
87+
are `#ifndef`-guarded so a consumer can override via `-D`. v2 never includes
88+
this file. **Re-read the upstream `.in` when bumping v3** — a newly added
89+
mandatory value-define would break silently here.
90+
91+
## The `main` feature
92+
93+
`features = ["main"]` compiles a **generated** TU that supplies a default entry
94+
point, branching on `__has_include(<catch2/catch_all.hpp>)` to pick the v3
95+
(`Catch::Session`) or v2 (`CATCH_CONFIG_MAIN`) spelling.
96+
97+
It deliberately does **not** point at upstream's
98+
`src/catch2/internal/catch_main.cpp`. That was the first draft's approach and
99+
it does not work. Measured on mcpp 0.0.109:
100+
101+
| descriptor form | default path | `features = ["main"]` |
102+
|---|---|---|
103+
| glob + `!` negation, feature → the negated path | ok | **FAIL** `undefined reference to 'main'` |
104+
| glob, no negation, feature → the globbed path | **FAIL** `multiple definition of 'main'` | ok |
105+
| glob + `!` negation + explicit re-add, feature → that path | ok | **FAIL** `undefined reference to 'main'` |
106+
| glob + `!` negation, feature → **generated TU** | ok | ok |
107+
108+
Two mechanics behind that table:
109+
110+
1. A `!` negation in `sources` is an **absolute** exclusion applied to the
111+
final source set. A feature cannot add the path back — not even if the path
112+
is also listed explicitly afterwards.
113+
2. Feature gating matches **literal** `sources` entries. A file pulled in by a
114+
glob is not gated at all, so it lands in the default build.
115+
116+
Together these mean the `compat.gtest` pattern does **not** transfer:
117+
`gtest_main.cc` is a literal `sources` entry, so listing it under a feature
118+
gates it. Under a glob the same idea silently breaks in one direction or the
119+
other. A generated TU sidesteps the conflict.
120+
121+
Cost of not reusing upstream's file: no `LeakDetector` registration (a Windows
122+
CRT-debug nicety) and no `wmain` variant (reachable only under `_UNICODE` on
123+
Windows). Consumers needing either write their own `main()`.
124+
125+
## Why this needs four workspace members
126+
127+
Features are resolved **per consuming project**, so one project cannot hold
128+
`catch2` both with and without `main` (same constraint that forces `asio-ssl`
129+
to be separate from `asio-module`). Two majors × {default, `main` feature} = 4:
130+
131+
| member | version | requests | what it would catch |
132+
|---|---|---|---|
133+
| `catch2` | 3.15.2 | — (own `main()`) | negation stops working → `multiple definition of 'main'` |
134+
| `catch2-main` | 3.15.2 | `features = ["main"]` | feature stops gating the TU in → `undefined reference to 'main'` |
135+
| `catch2-v2` | 2.13.10 | — (own `CATCH_CONFIG_MAIN`) | union bleeds across versions; `single_include` unresolved |
136+
| `catch2-v2-main` | 2.13.10 | `features = ["main"]` | the `__has_include` ELSE branch (v3 members only take THEN) |
137+
138+
The first draft declared a `main` feature on both packages and covered
139+
**neither** — which is precisely why the broken feature passed CI. Every test
140+
body keeps a real `REQUIRE`, and Catch2 prints its assertion count on exit, so
141+
a TU that compiles to nothing cannot pass quietly.
142+
143+
## Download URLs and mirrors
144+
145+
GitHub tag archive tarballs (not the individual amalgamated release assets):
146+
xpm takes one URL per version, and the tag archive is the only form that
147+
carries both layouts. Neither archive contains symlinks, so the Windows
148+
extraction path is safe.
149+
150+
| Version | GLOBAL | CN | SHA256 |
151+
|---|---|---|---|
152+
| 2.13.10 | `github.com/catchorg/Catch2/archive/refs/tags/v2.13.10.tar.gz` | `gitcode.com/mcpp-res/catch2/releases/download/2.13.10/catch2-2.13.10.tar.gz` | `d54a712b…9943` |
153+
| 3.15.2 | `github.com/catchorg/Catch2/archive/refs/tags/v3.15.2.tar.gz` | `gitcode.com/mcpp-res/catch2/releases/download/3.15.2/catch2-3.15.2.tar.gz` | `acfae120…0420` |
154+
155+
CN assets were uploaded to the `mcpp-res` gitcode org and re-downloaded: both
156+
are byte-identical to the upstream archives (same sha256, same size), so the
157+
single recorded `sha256` covers either source.
158+
159+
## Verification
160+
161+
Local, mcpp 0.0.109 (matching CI `MCPP_VERSION`), gcc@16.1.0, linux-x86_64:
162+
163+
- `mcpp xpkg parse` on the descriptor: OK
164+
- `tests/check_mirror_urls.lua`: OK
165+
- All four members via `mcpp test -p`: ok
166+
- **Negative controls** (not committed): dropping `features = ["main"]` from
167+
each `-main` member fails with `undefined reference to 'main'`, confirming
168+
the feature is what supplies the entry point rather than something else.
169+
- Both tarballs re-downloaded from GLOBAL and CN and sha256-checked.
170+
171+
CI covers linux / macOS / windows.
172+
173+
## Notes
174+
175+
- Catch2 v3 requires C++14, v2 requires C++11; both build under
176+
`language = "c++23"`.
177+
- `c_standard = "c11"` is for the anchor TU.
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Design doc: add EUI-NEO compat dependency packages
2+
3+
Date: 2026-07-28
4+
5+
## Motivation
6+
7+
[EUI-NEO](https://github.com/sudoevolve/EUI-NEO) is a declarative retained-mode C++20 UI
8+
framework. Its v0.5.3 release depends on several third-party C libraries that have no mcpp
9+
support. This batch adds 6 of those dependencies as `compat.*` packages, plus a placeholder
10+
for the 7th (`compat.eui` — the framework itself) which will follow in a separate PR once
11+
its 8-dependency chain is fully end-to-end tested.
12+
13+
## Packages (this PR)
14+
15+
| # | Package | Version | Shape | Upstream |
16+
|---|---------|---------|-------|----------|
17+
| 1 | `compat.md4c` | 0.5.3 | C source (1 TU) | https://github.com/mity/md4c |
18+
| 2 | `compat.yyjson` | 0.12.0 | C source (1 TU) | https://github.com/ibireme/yyjson |
19+
| 3 | `compat.glad` | 0.0.0-651a425 | C source (1 TU) | https://github.com/libigl/libigl-glad |
20+
| 4 | `compat.tray` | 0.0.0-8dd1358 | Header-only | https://github.com/zserge/tray |
21+
| 5 | `compat.libpng` | 1.6.43 | C source (15 TU) | https://github.com/pnggroup/libpng |
22+
| 6 | `compat.freetype` | 2.13.3 | C source (26 TU) | https://github.com/freetype/freetype |
23+
24+
## Shape decisions
25+
26+
### md4c, yyjson, glad — Single-source C libraries
27+
Each has exactly one `.c` file and one public header. Standard Form B descriptor:
28+
`language = "c++23"`, `c_standard = "c99"`, include directory + single source glob.
29+
No features, no dependencies (glad needs `-ldl` on Linux/macOS for `dlopen`).
30+
31+
### tray — Header-only
32+
Single-file header `tray.h` with `#define TRAY_IMPLEMENTATION` pattern (stb-style).
33+
Uses a generated anchor `.c` file to prevent an empty static library.
34+
No dependencies, no features.
35+
36+
### libpng — 15-source C library with generated config
37+
Upstream tarball does NOT include `pnglibconf.h` — it is generated at CMake time
38+
from `scripts/pnglibconf.h.prebuilt`. We ship the prebuilt version as a
39+
`generated_files` entry, mirroring the approach used by `compat.zlib`.
40+
41+
Depends on `compat.zlib` (already in index). Hardware optimizations disabled
42+
via `-DPNG_HARDWARE_OPTIMIZATIONS=0`.
43+
44+
### freetype — 26-source aggregate build
45+
Uses FreeType's aggregate source file pattern (one `.c` per module directory).
46+
The `ftbase.c` aggregate covers 18 base files but NOT `ftinit.c`, `ftglyph.c`,
47+
`ftbitmap.c`, `ftbbox.c`, or `ftmm.c` — those are compiled individually.
48+
49+
Critical define: `-DFT2_BUILD_LIBRARY` prevents `fterrors.h` from undefining
50+
`FT_ERR_PREFIX`, which is required for aggregate builds to link correctly.
51+
52+
Platform-specific sources: `builds/windows/{ftdebug,ftsystem}.c` on Windows,
53+
`builds/unix/ftsystem.c` on Linux/macOS.
54+
55+
Depends on `compat.libpng` (this PR) → `compat.zlib` (existing).
56+
57+
## CN mirror
58+
59+
No `mcpp-res` write access. CN URLs use placeholder gitcode.com paths with
60+
the upstream GitHub sha256. Maintainers will upload byte-identical tarballs
61+
and update the CN URLs post-merge.
62+
63+
## Features
64+
65+
None of these packages expose optional features. All compile the full library.
66+
67+
- md4c, yyjson, glad, tray: single compilation unit — nothing to gate
68+
- libpng: all 15 source files are mandatory for basic read/write
69+
- freetype: all modules are required by EUI-NEO (autofit, cff, truetype, sfnt, etc.)
70+
71+
## Verification
72+
73+
All 6 packages pass `mcpp test -p <member>` on Windows x86_64 with llvm@20.1.7:
74+
75+
```
76+
✅ compat.md4c test_md4c ... ok (1 passed, 0 failed)
77+
✅ compat.yyjson parse ... ok (1 passed, 0 failed)
78+
✅ compat.glad header ... ok (1 passed, 0 failed)
79+
✅ compat.tray header ... ok (1 passed, 0 failed)
80+
✅ compat.libpng read ... ok (1 passed, 0 failed)
81+
✅ compat.freetype init ... ok (1 passed, 0 failed)
82+
```
83+
84+
CI mcpp version: 0.0.109 (from validate.yml). Local mcpp: 2026.7.27.1.
85+
86+
## Follow-up
87+
88+
`compat.eui` (the 7th package) wraps EUI-NEO itself as a C++ module. It depends
89+
on all 6 packages here plus `compat.glfw`, `compat.opengl`, and `compat.zlib`.
90+
Will be submitted as a separate PR once the full 8-dependency chain passes
91+
end-to-end `mcpp test`.

.agents/skills/add-mcpp-index-package/SKILL.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@ description: Use when adding a new third-party library/package to the mcpp-index
1616
- `.agents/docs/2026-06-28-add-eigen-plan.md`(header-only 库及 source-gated `blas` feature)。
1717
- 既有 PR:#48(cjson 与 nlohmann.json)、#50(eigen)。
1818

19-
配套参考文档位于仓库 `docs/` 目录,供人工与 agent 共同使用,可按需查阅:
19+
配套参考文档位于仓库 `docs/` 目录(英文为默认版本,中文版在 `docs/zh/`,内容对等),供人工与 agent 共同使用,
20+
可按需查阅(下列链接指向中文版):
2021

21-
- [docs/package-types.md](../../../docs/package-types.md) —— 四种库形态的描述符模板与样例路径。
22-
- [docs/cn-mirror.md](../../../docs/cn-mirror.md) —— CN 镜像闭环,含无 `mcpp-res` 权限时的回退方案。
23-
- [docs/repository-and-schema.md](../../../docs/repository-and-schema.md) —— 仓库结构、schema、CI 行为、关键文件与注意事项。
22+
- [docs/zh/package-types.md](../../../docs/zh/package-types.md) —— 四种库形态的描述符模板与样例路径。
23+
- [docs/zh/cn-mirror.md](../../../docs/zh/cn-mirror.md) —— CN 镜像闭环,含无 `mcpp-res` 权限时的回退方案。
24+
- [docs/zh/repository-and-schema.md](../../../docs/zh/repository-and-schema.md) —— 仓库结构、schema、CI 行为、关键文件与注意事项。
2425

2526
## 适用范围
2627

@@ -48,12 +49,12 @@ description: Use when adding a new third-party library/package to the mcpp-index
4849
**纯头文件 / C 源码 / 自带 `.cppm` 模块 / 含可选组件(可实现 feature)** 中的何种形态。
4950
- 计算 `sha256sum`,并**重复计算两次以确认稳定**。GitLab 等部分归档源会重新打包,导致 sha 漂移,进而使 CI
5051
经 GLOBAL 拉取时校验失败。
51-
2. **确定形态并选择模板**:详见 [docs/package-types.md](../../../docs/package-types.md)。四类形态为:C 源码 compat、
52+
2. **确定形态并选择模板**:详见 [docs/zh/package-types.md](../../../docs/zh/package-types.md)。四类形态为:C 源码 compat、
5253
header-only、C++23 module(generated wrapper)、外部 Form-A 模块仓。
5354
3. **建立 CN 镜像**:使用 `gtc` 在 gitcode `mcpp-res` 组织下建仓并发布 release,上传**与 GLOBAL 相同的 tarball**,
5455
以保证字节一致(sha 相同)。**在不具备 `mcpp-res` 写权限时**,不应构造镜像表,而应使用纯字符串形式
5556
`url = "<GLOBAL 上游 release>"`(lint 允许此形式,CN 用户将回退至上游源),镜像由维护者后续补充。详见
56-
[docs/cn-mirror.md](../../../docs/cn-mirror.md)
57+
[docs/zh/cn-mirror.md](../../../docs/zh/cn-mirror.md)
5758
4. **编写描述符** `pkgs/<x>/<name>.lua`
5859
- 目录 `<x>` **取完整包名首字母**(`compat.eigen` 对应 `pkgs/c/`,`nlohmann.json` 对应 `pkgs/n/`),
5960
而非短名。放置错误将导致本地 path index 报 `not found in local index`
@@ -70,11 +71,11 @@ description: Use when adding a new third-party library/package to the mcpp-index
7071
编译为 no-op `main`(`#ifdef __linux__ … #else int main(){return 0;} #endif` 模式)。
7172
- 如需测试 feature,依赖采用长式声明 `name = { version = "…", features = ["…"] }`
7273
7. **本地验证**(使用与 CI 相同版本的 mcpp,详见下文“本地验证”)。必须实际执行 `mcpp test -p <member>` 并通过。
73-
8. **更新 README**:在对应分类表中新增一条记录。
74+
8. **更新 README**:在对应分类表中新增一条记录 —— `README.md`(英文,默认)与 `README.zh-CN.md`(中文)两份都要更新
7475
9. **撰写设计文档** `.agents/docs/<YYYY-MM-DD>-add-<lib>-plan.md`,记录形态判定、镜像、feature 评估、验证结论
7576
与注意事项。
7677
10. **本地 lint**:在本地复现 `validate.yml` 的 lint 检查(语法、必填字段、无前导 v、镜像表校验)。详见
77-
[docs/repository-and-schema.md](../../../docs/repository-and-schema.md)
78+
[docs/zh/repository-and-schema.md](../../../docs/zh/repository-and-schema.md)
7879
11. **提交变更**:由 `main` 切出新分支,依次 commit、push、开 PR(不应直接推送 `main`)。PR 描述应载明形态、镜像、
7980
feature 与验证结论。
8081
12. **确认 CI 通过**:`workspace (linux|macos|windows)` 的选择性成员测试应仅选中本库对应成员并通过

.github/workflows/validate.yml

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ on:
55
# mcpp.toml and index.toml carry the workspace member list, the inherited
66
# [indices] redirect and the client version floor — a change to any of them
77
# can break every member, so they gate the run like the descriptors do.
8-
paths: ["pkgs/**/*.lua", "tests/**", "README.md", "mcpp.toml", "index.toml", ".github/workflows/validate.yml"]
8+
paths: ["pkgs/**/*.lua", "tests/**", "README.md", "README.zh-CN.md", "mcpp.toml", "index.toml", ".github/workflows/validate.yml"]
99
push:
1010
branches: [main]
1111
schedule:
@@ -263,13 +263,37 @@ jobs:
263263
- uses: actions/checkout@v4
264264
with:
265265
fetch-depth: 0
266+
# The cache key is computed ONCE, here, instead of inline in the cache
267+
# step. `hashFiles()` globs the WORKING TREE, and actions/cache
268+
# re-evaluates its `key` in the post (save) step — i.e. AFTER the build,
269+
# when `tests/**` no longer matches 80-odd tracked sources but tens of
270+
# thousands of build-output files under tests/examples/*/target and
271+
# .mcpp (multi-GB; .gitignore does not apply to hashFiles). Hashing that
272+
# tree blew past the runner's 120s template-evaluation cap on windows
273+
# and failed an otherwise all-green job:
274+
# "hashFiles('pkgs/**/*.lua, tests/**, .github/workflows/validate.yml')
275+
# couldn't finish within 120 seconds"
276+
# `git ls-files -s` reads the INDEX, so it sees exactly the tracked
277+
# inputs, never build output, and reports blob SHAs git already has —
278+
# no file content is read at all. Freezing the result in the job env
279+
# also guarantees the save step keys on the same string the restore
280+
# step used, no matter what the build left behind.
281+
- name: Compute registry cache key
282+
shell: bash
283+
run: |
284+
# git hash-object rather than sha256sum/cut: git is already a hard
285+
# requirement here (checkout ran), coreutils on the windows leg is
286+
# only a Git-Bash convenience.
287+
h=$(git ls-files -s -- 'pkgs/**/*.lua' 'tests/**' '.github/workflows/validate.yml' \
288+
| git hash-object --stdin)
289+
echo "REGISTRY_CACHE_KEY=mcpp-registry-${{ runner.os }}-${{ env.MCPP_EFFECTIVE }}-$h" >> "$GITHUB_ENV"
266290
- name: Restore mcpp registry cache
267291
uses: actions/cache@v4
268292
with:
269293
# Holds toolchains AND the built compat packages (data/xpkgs), so a
270294
# repeat `mcpp test` rebuilds little.
271295
path: ~/.mcpp/registry
272-
key: mcpp-registry-${{ runner.os }}-${{ env.MCPP_EFFECTIVE }}-${{ hashFiles('pkgs/**/*.lua', 'tests/**', '.github/workflows/validate.yml') }}
296+
key: ${{ env.REGISTRY_CACHE_KEY }}
273297
restore-keys: |
274298
mcpp-registry-${{ runner.os }}-${{ env.MCPP_EFFECTIVE }}-
275299
- name: Download mcpp

0 commit comments

Comments
 (0)