Skip to content

Commit af34f6e

Browse files
committed
docs(skills): add add-mcpp-index-package agent skill (SOP)
Codifies the full process used for cjson/nlohmann/eigen into a reusable skill under .agents/skills/add-mcpp-index-package/: - SKILL.md — 12-step SOP, the sources-only feature gate (+ mandatory negative test), version-matched local verification, red-flags/common mistakes. - package-types.md — descriptor templates for the four shapes (C-source compat / header-only / C++23 module via generated wrapper / external Form-A module repo), each with real sample paths. - cn-mirror.md — gtc/gitcode mcpp-res mirror closed loop + every known gotcha. - references.md — repo layout, descriptor schema cheat-sheet, validate.yml CI behavior, one-shot local lint, real worked examples (#48 / #50).
1 parent a240c7f commit af34f6e

4 files changed

Lines changed: 388 additions & 0 deletions

File tree

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
---
2+
name: add-mcpp-index-package
3+
description: Use when adding a new third-party library/package to the mcpp-index repo — writing a pkgs/*/*.lua descriptor, setting up a GitCode CN mirror via gtc, adding a minimal example, and opening a green PR. Covers the four package shapes (C-source compat / header-only / C++23 module / external Form-A module repo), the GLOBAL+CN mirror table, lint rules, the feature (sources-only) gate, and local + CI verification.
4+
---
5+
6+
# 给 mcpp-index 新增一个库(SOP)
7+
8+
把一个上游库收录进 [`mcpp-index`](https://github.com/mcpplibs/mcpp-index) 的标准流程。产出 = 一个
9+
`pkgs/<x>/<name>.lua` 描述符 + GitCode CN 镜像 + `tests/examples/` 最小工程 + README 一行 + 设计文档,最终
10+
**本地实测绿 → 开 PR → CI 全绿 → 合并**(合并后 `publish-artifact.yml` 自动重发 index artifact)。
11+
12+
参考真实落地案例(逐字可抄):
13+
- `.agents/docs/2026-06-27-add-cjson-and-nlohmann-json-plan.md`(C 源码 compat + 模块库)
14+
- `.agents/docs/2026-06-28-add-eigen-plan.md`(header-only + source-gated `blas` feature)
15+
- 既有 PR:#48(cjson + nlohmann.json),#50(eigen)。
16+
17+
> 配套参考文件(按需读):
18+
> - [package-types.md](package-types.md) —— 四种库形态的描述符模板 + 真实样例路径
19+
> - [cn-mirror.md](cn-mirror.md) —— gtc / gitcode CN 镜像闭环
20+
> - [references.md](references.md) —— 仓库结构、schema、CI、关键文件、踩坑清单
21+
22+
## 何时用 / 不用
23+
- ****:把一个新的第三方库(或新版本)加进 mcpp-index。
24+
- **不用**:改 mcpp 本体、改 xlings 引擎、纯文档。那些不在本仓。
25+
26+
## 总流程(12 步)
27+
28+
按顺序做;每步的细节进对应参考文件。建议用 todo 跟踪。
29+
30+
1. **调研上游**(最关键,决定形态)
31+
- 上游仓库、**最新 tag/版本**(`git ls-remote --tags <repo>``sort -V | tail`;注意大版本跳跃,如 Eigen 3.4 → 5.x)。
32+
- License、源码布局:下 tarball,`tar -tzf` 看顶层 wrap 目录 + 子目录;判断是
33+
**纯头 / C 源码 / 自带 `.cppm` 模块 / 有可选组件(可做 feature)**
34+
-`sha256sum`,并**复算两次确认稳定**(GitLab/部分归档会重新打包导致 sha 漂移 → CI 用 GLOBAL 拉取会校验失败)。
35+
2. **定形态 → 选模板**:见 [package-types.md](package-types.md)。四类:C-source compat / header-only / C++23 module(generated wrapper)/ 外部 Form-A 模块仓。
36+
3. **建 CN 镜像**:`gtc` 在 gitcode `mcpp-res` org 下建仓 + 发 release,上传**与 GLOBAL 同一个 tarball**(保证 byte-identical → 同 sha)。见 [cn-mirror.md](cn-mirror.md)
37+
4. **写描述符** `pkgs/<x>/<name>.lua`
38+
- ⚠️ **目录 `<x>` = 完整包名首字母**(`compat.eigen``pkgs/c/`,`nlohmann.json``pkgs/n/`),**不是短名**。放错本地 path index 会 `not found in local index`
39+
- 三平台 `xpm`(linux/macosx/windows),每个版本 `url = { GLOBAL=…, CN=… }` + `sha256`
40+
- 版本号写**裸版本**(`"1.2.3"`,不带前导 `v`);下载 URL 里可保留上游 `…/v1.2.3.tar.gz`
41+
5. **识别可门控的可选组件 → feature**(见下"feature 机制")。只能门控 **sources**
42+
6. **最小工程** `tests/examples/<short>/`(`short` = 包名去 `compat.`/`mcpplibs.` 前缀)
43+
- `mcpp.toml`(`[indices].compat = { path = "../../.." }` 指回仓根)+ 一个 `src/main.*`,做一个**会失败的真断言**(`return ok?0:1`)。
44+
- 若要测 feature:依赖写长式 `name = { version = "…", features = ["…"] }`
45+
7. **本地实测**(用与 CI 同版本的 mcpp):见下"本地验证"。必须真跑过 `mcpp build && mcpp run` 绿。
46+
8. **README** 在对应表(mcpplibs 模块库 / 独立模块库 / 第三方 C/C++ 库)加一行。
47+
9. **设计文档** `.agents/docs/<YYYY-MM-DD>-add-<lib>-plan.md`,记录形态判断、镜像、feature 评估、实测结论、踩坑。
48+
10. **本地 lint**:模拟 `validate.yml` 的 lint(语法 / 必填字段 / 无前导 v / mirror 检查)。见 [references.md](references.md)
49+
11. **分支 → commit → push → PR**(从 `main` 切新分支;别直接推 main)。PR 描述写清形态、镜像、feature、实测。
50+
12. **盯 CI 绿**:`detect` 应只选中本库的 example(`smoke-full-linux`/`portable` 显示 `skipping`),`smoke-examples (<short>)` 绿;`mirror-cn-reachable` 覆盖新 CN url。合并交维护者。
51+
52+
## feature 机制(务必读准)
53+
54+
mcpp **0.0.68** 的包描述符 `features`**只能门控 `sources`**(其它子字段被 skip;源码核实见 `manifest.cppm` 的 features 解析)。
55+
56+
```lua
57+
mcpp = {
58+
sources = { "*/core.c" }, -- 默认总编
59+
features = {
60+
["extra"] = { sources = { "*/extra.c" } }, -- 默认排除;features=["extra"] 时编入同一个 lib 目标
61+
},
62+
}
63+
```
64+
- 消费侧:`dep = { version = "…", features = ["extra"] }`
65+
- 真实例:`compat.gtest``main`(gtest_main.cc)、`compat.cjson``utils`(cJSON_Utils.c)、`compat.eigen``blas`(`*/blas/*.cpp` + `*/blas/f2c/*.c`)。
66+
- **判定"可不可以做 feature"**:这个可选组件是不是**额外的可编译源码**?是 → 能门控。
67+
纯头(如 Eigen `unsupported/`,与核共享 include 根,藏不住)→ 不能。
68+
编译 **define**(如 `EIGEN_MPL2_ONLY``EIGEN_USE_BLAS`)→ 当前 feature 表带不了 → 不能(留注释,待 mcpp 支持 define/cflags)。
69+
- **glob**:支持 `*`(段内)与 `**`(跨段),所以 `*/blas/*.cpp``*/foo/**/*.c` 都行。
70+
- **必须做负向验证**:不带 feature 时那个符号/源应**确实缺失**(链接 `undefined reference` 或编译找不到),证明门控真生效——不是默认就编进去了。
71+
72+
## 本地验证(与 CI 同版本)
73+
74+
CI 的 mcpp 版本 = `.github/workflows/validate.yml``env.MCPP_VERSION`**用同一版本本地跑**,别用恰好装着的旧版。
75+
76+
```bash
77+
MV=$(grep -oP 'MCPP_VERSION:\s*"\K[0-9.]+' .github/workflows/validate.yml)
78+
curl -L -fsS -o mcpp.tgz "https://github.com/mcpp-community/mcpp/releases/download/v$MV/mcpp-$MV-linux-x86_64.tar.gz"
79+
tar -xzf mcpp.tgz
80+
root="$PWD/mcpp-$MV-linux-x86_64"
81+
mkdir -p ~/.mcpp/registry && cp -a "$root/registry/." ~/.mcpp/registry/
82+
export MCPP="$root/bin/mcpp"
83+
export MCPP_VENDORED_XLINGS="$root/registry/bin/xlings"
84+
export MCPP_INDEX_MIRROR=GLOBAL # CI example 走 GLOBAL;CN 由 mirror-cn-reachable 单独兜
85+
MCPP="$MCPP" bash tests/run_example.sh <short>
86+
```
87+
- 输出末尾要有你的断言行 + `OK: <short>`
88+
- run_example.sh 会 `rm -rf target .mcpp` 从干净态走真实管线(fetch→generate→compile→link→run)。
89+
- 想直接看头/源:解包在 `tests/examples/<short>/.mcpp/.xlings/data/xpkgs/<idx>-x-<name>/<ver>/<wrap>/`
90+
91+
## 红线 / 常见错误
92+
93+
- ❌ 把 `compat.foo.lua` 放进 `pkgs/f/`。✅ 放 `pkgs/c/`(完整名首字母)。
94+
- ❌ 版本写 `"v1.2.3"`。✅ 裸 `"1.2.3"`(lint 会拦前导 v)。
95+
- ❌ CN 镜像传了"加工过"的包(≠ GLOBAL)。✅ 传 GLOBAL 同一个 tarball,sha 一致;否则破坏 GLOBAL/CN 一致性。
96+
- ❌ 只 `mcpp build``run`,或例子里没有真断言。✅ 真跑 + `return ok?0:1`
97+
- ❌ 宣称"做了 feature"却没负向验证。✅ 证明默认确实不含。
98+
- ❌ 没对齐 CI 的 mcpp 版本,本地绿 CI 红。✅ 读 `MCPP_VERSION`
99+
- ❌ 直接 push `main`。✅ 切分支开 PR。
100+
- 完成前遵循 `verification-before-completion`:**贴出真实命令输出**再说"绿/完成",不要凭感觉。
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# CN 镜像闭环(gtc / gitcode / mcpp-res)
2+
3+
给每个包配一个国内下载镜像,让 `mcpp add/build` 在中国也快。机制 = `xpm.<plat>.<ver>.url` 从单串变成
4+
`{ GLOBAL = "<上游>", CN = "<gitcode 镜像>" }`,解析优先级 **GLOBAL > CN**(GLOBAL 仍是默认,死了才回落 CN)。
5+
解析方是 **xlings**(xim 引擎),不是 mcpp 的 C++ parser —— 这是 spec 驱动、走既有引擎。
6+
7+
- CN 布局:gitcode org **`mcpp-res`**,一库一仓,资产挂在按版本号打的 release 上。
8+
- **repo slug = 包名去 `compat.` / `mcpplibs.` 前缀**(`compat.eigen``eigen`,`compat.nlohmann?``nlohmann-json` 避免裸 `json` 歧义)。
9+
- CN 资产公网 URL 约定:
10+
`https://gitcode.com/mcpp-res/<slug>/releases/download/<ver>/<slug>-<ver>.<ext>`
11+
12+
## gtc 工具
13+
14+
`gtc`(`tools/gtc`,亦在 `~/.local/bin/gtc`,python,gitcode API v5)。Token 在
15+
`~/.config/gitcode-tool/config.json`(或 `GITCODE_TOKEN` / `--token`)。登录用户 `Sunrisepeak`
16+
17+
```bash
18+
gtc repo create <owner>/<name> [--description …] [--private] # owner==login 走 /user/repos 否则 /orgs/<owner>/repos;幂等(422 警告)
19+
gtc repo push <owner>/<name> <dir> [--branch main]
20+
gtc release create <owner>/<repo> --tag T [--name] [--body-file] [--target] [--prerelease] # 幂等(tag 已存在则跳过)
21+
gtc release upload <owner>/<repo> --tag T <file…> # 非幂等!上传前先查现有资产
22+
gtc release publish <owner>/<repo> --tag T [--name] [--target] [--asset FILE] # = create + upload
23+
gtc pr create <owner>/<repo> --title --head --base [--body-file]
24+
```
25+
> `gtc` **不能建 org**;`mcpp-res` 已存在。若要新 org:`POST /api/v5/orgs` 需同时带 `name``path`
26+
27+
## 标准操作(以 slug=`eigen`、ver=`5.0.1` 为例)
28+
29+
```bash
30+
# 0. 下 GLOBAL 上游 tarball,算 sha256(填进描述符三平台);复算两次确认稳定
31+
curl -L -fsS -o eigen-5.0.1.tar.gz "https://gitlab.com/libeigen/eigen/-/archive/5.0.1/eigen-5.0.1.tar.gz"
32+
sha256sum eigen-5.0.1.tar.gz
33+
34+
# 1. 建仓(幂等)
35+
gtc repo create mcpp-res/eigen --description "Eigen — CN mirror for mcpp-index"
36+
37+
# 2. 新仓没有分支 → 先推一个 init commit,release 才能 target main
38+
mkdir eigen-init && echo "# Eigen — CN mirror" > eigen-init/README.md
39+
gtc repo push mcpp-res/eigen eigen-init --branch main
40+
41+
# 3. 发 release + 传资产(传 GLOBAL 同一个文件 → byte-identical → 同 sha)
42+
gtc release publish mcpp-res/eigen --tag 5.0.1 --name "Eigen 5.0.1" --target main --asset eigen-5.0.1.tar.gz
43+
44+
# 4. 闭环校验:CN 200 + 与 GLOBAL 字节一致
45+
CN="https://gitcode.com/mcpp-res/eigen/releases/download/5.0.1/eigen-5.0.1.tar.gz"
46+
curl -fsSL -o cn.tar.gz -w 'CN http=%{http_code}\n' "$CN"
47+
[ "$(sha256sum eigen-5.0.1.tar.gz|cut -d' ' -f1)" = "$(sha256sum cn.tar.gz|cut -d' ' -f1)" ] && echo "BYTE-IDENTICAL ✓"
48+
```
49+
50+
## 踩坑(全部已被前人踩过)
51+
52+
- **gitcode API 限流 = 25 次/分/用户** → 调用间隔 ~3.2s,遇 429 退避。
53+
- **同名 release 资产不可覆盖**:误传只能网页删 → 命名一次定死(`<slug>-<ver>.<ext>`)。
54+
- **新仓无分支**:不先 push init,release `--target main` 会失败。
55+
- **内容过滤**:某些库描述/仓名被 gitcode 文本过滤拒绝 → 换中性措辞。
56+
- **务必传 GLOBAL 同一个包**:不要把"加了文件的包"传上去 → CN ≠ GLOBAL,破坏一致性(除 `mirror-cn-reachable` 外无人察觉)。
57+
- **sha 漂移**:GitLab 归档偶尔重打包 → sha 变;描述符 sha 必须 = 当前 GLOBAL 实际字节(下载即用,且复算两次)。
58+
- xlings 按 sha256 全局去重:CN 与 GLOBAL 同 sha 时,本地"切 CN 重测"会被去重命中、不真打 gitcode → 验 CN 用直接 `curl`(就是上面第 4 步,也是 CI `mirror-cn-reachable` 干的)。
59+
60+
## CI 兜底
61+
62+
`.github/workflows/validate.yml` 两道闸:
63+
- `lint``tests/check_mirror_urls.lua`:url 写成表时 **GLOBAL+CN 都得有,且 CN 必须指向 gitcode `mcpp-res` 镜像**
64+
- `mirror-cn-reachable`:把所有 CN url 抽出来逐个 `curl`,非 200 即失败。
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# 库形态与描述符模板
2+
3+
先判断库属于哪一类,再抄对应模板。所有 `mcpp = {}` 内路径是**相对 verdir 的 GLOB**,前导 `*` 吸收 tarball 的
4+
`<repo>-<tag>/` wrap 层;`*` 匹配单段、`**` 跨段(`*/blas/*.cpp` 合法)。
5+
6+
四类速判:
7+
8+
| 形态 | 特征 | 真实样例 | 关键字段 |
9+
|---|---|---|---|
10+
| **A. C 源码 compat** | 纯 C/少量源、用户 `#include <foo.h>` | `pkgs/c/compat.cjson.lua``compat.zlib.lua``compat.gtest.lua` | `sources` + `c_standard` |
11+
| **B. header-only** | 纯头、无需编译 | `pkgs/c/compat.eigen.lua``compat.opengl.lua``compat.khrplatform.lua` | `include_dirs` + anchor 源 |
12+
| **C. C++23 module** | 暴露 `import x.y;` | `pkgs/n/nlohmann.json.lua` | `modules` + `generated_files`/源 `.cppm` |
13+
| **D. 外部 Form-A 模块仓** | 上游自带 mcpp 描述,独立仓 | `pkgs/i/imgui.lua``pkgs/m/mcpplibs.*` | `mcpp = "<repo 路径>"`(Form A) |
14+
15+
公共骨架(`package` 头 + `xpm`),A/B/C 通用:
16+
17+
```lua
18+
package = {
19+
spec = "1",
20+
namespace = "compat", -- compat / nlohmann / mcpplibs / …(决定 import 前缀与依赖 key)
21+
name = "compat.<lib>", -- 完整包名;决定 pkgs/<首字母>/ 落点
22+
description = "",
23+
licenses = {"MIT"}, -- SPDX
24+
repo = "https://…",
25+
type = "package",
26+
27+
xpm = { -- 三平台都写;纯源码/纯头时三平台同 url+sha256
28+
linux = { ["1.2.3"] = { url = { GLOBAL = "https://…/v1.2.3.tar.gz",
29+
CN = "https://gitcode.com/mcpp-res/<slug>/releases/download/1.2.3/<slug>-1.2.3.tar.gz" },
30+
sha256 = "<算出来>" } },
31+
macosx = { ["1.2.3"] = { url = { GLOBAL = "", CN = "" }, sha256 = "" } },
32+
windows = { ["1.2.3"] = { url = { GLOBAL = "", CN = "" }, sha256 = "" } },
33+
},
34+
35+
mcpp = { … 见下各形态 … },
36+
}
37+
```
38+
39+
---
40+
41+
## A. C 源码 compat(`compat.cjson` / `compat.zlib`)
42+
43+
把 C 源编成 lib,头经 `include_dirs` 暴露。可选组件走 `features` 门控。
44+
45+
```lua
46+
mcpp = {
47+
language = "c++23", -- 与既有 compat 对齐;真正的 C 由 c_standard 决定
48+
import_std = false,
49+
c_standard = "c99", -- 或 c11
50+
include_dirs = { "*" }, -- 暴露顶层头(*/foo.h)
51+
sources = { "*/cJSON.c" }, -- 核心源,总编
52+
targets = { ["cjson"] = { kind = "lib" } },
53+
features = { -- 可选扩展,默认不编
54+
["utils"] = { sources = { "*/cJSON_Utils.c" } },
55+
},
56+
deps = { },
57+
}
58+
```
59+
要点:多源时逐个列(见 `compat.zlib` 列了 15 个 `.c`),或用 glob;需要配置头时用 `generated_files` 合成
60+
(zlib 用 `mcpp_generated/include/mcpp_zlib_config.h` + `cflags = {"-include …"}`)。
61+
62+
## B. header-only(`compat.eigen` / `compat.opengl`)
63+
64+
无可编译源:`include_dirs` 暴露头,加一个 trivial anchor `.c` 给 mcpp 一个可构建的 lib 目标。
65+
66+
```lua
67+
mcpp = {
68+
language = "c++23",
69+
import_std = false,
70+
c_standard = "c11",
71+
include_dirs = { "*" }, -- 或更精确 "*/include" / "*/api"
72+
generated_files = {
73+
["mcpp_generated/<lib>_anchor.c"] = "int mcpp_compat_<lib>_anchor(void) { return 0; }\n",
74+
},
75+
sources = { "mcpp_generated/<lib>_anchor.c" },
76+
targets = { ["<lib>"] = { kind = "lib" } },
77+
-- 若有"额外可编译源"组件(不是纯头!)→ source-gated feature:
78+
features = {
79+
["blas"] = { sources = { "*/blas/*.cpp", "*/blas/f2c/*.c" } }, -- eigen 实例
80+
},
81+
deps = { },
82+
}
83+
```
84+
注意:**纯头的可选项藏不住**(共享 include 根)→ 不要硬做 feature;只有"额外可编译源"才门控得了(eigen `blas` 就是
85+
C++/f2c-C,无 Fortran → 可门控)。
86+
87+
## C. C++23 module(`nlohmann.json`)
88+
89+
让用户 `import x.y;`。两条路:
90+
1. **上游已带 `.cppm`**:直接 `sources = { "*/path/to/unit.cppm" }`
91+
2. **上游 release 不带**(常见):用 `generated_files` 合成 wrapper(`#include <header>` + `export module x.y;` +
92+
`export using …`),基底头 pin 已发布 tag。**逐字复用上游官方 wrapper,别自己猜符号清单**
93+
94+
```lua
95+
mcpp = {
96+
schema = "0.1",
97+
language = "c++23",
98+
import_std = false, -- wrapper 含上游头,开 import std 易冲突
99+
modules = { "nlohmann.json" },
100+
include_dirs = { "*/single_include" }, -- 让 wrapper 内 #include <…> 可解析
101+
generated_files = {
102+
["mcpp_generated/nlohmann.json.cppm"] = "module;\n#include <nlohmann/json.hpp>\nexport module nlohmann.json;\n",
103+
},
104+
sources = { "mcpp_generated/nlohmann.json.cppm" },
105+
targets = { ["nlohmann_json"] = { kind = "lib" } },
106+
deps = { },
107+
}
108+
```
109+
⚠️ **mcpp 段解析器不支持 Lua 长括号 `[[ … ]]`**:`generated_files` 内容必须用双引号字符串 + `\n`/`\"` 转义
110+
(否则 `malformed mcpp segment`)。消费侧:`import x.y;` 别和文本 `#include <string>` 混用(GCC modules 冲突),
111+
`import std;`
112+
113+
## D. 外部 Form-A 模块仓(`imgui` / `mcpplibs.*`)
114+
115+
上游/独立仓自带 mcpp 描述符,本仓只做"指针":`mcpp = "<相对/远程路径>"`(Form A,而非 inline 的 Form B)。
116+
新增独立库基本属于另一个仓(如 `mcpplibs/imgui-m`),本仓只登记。照抄 `pkgs/i/imgui.lua``pkgs/m/mcpplibs.xpkg.lua` 的写法。
117+
118+
---
119+
120+
## 最小工程(`tests/examples/<short>/`)
121+
122+
`mcpp.toml`(短依赖 / 长依赖二选一):
123+
```toml
124+
[package]
125+
name = "<short>-example"
126+
version = "0.1.0"
127+
[toolchain]
128+
default = "gcc@16.1.0"
129+
[indices]
130+
compat = { path = "../../.." } # 指回仓根 → 用本地描述符
131+
[dependencies.compat]
132+
<short> = "1.2.3" # 或:<short> = { version = "1.2.3", features = ["…"] }
133+
[targets.<short>-example]
134+
kind = "bin"
135+
main = "src/main.cpp" # C 库可用 .c
136+
```
137+
`src/main.cpp`:做**真断言**`return ok ? 0 : 1`(别只打印)。module 库用 `import std; import x.y;`;
138+
header/C 库用文本 `#include`

0 commit comments

Comments
 (0)