diff --git a/.agents/skills/add-mcpp-index-package/SKILL.md b/.agents/skills/add-mcpp-index-package/SKILL.md index 4ca9b26..5bfbc92 100644 --- a/.agents/skills/add-mcpp-index-package/SKILL.md +++ b/.agents/skills/add-mcpp-index-package/SKILL.md @@ -16,11 +16,12 @@ description: Use when adding a new third-party library/package to the mcpp-index - `.agents/docs/2026-06-28-add-eigen-plan.md`(header-only 库及 source-gated `blas` feature)。 - 既有 PR:#48(cjson 与 nlohmann.json)、#50(eigen)。 -配套参考文档位于仓库 `docs/` 目录,供人工与 agent 共同使用,可按需查阅: +配套参考文档位于仓库 `docs/` 目录(英文为默认版本,中文版在 `docs/zh/`,内容对等),供人工与 agent 共同使用, +可按需查阅(下列链接指向中文版): -- [docs/package-types.md](../../../docs/package-types.md) —— 四种库形态的描述符模板与样例路径。 -- [docs/cn-mirror.md](../../../docs/cn-mirror.md) —— CN 镜像闭环,含无 `mcpp-res` 权限时的回退方案。 -- [docs/repository-and-schema.md](../../../docs/repository-and-schema.md) —— 仓库结构、schema、CI 行为、关键文件与注意事项。 +- [docs/zh/package-types.md](../../../docs/zh/package-types.md) —— 四种库形态的描述符模板与样例路径。 +- [docs/zh/cn-mirror.md](../../../docs/zh/cn-mirror.md) —— CN 镜像闭环,含无 `mcpp-res` 权限时的回退方案。 +- [docs/zh/repository-and-schema.md](../../../docs/zh/repository-and-schema.md) —— 仓库结构、schema、CI 行为、关键文件与注意事项。 ## 适用范围 @@ -48,12 +49,12 @@ description: Use when adding a new third-party library/package to the mcpp-index **纯头文件 / C 源码 / 自带 `.cppm` 模块 / 含可选组件(可实现 feature)** 中的何种形态。 - 计算 `sha256sum`,并**重复计算两次以确认稳定**。GitLab 等部分归档源会重新打包,导致 sha 漂移,进而使 CI 经 GLOBAL 拉取时校验失败。 -2. **确定形态并选择模板**:详见 [docs/package-types.md](../../../docs/package-types.md)。四类形态为:C 源码 compat、 +2. **确定形态并选择模板**:详见 [docs/zh/package-types.md](../../../docs/zh/package-types.md)。四类形态为:C 源码 compat、 header-only、C++23 module(generated wrapper)、外部 Form-A 模块仓。 3. **建立 CN 镜像**:使用 `gtc` 在 gitcode `mcpp-res` 组织下建仓并发布 release,上传**与 GLOBAL 相同的 tarball**, 以保证字节一致(sha 相同)。**在不具备 `mcpp-res` 写权限时**,不应构造镜像表,而应使用纯字符串形式 `url = ""`(lint 允许此形式,CN 用户将回退至上游源),镜像由维护者后续补充。详见 - [docs/cn-mirror.md](../../../docs/cn-mirror.md)。 + [docs/zh/cn-mirror.md](../../../docs/zh/cn-mirror.md)。 4. **编写描述符** `pkgs//.lua`。 - 目录 `` **取完整包名首字母**(`compat.eigen` 对应 `pkgs/c/`,`nlohmann.json` 对应 `pkgs/n/`), 而非短名。放置错误将导致本地 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 编译为 no-op `main`(`#ifdef __linux__ … #else int main(){return 0;} #endif` 模式)。 - 如需测试 feature,依赖采用长式声明 `name = { version = "…", features = ["…"] }`。 7. **本地验证**(使用与 CI 相同版本的 mcpp,详见下文“本地验证”)。必须实际执行 `mcpp test -p ` 并通过。 -8. **更新 README**:在对应分类表中新增一条记录。 +8. **更新 README**:在对应分类表中新增一条记录 —— `README.md`(英文,默认)与 `README.zh-CN.md`(中文)两份都要更新。 9. **撰写设计文档** `.agents/docs/-add--plan.md`,记录形态判定、镜像、feature 评估、验证结论 与注意事项。 10. **本地 lint**:在本地复现 `validate.yml` 的 lint 检查(语法、必填字段、无前导 v、镜像表校验)。详见 - [docs/repository-and-schema.md](../../../docs/repository-and-schema.md)。 + [docs/zh/repository-and-schema.md](../../../docs/zh/repository-and-schema.md)。 11. **提交变更**:由 `main` 切出新分支,依次 commit、push、开 PR(不应直接推送 `main`)。PR 描述应载明形态、镜像、 feature 与验证结论。 12. **确认 CI 通过**:`workspace (linux|macos|windows)` 的选择性成员测试应仅选中本库对应成员并通过 diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 4a7d33f..b937a58 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -5,7 +5,7 @@ on: # mcpp.toml and index.toml carry the workspace member list, the inherited # [indices] redirect and the client version floor — a change to any of them # can break every member, so they gate the run like the descriptors do. - paths: ["pkgs/**/*.lua", "tests/**", "README.md", "mcpp.toml", "index.toml", ".github/workflows/validate.yml"] + paths: ["pkgs/**/*.lua", "tests/**", "README.md", "README.zh-CN.md", "mcpp.toml", "index.toml", ".github/workflows/validate.yml"] push: branches: [main] schedule: @@ -263,13 +263,37 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 + # The cache key is computed ONCE, here, instead of inline in the cache + # step. `hashFiles()` globs the WORKING TREE, and actions/cache + # re-evaluates its `key` in the post (save) step — i.e. AFTER the build, + # when `tests/**` no longer matches 80-odd tracked sources but tens of + # thousands of build-output files under tests/examples/*/target and + # .mcpp (multi-GB; .gitignore does not apply to hashFiles). Hashing that + # tree blew past the runner's 120s template-evaluation cap on windows + # and failed an otherwise all-green job: + # "hashFiles('pkgs/**/*.lua, tests/**, .github/workflows/validate.yml') + # couldn't finish within 120 seconds" + # `git ls-files -s` reads the INDEX, so it sees exactly the tracked + # inputs, never build output, and reports blob SHAs git already has — + # no file content is read at all. Freezing the result in the job env + # also guarantees the save step keys on the same string the restore + # step used, no matter what the build left behind. + - name: Compute registry cache key + shell: bash + run: | + # git hash-object rather than sha256sum/cut: git is already a hard + # requirement here (checkout ran), coreutils on the windows leg is + # only a Git-Bash convenience. + h=$(git ls-files -s -- 'pkgs/**/*.lua' 'tests/**' '.github/workflows/validate.yml' \ + | git hash-object --stdin) + echo "REGISTRY_CACHE_KEY=mcpp-registry-${{ runner.os }}-${{ env.MCPP_EFFECTIVE }}-$h" >> "$GITHUB_ENV" - name: Restore mcpp registry cache uses: actions/cache@v4 with: # Holds toolchains AND the built compat packages (data/xpkgs), so a # repeat `mcpp test` rebuilds little. path: ~/.mcpp/registry - key: mcpp-registry-${{ runner.os }}-${{ env.MCPP_EFFECTIVE }}-${{ hashFiles('pkgs/**/*.lua', 'tests/**', '.github/workflows/validate.yml') }} + key: ${{ env.REGISTRY_CACHE_KEY }} restore-keys: | mcpp-registry-${{ runner.os }}-${{ env.MCPP_EFFECTIVE }}- - name: Download mcpp diff --git a/README.md b/README.md index c2ff18f..ee8e588 100644 --- a/README.md +++ b/README.md @@ -1,91 +1,103 @@ # mcpp-index -> [`mcpp`](https://github.com/mcpp-community/mcpp) 构建工具的默认包索引仓库。 -> 在线浏览所有包:**https://mcpplibs.github.io/mcpp-index/** +**English** | [简体中文](README.zh-CN.md) -本仓收录可被 `mcpp` 直接 `add` 的 C++23 包,既包含 `import` 即用的模块化库,也包含以 `compat` 形态从上游源码或 -头文件构建的第三方 C/C++ 库。每个包对应一个 `pkgs/<首字母>/<包名>.lua` 描述文件。 +> The default package index repository for the [`mcpp`](https://github.com/mcpp-community/mcpp) build tool. +> Browse every package online: **https://mcpplibs.github.io/mcpp-index/** -## 使用 +This repository hosts the C++23 packages that `mcpp` can `add` directly — both modular libraries that are ready to +`import`, and third-party C/C++ libraries built from upstream sources or headers in `compat` form. Every package maps +to one `pkgs//.lua` descriptor file. + +## Usage ```bash -mcpp add ftxui@6.1.9 # 添加依赖到 mcpp.toml -mcpp build # 自动拉取源码并构建,依赖沿链路自动传递 +mcpp add ftxui@6.1.9 # add the dependency to mcpp.toml +mcpp build # fetch sources and build; dependencies propagate along the chain -mcpp search # 搜索并刷新索引 -mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上游源 +mcpp search # search and refresh the index +mcpp self config --mirror CN # switch to the CN mirror; GLOBAL upstream is the default ``` -完整包列表见 **[在线索引站](https://mcpplibs.github.io/mcpp-index/)**。 +For the full package list, see the **[online index site](https://mcpplibs.github.io/mcpp-index/)**. -## 包生态与贡献 +## Package ecosystem and contributing -本仓收录两类包: +Two kinds of packages live here: -- **原生 mcpp 模块库**:以 C++23 模块发布、`import` 即用,包括 `mcpplibs.*`、`nlohmann.json`、`imgui`、`ffmpeg`、`opencv`,以及由 - 用户基于 mcpp 开发并登记进索引的库(如 `tensorvia-cpu`)。其上游通常自带 `mcpp.toml`,描述文件(Form A)只声明 - 元数据与下载地址。 -- **第三方 C/C++ 库(`compat`)**:其上游不提供 mcpp 支持,描述文件(Form B)内联构建信息。该类库存在 - header-only、纯 C 源码、C++23 module wrapper 等形态,可选组件经 `features` 门控,并配备 GitCode CN 镜像。 +- **Native mcpp module libraries**: shipped as C++23 modules and ready to `import` — `mcpplibs.*`, `nlohmann.json`, + `imgui`, `ffmpeg`, `opencv`, plus libraries developed on top of mcpp by users and registered into the index (such as + `tensorvia-cpu`). Their upstream usually carries its own `mcpp.toml`, so the descriptor (Form A) only declares + metadata and a download address. +- **Third-party C/C++ libraries (`compat`)**: upstream offers no mcpp support, so the descriptor (Form B) inlines the + build information. These come in several shapes — header-only, plain C sources, C++23 module wrapper — with optional + components gated behind `features` and a GitCode CN mirror configured. -### 参考示例(`.lua` 描述符) +### Reference examples (`.lua` descriptors) -| 形态 | 示例 | +| Shape | Examples | |------|------| -| 原生模块库(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 后端必需) | -| C++ 应用框架 compat(依赖复用索引内既有包) | [`compat.eui-neo`](pkgs/e/compat.eui-neo.lua)(上游 `3rd/` 自带 8 个 vendored 依赖,此处一个不编,全部改指索引内同版本 `compat.*`) | -| 互斥后端(同包多后端二选一) | [`compat.eui-neo`](pkgs/e/compat.eui-neo.lua):`vulkan` / `sdl2` 各自**替换**默认的 OpenGL / GLFW,默认后端由"不点名任何 feature"表达,并不存在 `opengl`/`glfw` feature。`default` feature 表达不了互斥 —— 它自带的 `defines`/`sources`/`deps` 完全不生效,而 `implies` 又恒生效、无法被点名的 feature 覆盖(后者反而正好是本表『恒开的 interface define』一行的解法)。可行解是读 mcpp 本就会传的 `-DMCPP_FEATURE_`,在强制包含头里做前置判定。另注意 `cflags` 只作用于 C TU,C++ 需 `cxxflags` —— 只写进 `cflags` 的后端 define 到不了任何 `.cpp` | -| 宿主运行时适配(不 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) | -| 全源码直编(config 快照 + 源列表,零外部构建系统) | [`compat.ffmpeg`](pkgs/c/compat.ffmpeg.lua)(2281 TU 含 NASM 汇编,28 个目录 glob 声明) | +| Native module library (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) (module layer; sources compiled directly through `compat.ffmpeg`) · [`opencv`](pkgs/o/opencv.lua) (single repository: the module layer and the full OpenCV 5 source build both live in the package, and only this descriptor stays on the index side) | +| C-source compat (with `features`) | [`compat.cjson`](pkgs/c/compat.cjson.lua) · [`compat.zlib`](pkgs/c/compat.zlib.lua) | +| header-only (with `features`) | [`compat.eigen`](pkgs/c/compat.eigen.lua) | +| Runtime loader compat (pure sources, sidestepping upstream codegen/asm) | [`compat.vulkan`](pkgs/c/compat.vulkan.lua) (the Khronos loader: `loader/generated/` is checked in, and the assembly path degrades to plain C through `UNKNOWN_FUNCTIONS_SUPPORTED`, so no CMake/Python/assembler is needed; windows deferred) · [`compat.vulkan-headers`](pkgs/c/compat.vulkan-headers.lua) | +| Whole-source direct build + generated config (only where a platform lacks one) | [`compat.curl`](pkgs/c/compat.curl.lua) (win32 uses upstream's checked-in config, unix generates one) · [`compat.sdl2`](pkgs/c/compat.sdl2.lua) (win/mac use upstream's checked-in config; linux generates one and enables X11 by hand) | +| Header package filling a gap in the index | [`compat.glx-headers`](pkgs/c/compat.glx-headers.lua) (libglvnd's `GL/glx.h`, absent from the Khronos registry and required by SDL's X11 backend) | +| C++ application framework compat (dependencies reuse packages already in the index) | [`compat.eui-neo`](pkgs/e/compat.eui-neo.lua) (upstream's `3rd/` ships 8 vendored dependencies; none of them is compiled here — all are redirected to the same-version `compat.*` packages in this index) | +| Mutually exclusive backends (one of several inside one package) | [`compat.eui-neo`](pkgs/e/compat.eui-neo.lua): `vulkan` / `sdl2` each **replace** the default OpenGL / GLFW, and the default backend is expressed by *naming no feature at all* — there is no `opengl`/`glfw` feature. A `default` feature cannot express exclusivity: its own `defines`/`sources`/`deps` have no effect whatsoever, while its `implies` always applies and cannot be overridden by a named feature (which is, conversely, exactly the solution for the "always-on interface define" row below). The workable answer is to read the `-DMCPP_FEATURE_` mcpp passes anyway and decide up front in a force-included header. Note also that `cflags` only reaches C TUs — C++ needs `cxxflags`, so a backend define written only into `cflags` never reaches any `.cpp` | +| Host runtime adaptation (drivers are not vendored) | [`compat.glx-runtime`](pkgs/c/compat.glx-runtime.lua) · [`compat.vulkan-runtime`](pkgs/c/compat.vulkan-runtime.lua) (mcpp binaries run against a bundled glibc, so a bare-soname `dlopen` never reaches the host drivers; a symlink farm plus `runtime.library_dirs` bridges that. Note the farm holds only versioned sonames — `library_dirs` also joins the link line) | +| Always-on interface define | `CURL_STATICLIB` in [`compat.curl`](pkgs/c/compat.curl.lua): `cflags` is always on but package-private, while a feature's `defines` reaches consumers yet has to be named — `default = { implies = … }` applies unconditionally and happens to give both | +| Multiple majors in one package (shape switches with the version) | [`compat.catch2`](pkgs/c/compat.catch2.lua) (3.x compiles `src/catch2/` into a static library; 2.x goes header-only through `single_include/`) | +| External build system (`install()` builds from source) | [`compat.openblas`](pkgs/c/compat.openblas.lua) (Make) · [`compat.openssl`](pkgs/c/compat.openssl.lua) (Perl Configure + Make, static libssl/libcrypto) | +| Whole-source direct build (config snapshot + source list, no external build system) | [`compat.ffmpeg`](pkgs/c/compat.ffmpeg.lua) (2281 TUs including NASM assembly, declared through 28 directory globs) | | C++23 module wrapper | [`nlohmann.json`](pkgs/n/nlohmann.json.lua) · [`marzer.tomlplusplus`](pkgs/m/marzer.tomlplusplus.lua) · [`neargye.magic_enum`](pkgs/n/neargye.magic_enum.lua) | -### 新增一个包 +### Adding a package -完整流程定义于 agent skill [`add-mcpp-index-package`](.agents/skills/add-mcpp-index-package/SKILL.md)。可将下列 -指令提供给 agent(如 Claude Code),由其调用该 skill 完成描述文件的编写与全流程: +The full procedure is defined in the agent skill +[`add-mcpp-index-package`](.agents/skills/add-mcpp-index-package/SKILL.md). Hand the instruction below to an agent +(Claude Code, for example) and it will invoke that skill to write the descriptor and carry out the whole flow: ```text -参考本仓 skill `.agents/skills/add-mcpp-index-package`,将 <库名 / 仓库URL> @<版本> 收录进 mcpp-index: -判定形态;配置 CN 镜像(无 mcpp-res 权限时使用 plain-string 上游 url);编写 pkgs/<首字母>/<包名>.lua; -添加 tests/examples/<库>/ 测试工程并登记为 workspace 成员;使用与 CI 同版本的 mcpp 本地执行 -`mcpp test -p <成员>` 进行验证;更新 README 与在线索引;提交 PR 并确认 CI 通过。 +Following this repo's skill `.agents/skills/add-mcpp-index-package`, add @ to +mcpp-index: determine the shape; configure the CN mirror (use a plain-string upstream url when you have no mcpp-res +access); write pkgs//.lua; add a tests/examples// test project and register it as a workspace +member; verify locally with the same mcpp version CI pins by running `mcpp test -p `; update the README and +the online index; open a PR and confirm CI is green. ``` -细节文档位于 [`docs/`](docs/),供人工与 agent 共同使用: +Detailed documentation lives in [`docs/`](docs/), written for humans and agents alike: -- [库形态与描述符模板](docs/package-types.md):各类形态的描述符模板与样例,以及最小工程的写法。 -- [CN 镜像闭环](docs/cn-mirror.md):`gtc` 与 gitcode 操作,以及无 `mcpp-res` 权限时的回退方案。 -- [仓库结构与 schema 与 CI](docs/repository-and-schema.md):字段速查、选跑机制与本地 lint。 -- 字段的**权威判定**是 `mcpp xpkg parse`(CI 用的就是它:未知的 mcpp 段字段直接失败,而不是被静默忽略); - 语义与约束见 mcpp 仓的 [`docs/spec/`](https://github.com/mcpp-community/mcpp/tree/main/docs/spec)。 +- [Library shapes and descriptor templates](docs/package-types.md): descriptor templates and samples for each shape, + plus how to write the minimal project. +- [The CN mirror loop](docs/cn-mirror.md): `gtc` and gitcode operations, plus the fallback when you have no + `mcpp-res` access. +- [Repository layout, schema and CI](docs/repository-and-schema.md): field cheat-sheet, selective-run mechanics and + local lint. +- The **authoritative** judge of a field is `mcpp xpkg parse` (exactly what CI runs: an unknown mcpp-segment field + fails outright instead of being silently ignored); for semantics and constraints see + [`docs/spec/`](https://github.com/mcpp-community/mcpp/tree/main/docs/spec) in the mcpp repository. -> 提交 PR 后,`validate` 自动执行 lint 并按改动库选跑对应 workspace 成员(整个测试面是一个 mcpp -> workspace,公开模块包 `imgui`/`ffmpeg`/`opencv`/`tinyhttps` 也是普通成员——`compat` 的重定向声明在 -> workspace 根并由成员继承,消费其他命名空间的成员各自覆盖,零 shell 驱动);合并后,`deploy-site` -> 将其发布至在线浏览站。 +> Once a PR is open, `validate` runs lint automatically and selects the workspace members affected by the changed +> library (the whole test surface is one mcpp workspace, and the public module packages +> `imgui`/`ffmpeg`/`opencv`/`tinyhttps` are ordinary members too — the `compat` redirect is declared at the workspace +> root and inherited by members, while members that consume another namespace override it themselves, with zero shell +> driving). After the merge, `deploy-site` publishes it to the online browser. -## 相关链接 +## Related links -| 项目 | 说明 | +| Project | Description | |------|------| -| [mcpp](https://github.com/mcpp-community/mcpp) | 现代 C++23 构建与包管理工具 | -| [xlings](https://github.com/d2learn/xlings) | mcpp 底层的包安装引擎与沙箱环境 | -| [xpkg V1 spec](https://github.com/d2learn/xim-pkgindex/blob/main/docs/V1/xpackage-spec.md) | 包描述文件规范 | -| [mcpplibs](https://github.com/mcpplibs) | mcpp 生态的模块化 C++23 库集合 | -| [mcpp-res](https://gitcode.com/mcpp-res) | 包资源的 CN 镜像组织(gitcode) | +| [mcpp](https://github.com/mcpp-community/mcpp) | Modern C++23 build and package management tool | +| [xlings](https://github.com/d2learn/xlings) | The package installation engine and sandbox environment underneath mcpp | +| [xpkg V1 spec](https://github.com/d2learn/xim-pkgindex/blob/main/docs/V1/xpackage-spec.md) | Package descriptor specification | +| [mcpplibs](https://github.com/mcpplibs) | The collection of modular C++23 libraries in the mcpp ecosystem | +| [mcpp-res](https://gitcode.com/mcpp-res) | The CN mirror organization for package resources (gitcode) | -## 社区 +## Community -[mcpp issues](https://github.com/mcpp-community/mcpp/issues) · [d2learn 论坛](https://forum.d2learn.org) +[mcpp issues](https://github.com/mcpp-community/mcpp/issues) · [d2learn forum](https://forum.d2learn.org) ## License -包描述文件采用 CC0;各上游库保留其自身许可证。 +The package descriptors are CC0; each upstream library keeps its own license. diff --git a/README.zh-CN.md b/README.zh-CN.md new file mode 100644 index 0000000..cb3b366 --- /dev/null +++ b/README.zh-CN.md @@ -0,0 +1,93 @@ +# mcpp-index + +[English](README.md) | **简体中文** + +> [`mcpp`](https://github.com/mcpp-community/mcpp) 构建工具的默认包索引仓库。 +> 在线浏览所有包:**https://mcpplibs.github.io/mcpp-index/** + +本仓收录可被 `mcpp` 直接 `add` 的 C++23 包,既包含 `import` 即用的模块化库,也包含以 `compat` 形态从上游源码或 +头文件构建的第三方 C/C++ 库。每个包对应一个 `pkgs/<首字母>/<包名>.lua` 描述文件。 + +## 使用 + +```bash +mcpp add ftxui@6.1.9 # 添加依赖到 mcpp.toml +mcpp build # 自动拉取源码并构建,依赖沿链路自动传递 + +mcpp search # 搜索并刷新索引 +mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上游源 +``` + +完整包列表见 **[在线索引站](https://mcpplibs.github.io/mcpp-index/)**。 + +## 包生态与贡献 + +本仓收录两类包: + +- **原生 mcpp 模块库**:以 C++23 模块发布、`import` 即用,包括 `mcpplibs.*`、`nlohmann.json`、`imgui`、`ffmpeg`、`opencv`,以及由 + 用户基于 mcpp 开发并登记进索引的库(如 `tensorvia-cpu`)。其上游通常自带 `mcpp.toml`,描述文件(Form A)只声明 + 元数据与下载地址。 +- **第三方 C/C++ 库(`compat`)**:其上游不提供 mcpp 支持,描述文件(Form B)内联构建信息。该类库存在 + header-only、纯 C 源码、C++23 module wrapper 等形态,可选组件经 `features` 门控,并配备 GitCode CN 镜像。 + +### 参考示例(`.lua` 描述符) + +| 形态 | 示例 | +|------|------| +| 原生模块库(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 后端必需) | +| C++ 应用框架 compat(依赖复用索引内既有包) | [`compat.eui-neo`](pkgs/e/compat.eui-neo.lua)(上游 `3rd/` 自带 8 个 vendored 依赖,此处一个不编,全部改指索引内同版本 `compat.*`) | +| 互斥后端(同包多后端二选一) | [`compat.eui-neo`](pkgs/e/compat.eui-neo.lua):`vulkan` / `sdl2` 各自**替换**默认的 OpenGL / GLFW,默认后端由"不点名任何 feature"表达,并不存在 `opengl`/`glfw` feature。`default` feature 表达不了互斥 —— 它自带的 `defines`/`sources`/`deps` 完全不生效,而 `implies` 又恒生效、无法被点名的 feature 覆盖(后者反而正好是本表『恒开的 interface define』一行的解法)。可行解是读 mcpp 本就会传的 `-DMCPP_FEATURE_`,在强制包含头里做前置判定。另注意 `cflags` 只作用于 C TU,C++ 需 `cxxflags` —— 只写进 `cflags` 的后端 define 到不了任何 `.cpp` | +| 宿主运行时适配(不 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) | +| 全源码直编(config 快照 + 源列表,零外部构建系统) | [`compat.ffmpeg`](pkgs/c/compat.ffmpeg.lua)(2281 TU 含 NASM 汇编,28 个目录 glob 声明) | +| C++23 module wrapper | [`nlohmann.json`](pkgs/n/nlohmann.json.lua) · [`marzer.tomlplusplus`](pkgs/m/marzer.tomlplusplus.lua) · [`neargye.magic_enum`](pkgs/n/neargye.magic_enum.lua) | + +### 新增一个包 + +完整流程定义于 agent skill [`add-mcpp-index-package`](.agents/skills/add-mcpp-index-package/SKILL.md)。可将下列 +指令提供给 agent(如 Claude Code),由其调用该 skill 完成描述文件的编写与全流程: + +```text +参考本仓 skill `.agents/skills/add-mcpp-index-package`,将 <库名 / 仓库URL> @<版本> 收录进 mcpp-index: +判定形态;配置 CN 镜像(无 mcpp-res 权限时使用 plain-string 上游 url);编写 pkgs/<首字母>/<包名>.lua; +添加 tests/examples/<库>/ 测试工程并登记为 workspace 成员;使用与 CI 同版本的 mcpp 本地执行 +`mcpp test -p <成员>` 进行验证;更新 README 与在线索引;提交 PR 并确认 CI 通过。 +``` + +细节文档位于 [`docs/zh/`](docs/zh/),供人工与 agent 共同使用(英文版位于 [`docs/`](docs/)): + +- [库形态与描述符模板](docs/zh/package-types.md):各类形态的描述符模板与样例,以及最小工程的写法。 +- [CN 镜像闭环](docs/zh/cn-mirror.md):`gtc` 与 gitcode 操作,以及无 `mcpp-res` 权限时的回退方案。 +- [仓库结构与 schema 与 CI](docs/zh/repository-and-schema.md):字段速查、选跑机制与本地 lint。 +- 字段的**权威判定**是 `mcpp xpkg parse`(CI 用的就是它:未知的 mcpp 段字段直接失败,而不是被静默忽略); + 语义与约束见 mcpp 仓的 [`docs/spec/`](https://github.com/mcpp-community/mcpp/tree/main/docs/spec)。 + +> 提交 PR 后,`validate` 自动执行 lint 并按改动库选跑对应 workspace 成员(整个测试面是一个 mcpp +> workspace,公开模块包 `imgui`/`ffmpeg`/`opencv`/`tinyhttps` 也是普通成员——`compat` 的重定向声明在 +> workspace 根并由成员继承,消费其他命名空间的成员各自覆盖,零 shell 驱动);合并后,`deploy-site` +> 将其发布至在线浏览站。 + +## 相关链接 + +| 项目 | 说明 | +|------|------| +| [mcpp](https://github.com/mcpp-community/mcpp) | 现代 C++23 构建与包管理工具 | +| [xlings](https://github.com/d2learn/xlings) | mcpp 底层的包安装引擎与沙箱环境 | +| [xpkg V1 spec](https://github.com/d2learn/xim-pkgindex/blob/main/docs/V1/xpackage-spec.md) | 包描述文件规范 | +| [mcpplibs](https://github.com/mcpplibs) | mcpp 生态的模块化 C++23 库集合 | +| [mcpp-res](https://gitcode.com/mcpp-res) | 包资源的 CN 镜像组织(gitcode) | + +## 社区 + +[mcpp issues](https://github.com/mcpp-community/mcpp/issues) · [d2learn 论坛](https://forum.d2learn.org) + +## License + +包描述文件采用 CC0;各上游库保留其自身许可证。 diff --git a/docs/README.md b/docs/README.md index f2f9615..75ed80c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,13 +1,20 @@ -# mcpp-index 文档 +# mcpp-index documentation -本目录为面向贡献者的参考文档,供人工与 agent 共同使用。新增一个包的端到端流程定义于 agent skill -[`add-mcpp-index-package`](../.agents/skills/add-mcpp-index-package/SKILL.md);下列文档提供各环节的细节参考。 +**English** | [简体中文](zh/README.md) -| 文档 | 内容 | +This directory holds the contributor reference documentation, written for humans and agents alike. The end-to-end +procedure for adding a package is defined in the agent skill +[`add-mcpp-index-package`](../.agents/skills/add-mcpp-index-package/SKILL.md); the documents below provide the details +behind each step. + +| Document | Contents | |------|------| -| [package-types.md](package-types.md) | 四种库形态(C 源码 compat、header-only、C++23 module wrapper、外部 Form-A 模块仓)的描述符模板与样例 | -| [cn-mirror.md](cn-mirror.md) | GitCode `mcpp-res` CN 镜像闭环(`gtc` 工具、闭环校验、注意事项),含无 `mcpp-res` 写权限时的回退方案(plain-string 上游 url) | -| [repository-and-schema.md](repository-and-schema.md) | 仓库布局、描述符 schema 速查、`validate.yml` CI 行为、本地 lint 复现、案例索引 | +| [package-types.md](package-types.md) | Descriptor templates and samples for the four library shapes (C-source compat, header-only, C++23 module wrapper, external Form-A module repo) | +| [cn-mirror.md](cn-mirror.md) | The GitCode `mcpp-res` CN mirror loop (`gtc` tooling, closed-loop verification, caveats), including the fallback when you lack write access to `mcpp-res` (plain-string upstream url) | +| [repository-and-schema.md](repository-and-schema.md) | Repository layout, descriptor schema cheat-sheet, `validate.yml` CI behavior, reproducing lint locally, case index | + +> The package field specification (the `mcpp = { … }` extension) is settled by `mcpp xpkg parse` — the very parser CI +> uses; for semantics and constraints see [`docs/spec/`](https://github.com/mcpp-community/mcpp/tree/main/docs/spec) +> in the mcpp repository. -> 包的字段规范(`mcpp = { … }` 扩展)以上游为准,见 -> `mcpp xpkg parse`(CI 使用的同一个解析器),语义与约束见 mcpp 仓的 [`docs/spec/`](https://github.com/mcpp-community/mcpp/tree/main/docs/spec)。 +Chinese versions of these documents live in [`zh/`](zh/). diff --git a/docs/cn-mirror.md b/docs/cn-mirror.md index 65c9c90..a4c14a7 100644 --- a/docs/cn-mirror.md +++ b/docs/cn-mirror.md @@ -1,91 +1,105 @@ -# CN 镜像闭环(gtc / gitcode / mcpp-res) +# The CN mirror loop (gtc / gitcode / mcpp-res) -为每个包配备国内下载镜像,可提升 `mcpp add/build` 在中国大陆的访问速度。其机制为:将 `xpm...url` 由 -单一字符串改写为 `{ GLOBAL = "<上游>", CN = "" }`,解析优先级为 **GLOBAL 优先于 CN**(GLOBAL 仍为 -默认,仅在其不可用时回退至 CN)。解析由 **xlings**(xim 引擎)完成,而非 mcpp 的 C++ parser,因此该机制由 spec -驱动并经由既有引擎生效。 +**English** | [简体中文](zh/cn-mirror.md) -- CN 布局:gitcode 组织 **`mcpp-res`**,每库一仓,资产挂载于按版本号打的 release 上。 -- repo slug 取包名去除 `compat.` 或 `mcpplibs.` 前缀后的结果(`compat.eigen` 对应 `eigen`;`nlohmann` 类宜用 - `nlohmann-json` 以避免裸 `json` 的歧义)。 -- CN 资产的公网 URL 约定为: +Giving every package a domestic download mirror speeds up `mcpp add/build` inside mainland China. The mechanism: +rewrite `xpm...url` from a single string into `{ GLOBAL = "", CN = "" }`, where +resolution prefers **GLOBAL over CN** (GLOBAL stays the default and CN is only the fallback when it is unavailable). +Resolution happens in **xlings** (the xim engine), not in mcpp's C++ parser, so the mechanism is spec-driven and takes +effect through the existing engine. + +- CN layout: the gitcode organization **`mcpp-res`**, one repository per library, with the assets attached to a + release tagged by version number. +- The repo slug is the package name with the `compat.` or `mcpplibs.` prefix stripped (`compat.eigen` → `eigen`; + for the `nlohmann` family prefer `nlohmann-json`, to avoid the ambiguity of a bare `json`). +- The public URL of a CN asset follows this convention: `https://gitcode.com/mcpp-res//releases/download//-.` -## 无 `mcpp-res` 写权限时的回退 +## Fallback without `mcpp-res` write access -建立镜像需要 gitcode `mcpp-res` 组织的写权限(token)。在不具备该权限时,不应勉强构造镜像表:lint -(`check_mirror_urls.lua`)强制要求,一旦 `url` 写成表形式,其 `CN` 必须为 `https://gitcode.com/mcpp-res/…`, -因此 `{ GLOBAL=上游, CN=上游 }` 会直接导致 lint 失败。正确的回退方式是采用纯字符串 url(仅填上游 release), -lint 对纯字符串 url 不施加镜像约束: +Setting up a mirror requires write access (a token) to the gitcode `mcpp-res` organization. Without it, do not force a +mirror table into existence: lint (`check_mirror_urls.lua`) mandates that once `url` is written as a table, its `CN` +must be `https://gitcode.com/mcpp-res/…`, so `{ GLOBAL=upstream, CN=upstream }` fails lint outright. The correct +fallback is a plain-string url (upstream release only) — lint imposes no mirror constraint on plain-string urls: ```lua --- 回退:无 CN 镜像,GLOBAL 与 CN 均等价于上游 release(使用单一字符串即可) +-- fallback: no CN mirror, GLOBAL and CN both equal the upstream release (a single string is enough) ["0.1.1"] = { url = "https://github.com///releases/download/v0.1.1/-0.1.1.tar.gz", sha256 = "…" }, ``` -- CN 用户将回退至上游源,访问较慢但功能不受影响。 -- 既有先例:`pkgs/t/tensorvia-cpu.lua`(用户自有 mcpp 库,无 CN 镜像,三平台均使用上游单一字符串 url)。 -- 后续在获得权限、或由维护者补充镜像后,可将该版本的 `url` 改写为 `{ GLOBAL=…, CN=… }` 表(sha256 保持不变)。 +- CN users fall back to the upstream source: slower to reach, but functionally unaffected. +- Existing precedent: `pkgs/t/tensorvia-cpu.lua` (a user's own mcpp library, no CN mirror, a single upstream string + url on all three platforms). +- Later, once access is granted or a maintainer supplies the mirror, that version's `url` can be rewritten into a + `{ GLOBAL=…, CN=… }` table (the sha256 stays the same). -如需表达“GLOBAL 与 CN 均指向上游”,应采用上述单一字符串写法,而不应写成 `{ GLOBAL=x, CN=x }` 表(其无法通过 -“CN 必须指向 gitcode”的 lint 检查)。 +If you need to express "GLOBAL and CN both point at upstream", use the single-string form above rather than a +`{ GLOBAL=x, CN=x }` table (which cannot pass the "CN must point at gitcode" lint check). -## gtc 工具 +## The gtc tool -`gtc`(`tools/gtc`,亦位于 `~/.local/bin/gtc`,python 实现,基于 gitcode API v5)。Token 位于 -`~/.config/gitcode-tool/config.json`,亦可经 `GITCODE_TOKEN` 或 `--token` 覆盖。当前登录用户为 `Sunrisepeak`。 +`gtc` (`tools/gtc`, also at `~/.local/bin/gtc`; a Python implementation over the gitcode API v5). The token lives in +`~/.config/gitcode-tool/config.json` and can be overridden through `GITCODE_TOKEN` or `--token`. The currently +logged-in user is `Sunrisepeak`. ```bash -gtc repo create / [--description …] [--private] # owner==login 时走 /user/repos,否则走 /orgs//repos;幂等(422 仅告警) +gtc repo create / [--description …] [--private] # owner==login goes through /user/repos, otherwise /orgs//repos; idempotent (422 only warns) gtc repo push / [--branch main] -gtc release create / --tag T [--name] [--body-file] [--target] [--prerelease] # 幂等(tag 已存在则跳过) -gtc release upload / --tag T # 非幂等,上传前应先查询现有资产 -gtc release publish / --tag T [--name] [--target] [--asset FILE] # 等价于 create + upload +gtc release create / --tag T [--name] [--body-file] [--target] [--prerelease] # idempotent (skips when the tag exists) +gtc release upload / --tag T # NOT idempotent; query the existing assets before uploading +gtc release publish / --tag T [--name] [--target] [--asset FILE] # equivalent to create + upload gtc pr create / --title --head --base [--body-file] ``` -`gtc` 不能创建 org;`mcpp-res` 已存在。如需新建 org,应调用 `POST /api/v5/orgs` 并同时提供 `name` 与 `path` 字段。 +`gtc` cannot create an org; `mcpp-res` already exists. To create a new org, call `POST /api/v5/orgs` and supply both +the `name` and the `path` field. -## 标准操作(以 slug=`eigen`、ver=`5.0.1` 为例) +## Standard operation (with slug=`eigen`, ver=`5.0.1` as the example) ```bash -# 0. 下载 GLOBAL 上游 tarball,计算 sha256(填入描述符三平台);重复计算两次以确认稳定 +# 0. download the GLOBAL upstream tarball and compute its sha256 (goes into all three platforms of the descriptor); +# compute it twice to confirm it is stable 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" sha256sum eigen-5.0.1.tar.gz -# 1. 建仓(幂等) +# 1. create the repository (idempotent) gtc repo create mcpp-res/eigen --description "Eigen — CN mirror for mcpp-index" -# 2. 新仓尚无分支,须先推送一个 init commit,release 方可 target main +# 2. a new repo has no branch yet, so push an init commit first — only then can a release target main mkdir eigen-init && echo "# Eigen — CN mirror" > eigen-init/README.md gtc repo push mcpp-res/eigen eigen-init --branch main -# 3. 发布 release 并上传资产(上传与 GLOBAL 相同的文件,以保证字节一致与 sha 相同) +# 3. publish the release and upload the asset (upload the same file as GLOBAL, so the bytes and the sha match) gtc release publish mcpp-res/eigen --tag 5.0.1 --name "Eigen 5.0.1" --target main --asset eigen-5.0.1.tar.gz -# 4. 闭环校验:CN 返回 200,且与 GLOBAL 字节一致 +# 4. close the loop: CN returns 200 and is byte-identical to GLOBAL CN="https://gitcode.com/mcpp-res/eigen/releases/download/5.0.1/eigen-5.0.1.tar.gz" curl -fsSL -o cn.tar.gz -w 'CN http=%{http_code}\n' "$CN" [ "$(sha256sum eigen-5.0.1.tar.gz|cut -d' ' -f1)" = "$(sha256sum cn.tar.gz|cut -d' ' -f1)" ] && echo "BYTE-IDENTICAL" ``` -## 注意事项 - -- gitcode API 限流为 25 次/分钟/用户,调用间隔宜约 3.2 秒,遇 429 应退避重试。 -- 同名 release 资产不可覆盖,误传后只能经网页删除,因此命名应一次确定(`-.`)。 -- 新仓无分支:未先推送 init commit 时,release `--target main` 将失败。 -- 内容过滤:部分库描述或仓名会被 gitcode 文本过滤拒绝,应改用中性措辞。 -- 必须上传与 GLOBAL 相同的包:不应上传经改动的包,否则 CN 与 GLOBAL 不一致,且除 `mirror-cn-reachable` 外难以察觉。 -- sha 漂移:GitLab 归档偶有重新打包导致 sha 变化,描述符的 sha 必须等于当前 GLOBAL 的实际字节(下载后即用,并重复 - 计算两次)。 -- xlings 按 sha256 全局去重:当 CN 与 GLOBAL 的 sha 相同时,本地“切换 CN 重新测试”会命中去重缓存,不会真正访问 - gitcode,因此校验 CN 应直接使用 `curl`(即上文第 4 步,亦为 CI `mirror-cn-reachable` 所执行)。 - -## CI 保障 - -`.github/workflows/validate.yml` 设有两道校验: - -- `lint` 中的 `tests/check_mirror_urls.lua`:当 url 写成表时,GLOBAL 与 CN 均须存在,且 CN 必须指向 gitcode - `mcpp-res` 镜像。 -- `mirror-cn-reachable`:抽取全部 CN url 并逐个 `curl`,任一非 200 即失败。 +## Caveats + +- The gitcode API is rate-limited to 25 calls/minute/user, so space calls about 3.2 seconds apart and back off and + retry on a 429. +- Release assets with the same name cannot be overwritten; a mistaken upload can only be deleted through the web UI, + so settle on the name in one go (`-.`). +- A new repo has no branch: without pushing an init commit first, a release with `--target main` fails. +- Content filtering: some library descriptions or repository names are rejected by gitcode's text filter — reword them + neutrally. +- You must upload the same package as GLOBAL: never upload a modified one, or CN and GLOBAL diverge in a way that is + hard to notice outside `mirror-cn-reachable`. +- sha drift: GitLab archives are occasionally repacked, which changes the sha, so the descriptor's sha must equal the + actual bytes GLOBAL serves today (use it right after downloading, and compute it twice). +- xlings deduplicates globally by sha256: when CN and GLOBAL share a sha, a local "switch to CN and re-test" hits the + dedup cache and never actually reaches gitcode — so verify CN with `curl` directly (step 4 above, which is also what + CI's `mirror-cn-reachable` does). + +## CI guarantees + +`.github/workflows/validate.yml` sets up two checks: + +- `tests/check_mirror_urls.lua` inside `lint`: when a url is written as a table, both GLOBAL and CN must be present, + and CN must point at the gitcode `mcpp-res` mirror. +- `mirror-cn-reachable`: extracts every CN url and `curl`s them one by one; anything other than 200 fails. diff --git a/docs/package-types.md b/docs/package-types.md index 3dffe24..d348f49 100644 --- a/docs/package-types.md +++ b/docs/package-types.md @@ -1,115 +1,125 @@ -# 库形态与描述符模板 +# Library shapes and descriptor templates -编写描述符前,应先判定库所属的形态,再选用对应模板。`mcpp = {}` 内的所有路径均为**相对 verdir 的 GLOB**: -前导 `*` 用于吸收 tarball 的 `-/` wrap 层;`*` 匹配单段,`**` 匹配跨段(例如 `*/blas/*.cpp` 合法)。 +**English** | [简体中文](zh/package-types.md) -A–D 是四种**基础**形态,先按它们判定;E–G 是在基础形态之上叠加的处理方式,按需组合。 +Before writing a descriptor, work out which shape the library belongs to, then pick the matching template. Every path +inside `mcpp = {}` is a **GLOB relative to verdir**: a leading `*` absorbs the `-/` wrap layer of a tarball; +`*` matches a single segment and `**` matches across segments (so `*/blas/*.cpp` is legal). -| 形态 | 特征 | 样例 | 关键字段 | +A–D are the four **basic** shapes — judge by them first; E–G are treatments layered on top of a basic shape, to be +combined as needed. + +| Shape | Characteristics | Samples | Key fields | |---|---|---|---| -| **A. C 源码 compat** | 纯 C 或少量源码,用户 `#include ` | `pkgs/c/compat.cjson.lua`、`compat.zlib.lua`、`compat.gtest.lua` | `sources` 与 `c_standard` | -| **B. header-only** | 纯头文件,无需编译 | `pkgs/c/compat.eigen.lua`、`compat.opengl.lua`、`compat.khrplatform.lua` | `include_dirs` 与 anchor 源 | -| **C. C++23 module** | 暴露 `import x.y;` | `pkgs/n/nlohmann.json.lua` | `modules` 与 `generated_files` 或源 `.cppm` | -| **D. 外部 Form-A 模块仓** | 上游自带 mcpp 描述符,独立仓库 | `pkgs/i/imgui.lua`、`pkgs/m/mcpplibs.*` | `mcpp = ""`(Form A) | -| **E. 生成 config 的全源码直编** | 上游用 configure/CMake 生成配置头,此处以 `generated_files` 落一份快照 | `pkgs/c/compat.libpng.lua`、`compat.curl.lua`、`compat.sdl2.lua`、`compat.ffmpeg.lua` | `generated_files` + `include_dirs` | -| **F. 共享库 compat** | 必须是**唯一**的那个 `.so`(会被第三方 `dlopen`) | `pkgs/c/compat.x11.lua` 等 X11 家族、`compat.vulkan.lua`(linux) | `targets = { kind = "shared", soname = … }` | -| **G. 宿主运行时适配** | 驱动之类无法 vendor 的东西,只做符号链接农场 + 元数据 | `pkgs/c/compat.glx-runtime.lua`、`compat.vulkan-runtime.lua` | `runtime.library_dirs` / `capabilities` | +| **A. C-source compat** | plain C or a handful of sources; the user writes `#include ` | `pkgs/c/compat.cjson.lua`, `compat.zlib.lua`, `compat.gtest.lua` | `sources` and `c_standard` | +| **B. header-only** | headers only, nothing to compile | `pkgs/c/compat.eigen.lua`, `compat.opengl.lua`, `compat.khrplatform.lua` | `include_dirs` and an anchor source | +| **C. C++23 module** | exposes `import x.y;` | `pkgs/n/nlohmann.json.lua` | `modules` plus `generated_files` or a source `.cppm` | +| **D. External Form-A module repo** | upstream ships its own mcpp descriptor in a separate repository | `pkgs/i/imgui.lua`, `pkgs/m/mcpplibs.*` | `mcpp = ""` (Form A) | +| **E. Whole-source direct build with a generated config** | upstream generates its config header through configure/CMake; here a snapshot of it lands in `generated_files` | `pkgs/c/compat.libpng.lua`, `compat.curl.lua`, `compat.sdl2.lua`, `compat.ffmpeg.lua` | `generated_files` + `include_dirs` | +| **F. Shared-library compat** | has to be the **only** copy of that `.so` in the process (third parties `dlopen` it) | the X11 family such as `pkgs/c/compat.x11.lua`, and `compat.vulkan.lua` (linux) | `targets = { kind = "shared", soname = … }` | +| **G. Host runtime adaptation** | things that cannot be vendored, such as drivers — only a symlink farm plus metadata | `pkgs/c/compat.glx-runtime.lua`, `compat.vulkan-runtime.lua` | `runtime.library_dirs` / `capabilities` | -完整的样例索引见[根 README 的「参考示例」表](../README.md#参考示例lua-描述符)。 +For the complete sample index, see the +[Reference examples table in the root README](../README.md#reference-examples-lua-descriptors). -A、B、C 三类共用的骨架(`package` 头与 `xpm`)如下: +Shapes A, B and C share this skeleton (the `package` header and `xpm`): ```lua package = { spec = "1", - namespace = "compat", -- 点分层级路径;compat / nlohmann / mcpplibs 等,决定 import 前缀与依赖 key - name = "", -- 单一原子段,不重复 namespace(SPEC-001 §3.2) + namespace = "compat", -- dotted hierarchical path; compat / nlohmann / mcpplibs / …, decides the import prefix and the dependency key + name = "", -- a single atomic segment; never repeats the namespace (SPEC-001 §3.2) description = "…", licenses = {"MIT"}, -- SPDX repo = "https://…", type = "package", - xpm = { -- 三平台均需声明;纯源码或纯头时三平台共用同一 url 与 sha256 + xpm = { -- all three platforms must be declared; pure-source or pure-header packages share one url and sha256 across them linux = { ["1.2.3"] = { url = { GLOBAL = "https://…/v1.2.3.tar.gz", CN = "https://gitcode.com/mcpp-res//releases/download/1.2.3/-1.2.3.tar.gz" }, - sha256 = "<计算所得>" } }, + sha256 = "" } }, macosx = { ["1.2.3"] = { url = { GLOBAL = "…", CN = "…" }, sha256 = "…" } }, windows = { ["1.2.3"] = { url = { GLOBAL = "…", CN = "…" }, sha256 = "…" } }, }, - mcpp = { … 见下文各形态 … }, + mcpp = { … see each shape below … }, } ``` -身份是 `(namespace, name)` 二元组:层级一律放 `namespace`,`name` 只写一段。文件名不参与解析,推荐 -`pkgs/<首字母>/..lua`(命中 mcpp 的快路径)。详见 -[仓库结构与 schema](repository-and-schema.md#包身份namespace-name)。 +Identity is the `(namespace, name)` pair: hierarchy always goes in `namespace`, and `name` carries exactly one segment. +The file name plays no part in resolution; `pkgs//..lua` is recommended (it hits mcpp's fast +path). See [Repository layout and schema](repository-and-schema.md#package-identity-namespace-name) for details. --- -## A. C 源码 compat(`compat.cjson` / `compat.zlib`) +## A. C-source compat (`compat.cjson` / `compat.zlib`) -将 C 源码编译为 lib,头文件经 `include_dirs` 暴露,可选组件由 `features` 门控。 +Compile the C sources into a lib, expose the headers through `include_dirs`, and gate optional components behind +`features`. ```lua mcpp = { - language = "c++23", -- 与既有 compat 对齐;实际的 C 行为由 c_standard 决定 + language = "c++23", -- aligned with the existing compat packages; actual C behavior comes from c_standard import_std = false, - c_standard = "c99", -- 或 c11 - include_dirs = { "*" }, -- 暴露顶层头文件(*/foo.h) - sources = { "*/cJSON.c" }, -- 核心源码,始终编译 + c_standard = "c99", -- or c11 + include_dirs = { "*" }, -- expose the top-level headers (*/foo.h) + sources = { "*/cJSON.c" }, -- core sources, always compiled targets = { ["cjson"] = { kind = "lib" } }, - features = { -- 可选扩展,默认不编译 + features = { -- optional extras, not compiled by default ["utils"] = { sources = { "*/cJSON_Utils.c" } }, }, deps = { }, } ``` -要点:多源码时可逐个列出(`compat.zlib` 列出了 15 个 `.c`)或使用 glob;需要配置头时可用 `generated_files` 合成 -(`compat.zlib` 使用 `mcpp_generated/include/mcpp_zlib_config.h` 配合 `cflags = {"-include …"}`)。 +Notes: with many sources you can either list them one by one (`compat.zlib` lists 15 `.c` files) or use a glob; when a +config header is needed, synthesize it with `generated_files` (`compat.zlib` uses +`mcpp_generated/include/mcpp_zlib_config.h` together with `cflags = {"-include …"}`). -## B. header-only(`compat.eigen` / `compat.opengl`) +## B. header-only (`compat.eigen` / `compat.opengl`) -此类库无可编译源码:由 `include_dirs` 暴露头文件,并加入一个 trivial anchor `.c`,以提供一个可构建的 lib 目标。 +These libraries have no compilable sources: `include_dirs` exposes the headers, and a trivial anchor `.c` is added so +there is a buildable lib target. ```lua mcpp = { language = "c++23", import_std = false, c_standard = "c11", - include_dirs = { "*" }, -- 或更精确的 "*/include" / "*/api" + include_dirs = { "*" }, -- or something tighter, "*/include" / "*/api" generated_files = { ["mcpp_generated/_anchor.c"] = "int mcpp_compat__anchor(void) { return 0; }\n", }, sources = { "mcpp_generated/_anchor.c" }, targets = { [""] = { kind = "lib" } }, - -- 若存在额外可编译源码的组件(非纯头),可实现为 source-gated feature: + -- a component that does carry extra compilable sources (i.e. is not header-only) can become a source-gated feature: features = { - ["blas"] = { sources = { "*/blas/*.cpp", "*/blas/f2c/*.c" } }, -- eigen 实例 + ["blas"] = { sources = { "*/blas/*.cpp", "*/blas/f2c/*.c" } }, -- the eigen case }, deps = { }, } ``` -注意:纯头形式的可选项无法隐藏(与核心共享 include 根),因此不应为其勉强构造 feature;只有额外可编译源码才能被门控 -(`compat.eigen` 的 `blas` 即由 C++ 与 f2c 转换的 C 构成,不依赖 Fortran,因此可门控)。 +Careful: an optional part that is header-only cannot be hidden (it shares the include root with the core), so do not +force a feature around it; only extra compilable sources can be gated (`compat.eigen`'s `blas` consists of C++ and +f2c-converted C, needs no Fortran, and therefore can be gated). -## C. C++23 module(`nlohmann.json`) +## C. C++23 module (`nlohmann.json`) -使用户可 `import x.y;`。有两种实现路径: +Lets users write `import x.y;`. There are two ways to get there: -1. **上游已自带 `.cppm`**:直接 `sources = { "*/path/to/unit.cppm" }`。 -2. **上游 release 不含**(较常见):以 `generated_files` 合成 wrapper(`#include
`、`export module x.y;`、 - `export using …`),基底头 pin 至已发布 tag。应逐字复用上游官方 wrapper,而非自行推断符号清单。 +1. **Upstream already ships a `.cppm`**: point straight at it with `sources = { "*/path/to/unit.cppm" }`. +2. **The upstream release does not include one** (the common case): synthesize a wrapper through `generated_files` + (`#include
`, `export module x.y;`, `export using …`), with the base header pinned to a published tag. + Reuse upstream's official wrapper verbatim rather than inferring the symbol list yourself. ```lua mcpp = { schema = "0.1", language = "c++23", - import_std = false, -- wrapper 含上游头,启用 import std 易产生冲突 + import_std = false, -- the wrapper includes upstream headers, and enabling import std invites conflicts modules = { "nlohmann.json" }, - include_dirs = { "*/single_include" }, -- 使 wrapper 内的 #include <…> 可解析 + include_dirs = { "*/single_include" }, -- makes the #include <…> inside the wrapper resolvable generated_files = { ["mcpp_generated/nlohmann.json.cppm"] = "module;\n#include \nexport module nlohmann.json;\n…", }, @@ -119,50 +129,56 @@ mcpp = { } ``` -注意:mcpp 段解析器不支持 Lua 长括号 `[[ … ]]`,`generated_files` 的内容必须采用双引号字符串并对 `\n`、`\"` -转义,否则报 `malformed mcpp segment`。消费侧不应将 `import x.y;` 与文本 `#include ` 混用(会与 GCC -modules 冲突),应配合 `import std;`。 +Careful: the mcpp segment parser does not support Lua long brackets `[[ … ]]`, so `generated_files` content must use +double-quoted strings with `\n` and `\"` escaped — otherwise you get `malformed mcpp segment`. On the consumer side, +do not mix `import x.y;` with a textual `#include ` (it conflicts with GCC modules); pair it with `import std;` +instead. -## D. 外部 Form-A 模块仓(`imgui` / `mcpplibs.*`) +## D. External Form-A module repo (`imgui` / `mcpplibs.*`) -上游或独立仓库自带 mcpp 描述符,本仓仅充当指针:`mcpp = "<相对或远程路径>"`(Form A,而非内联的 Form B)。新增的 -独立库通常归属于另一仓库(如 `mcpplibs/imgui-m`),本仓只负责登记。写法可参照 `pkgs/i/imgui.lua` 与 -`pkgs/x/xpkg.lua`。 +Upstream, or a separate repository, ships its own mcpp descriptor, and this repository is only a pointer: +`mcpp = ""` (Form A, rather than the inline Form B). A newly added standalone library usually +belongs to another repository (`mcpplibs/imgui-m`, for example) and this repository only registers it. See +`pkgs/i/imgui.lua` and `pkgs/x/xpkg.lua` for how to write one. --- -## E. 生成 config 的全源码直编(`compat.curl` / `compat.sdl2`) +## E. Whole-source direct build with a generated config (`compat.curl` / `compat.sdl2`) -上游用 configure 或 CMake 生成一份配置头,而本仓要的是「列出 .c 文件」。可行的前提是这类库把**未选中的后端 -编成空 TU**(curl 的 `vtls/gtls.c` 从头到尾是 `#ifdef USE_GNUTLS`,SDL 的 `src/video/windows/*.c` 同理),于是 -源码列表可以是朴素的 glob,配置全部落在一份 `generated_files` 快照里。 +Upstream generates a config header through configure or CMake, whereas what this repository wants is "list the .c +files". That works because libraries of this kind compile the **unselected backends into empty TUs** (curl's +`vtls/gtls.c` is one big `#ifdef USE_GNUTLS` from top to bottom, and SDL's `src/video/windows/*.c` likewise), so the +source list can be a plain glob and all the configuration lands in one `generated_files` snapshot. -只在**上游有缺口的平台**生成:curl 签入了 `lib/config-win32.h`(Windows 无需生成),SDL 签入了 -`SDL_config_windows.h` / `SDL_config_macosx.h`(只有 linux 落到无用的 `SDL_config_minimal.h`)。生成时务必 -用**本索引的工具链**跑 configure —— 用宿主 `cc` 生成的 curl 配置曾断言 `ssize_t` 不存在,导致 curl 编不过自己 -的配置。 +Generate one only on the platforms where **upstream leaves a gap**: curl checks in `lib/config-win32.h` (Windows needs +nothing generated), and SDL checks in `SDL_config_windows.h` / `SDL_config_macosx.h` (only linux falls through to the +useless `SDL_config_minimal.h`). When generating, be sure to run configure with **this index's toolchain** — a curl +config generated with the host `cc` once asserted that `ssize_t` does not exist, and curl then failed to compile +against its own config. -## F. 共享库 compat(`compat.x11` 家族 / `compat.vulkan`) +## F. Shared-library compat (the `compat.x11` family / `compat.vulkan`) -当这个库会被第三方 `dlopen` 时,它必须是进程里**唯一**的那一个,静态链接会出问题。 +When a library gets `dlopen`ed by third parties, it must be the **only** copy in the process; static linking breaks. ```lua targets = { ["vulkan"] = { kind = "shared", soname = "libvulkan.so.1" } }, ``` -`soname` 不是可选项:SDL2 的 `SDL_CreateWindow(SDL_WINDOW_VULKAN)` 会 `dlopen("libvulkan.so.1")` 并用它解析 -surface 创建。若 loader 是静态的,应用最终会有两个 loader —— 自己那份建 instance,SDL 那份建 surface —— -`createSurface` 拿到一个对方没见过的 instance 而失败。 +`soname` is not optional: SDL2's `SDL_CreateWindow(SDL_WINDOW_VULKAN)` calls `dlopen("libvulkan.so.1")` and resolves +surface creation through it. With a static loader, the application ends up with two loaders — its own creating the +instance and SDL's creating the surface — and `createSurface` fails on an instance the other side has never seen. -声明位置也有讲究:`kind = "shared"` 会把 `-fPIC` 传播给消费者,而 clang 对 msvc 目标直接拒绝该选项。因此 -`compat.vulkan` 把它写在 **linux 块内**,Windows 走另一套(链接预生成的 import library)。平台块里的 `targets` -会覆盖顶层声明,`compat.ffmpeg` 亦如此。 +Where you declare it matters too: `kind = "shared"` propagates `-fPIC` to consumers, and clang rejects that option +outright for msvc targets. So `compat.vulkan` declares it **inside the linux block**, while Windows takes another +route (linking a pre-generated import library). A `targets` inside a platform block overrides the top-level +declaration; `compat.ffmpeg` does the same. -## G. 宿主运行时适配(`compat.glx-runtime` / `compat.vulkan-runtime`) +## G. Host runtime adaptation (`compat.glx-runtime` / `compat.vulkan-runtime`) -GPU 驱动无法打包 —— ICD 必须匹配机器上的内核驱动。本仓的既定立场是把它建模为**宿主能力**,而不是假装厂商 -驱动是可再分发的普通包(见 `.agents/docs/2026-06-03-gl-runtime-packages-plan.md`)。这类包不 vendor 任何东西, -只做符号链接农场加元数据: +GPU drivers cannot be packaged — an ICD has to match the kernel driver on the machine. This repository's settled +position is to model that as a **host capability** rather than pretend vendor drivers are ordinary redistributable +packages (see `.agents/docs/2026-06-03-gl-runtime-packages-plan.md`). Packages of this kind vendor nothing and are +just a symlink farm plus metadata: ```lua runtime = { @@ -171,22 +187,24 @@ runtime = { }, ``` -之所以需要它:mcpp 的产物跑在**自带的 glibc** 下(`interp` 指向 `xim-x-glibc`,rpath 只覆盖 mcpp 自己的树), -因此裸 soname 的 `dlopen` 根本不搜索宿主库路径 —— loader 能找到全部 ICD manifest,却一个驱动都打不开。 +Why it is needed: mcpp's binaries run under a **bundled glibc** (`interp` points at `xim-x-glibc`, and the rpath only +covers mcpp's own tree), so a bare-soname `dlopen` does not search the host library paths at all — the loader finds +every ICD manifest and yet cannot open a single driver. -两个反复踩到的细节: +Two details that keep biting: -- **农场里只放带版本号的 soname**(`lib*.so.*`)。`runtime.library_dirs` 同时进**链接行**,一个裸 `libxcb.so` - 会遮蔽本仓自己的 `compat.xcb`,链接报 `undefined reference to XauDisposeAuth`(mcpp#304)。带版本号的名字对 - 链接器不可见,而恰好是 `dlopen` 要的。 -- **闭包必须完整**。农场里有 `libxcb.so.1` 却没有它依赖的 `libXau.so.6`,会遮蔽掉本来能解析的宿主副本,可执行 - 文件直接起不来。 +- **Put only versioned sonames in the farm** (`lib*.so.*`). `runtime.library_dirs` also joins the **link line**, so a + bare `libxcb.so` shadows this repository's own `compat.xcb` and the link fails with + `undefined reference to XauDisposeAuth` (mcpp#304). Versioned names are invisible to the linker and are exactly what + `dlopen` asks for. +- **The closure has to be complete.** A farm holding `libxcb.so.1` but not the `libXau.so.6` it depends on shadows the + host copy that would otherwise have resolved, and the executable simply fails to start. --- -## 最小工程(`tests/examples//`) +## The minimal project (`tests/examples//`) -`mcpp.toml`(短式依赖与长式依赖二选一): +`mcpp.toml` (pick either the short or the long dependency form): ```toml [package] @@ -194,15 +212,16 @@ name = "-example" version = "0.1.0" [toolchain] default = "gcc@16.1.0" -# `compat` 由 workspace 根的 [indices] 继承,成员无需再写; -# 消费其他命名空间时才在此声明,例如 `[indices] fmtlib = { path = "../../.." }` -# —— 成员级声明会**替换**根级表而非与之合并(这正是保持单个项目索引 repo 的方式)。 +# `compat` is inherited from the workspace root's [indices], so members need not repeat it; +# declare one here only when consuming another namespace, e.g. `[indices] fmtlib = { path = "../../.." }` +# — a member-level declaration **replaces** the root-level table rather than merging with it +# (which is exactly how a single project index repo is maintained). [dependencies.compat] - = "1.2.3" # 或: = { version = "1.2.3", features = ["…"] } + = "1.2.3" # or: = { version = "1.2.3", features = ["…"] } [targets.-example] kind = "bin" -main = "src/main.cpp" # C 库可使用 .c +main = "src/main.cpp" # C libraries may use .c ``` -`src/main.cpp` 应包含有效断言并 `return ok ? 0 : 1`,而非仅打印输出。module 库使用 `import std; import x.y;`; -header-only 与 C 库使用文本 `#include`。 +`src/main.cpp` should carry real assertions and `return ok ? 0 : 1`, not merely print output. Module libraries use +`import std; import x.y;`; header-only and C libraries use textual `#include`. diff --git a/docs/repository-and-schema.md b/docs/repository-and-schema.md index abebf9d..7ddf60a 100644 --- a/docs/repository-and-schema.md +++ b/docs/repository-and-schema.md @@ -1,135 +1,179 @@ -# 仓库结构、schema、CI 与关键文件 +# Repository layout, schema, CI and key files -## 仓库布局 +**English** | [简体中文](zh/repository-and-schema.md) + +## Repository layout ``` -pkgs//.lua 描述符。 取完整包名首字母(compat.* → c,nlohmann.json → n,imgui → i) -mcpp.toml workspace 清单(members 列表)+ 根级 [indices] compat = { path = "." }, - 由成员继承(相对路径按 workspace 根解析,mcpp ≥ 0.0.97) -tests/examples// 每库测试工程(workspace 成员; 为包名去前缀,模块包为 - mcpp.toml -module)。消费 compat 的成员不写 [indices];消费其他 - 命名空间的成员写恰好一条(模块包用 default),该声明**替换** - 根级表而非合并 —— 每个成员最多一个项目级索引 repo 是硬约束, - 详见下文「索引重定向」。依赖按平台自门控 +pkgs//.lua descriptors. is the initial of the full package name (compat.* → c, nlohmann.json → n, imgui → i) +mcpp.toml workspace manifest (the members list) + the root-level [indices] compat = { path = "." }, + inherited by members (relative paths resolve against the workspace root, mcpp >= 0.0.97) +tests/examples// one test project per library (a workspace member; is the package name minus its + mcpp.toml prefix, or -module for module packages). Members consuming compat write no + [indices]; members consuming another namespace write exactly one (module packages use + default), and that declaration **replaces** the root-level table rather than merging with + it — at most one project-level index repo per member is a hard constraint, see + "Index redirection" below. Dependencies gate themselves per platform ([target.'cfg(...)']) - tests/*.cpp 行为断言(独立 main,退出码非 0 即失败) -tests/check_mirror_urls.lua lint:GLOBAL+CN 表完整性,以及 CN 指向 mcpp-res -tests/check_package_name.lua lint:身份形态(name 为单一原子段,层级归 namespace) -tests/list_cn_urls.lua 抽取 CN url,供 mirror-cn-reachable 使用 -README.md 索引说明与贡献入口 -.github/workflows/validate.yml CI:lint / mirror-cn-reachable / workspace(3 平台矩阵) -.agents/docs/-*.md 设计文档惯例 -docs/ 贡献者参考文档(本目录) -tools/gtc gitcode CLI,见 cn-mirror.md -tools/compat-ffmpeg/ 等 compat 大包的描述符再生成流水线 -.xpkgindex.json 站点配置(标题、链接、install 模板),通常无需改动 + tests/*.cpp behavioral assertions (standalone main; a non-zero exit code is a failure) +tests/check_mirror_urls.lua lint: GLOBAL+CN table completeness, and that CN points at mcpp-res +tests/check_package_name.lua lint: identity shape (name is a single atomic segment, hierarchy belongs to namespace) +tests/list_cn_urls.lua extracts the CN urls for mirror-cn-reachable +README.md index overview and contribution entry point (README.zh-CN.md is the Chinese version) +.github/workflows/validate.yml CI: lint / mirror-cn-reachable / workspace (a 3-platform matrix) +.agents/docs/-*.md the design-document convention +docs/ contributor reference documentation (this directory; docs/zh/ holds the Chinese version) +tools/gtc the gitcode CLI, see cn-mirror.md +tools/compat-ffmpeg/ etc. descriptor regeneration pipelines for the large compat packages +.xpkgindex.json site configuration (title, links, install template); rarely needs changing ``` -## 外部仓库与文档 +## External repositories and documentation -- mcpp 本体:https://github.com/mcpp-community/mcpp(本地通常存在 clone: - `/home/speak/workspace/github/mcpp-community/mcpp`)。`mcpp --version` 应与 CI 对齐;feature 与 glob 行为以 - `src/manifest.cppm`、`src/modgraph/scanner.cppm`、`src/build/prepare.cppm` 为准。 -- xpkg 扩展 schema(权威): - https://github.com/mcpp-community/mcpp/tree/main/docs/spec(对应本仓 `.xpkgindex.json` 的 - “mcpp ext” 链接)。V1 xpkg spec 见 `d2learn/xim-pkgindex` 的 `docs/V1/xpackage-spec.md`(url-template 约在第 172 行)。 -- CN 镜像组织:gitcode `mcpp-res`。 +- mcpp itself: https://github.com/mcpp-community/mcpp (a local clone usually exists at + `/home/speak/workspace/github/mcpp-community/mcpp`). `mcpp --version` should match CI; `src/manifest.cppm`, + `src/modgraph/scanner.cppm` and `src/build/prepare.cppm` are authoritative for feature and glob behavior. +- The xpkg extension schema (authoritative): + https://github.com/mcpp-community/mcpp/tree/main/docs/spec (the "mcpp ext" link in this + repository's `.xpkgindex.json`). For the V1 xpkg spec see `docs/V1/xpackage-spec.md` in `d2learn/xim-pkgindex` + (url-template is around line 172). +- The CN mirror organization: gitcode `mcpp-res`. -## 描述符 schema 速查(Form B inline) +## Descriptor schema cheat-sheet (Form B inline) -`package` 必填字段:`spec`、`namespace`、`name`、`description`、`licenses`、`repo`、`type="package"`、`xpm`、`mcpp`。 +Required `package` fields: `spec`, `namespace`, `name`, `description`, `licenses`, `repo`, `type="package"`, `xpm`, +`mcpp`. -### 包身份:`(namespace, name)` +### Package identity: `(namespace, name)` -身份是二元组 —— **`namespace` 是点分层级路径,`name` 是单一原子段**。层级一律放 `namespace`(mcpp SPEC-001 §3.2,见 mcpp 仓的 [`docs/spec/package-identity.md`](https://github.com/mcpp-community/mcpp/blob/main/docs/spec/package-identity.md)): +Identity is a pair — **`namespace` is a dotted hierarchical path, `name` is a single atomic segment**. Hierarchy always +goes in `namespace` (mcpp SPEC-001 §3.2, see +[`docs/spec/package-identity.md`](https://github.com/mcpp-community/mcpp/blob/main/docs/spec/package-identity.md) in +the mcpp repository): ```lua namespace = "compat", name = "zlib" -- ✅ -namespace = "mcpplibs.capi", name = "lua" -- ✅ 多级命名空间 -namespace = "mcpplibs", name = "capi.lua" -- ❌ 短名仍带点 +namespace = "mcpplibs.capi", name = "lua" -- ✅ multi-level namespace +namespace = "mcpplibs", name = "capi.lua" -- ❌ the short name still carries a dot ``` -最后一种被拒绝而非重新解读:`name` 里多出的点描述的是一个**没人声明过的命名空间**。mcpp 曾按最后一个点切分、静默造出 `(mcpplibs.capi, lua)`,0.0.106 起改为拒绝。 +The last one is rejected rather than reinterpreted: the extra dot inside `name` describes a namespace **nobody ever +declared**. mcpp used to split on the last dot and silently invent `(mcpplibs.capi, lua)`; since 0.0.106 it rejects. -**兼容形态**:SPEC-001 之前发布的描述符把命名空间重复写在 `name` 里(`namespace="compat", name="compat.zlib"`),仍被接受 —— 前缀会先剥离再判定,wire key 是字面 `name`,两种写法都可安装。本仓已统一迁到短名形态。 +**The compatibility shape**: descriptors published before SPEC-001 repeat the namespace inside `name` +(`namespace="compat", name="compat.zlib"`) and are still accepted — the prefix is stripped before the check, the wire +key is the literal `name`, and both spellings install. This repository has been migrated wholesale to the short form. -**同短名不同命名空间可共存**:本仓现有三对 —— `compat:imgui` 与默认命名空间的 `imgui`、`compat:ffmpeg` 与 `ffmpeg`、`compat:lua` 与 `mcpplibs.capi:lua`。需要 xlings ≥ 0.4.69([xlings#381](https://github.com/openxlings/xlings/issues/381));`(namespace, name)` 唯一即可,`name` 本身不必唯一。 +**The same short name can coexist across namespaces**: there are three such pairs here today — `compat:imgui` and the +default namespace's `imgui`, `compat:ffmpeg` and `ffmpeg`, `compat:lua` and `mcpplibs.capi:lua`. This needs +xlings >= 0.4.69 ([xlings#381](https://github.com/openxlings/xlings/issues/381)); `(namespace, name)` has to be +unique, `name` alone does not. -**文件名不参与解析**,可以任意。推荐 `.lua` 或 `..lua`(命中 mcpp 的快路径),但描述符按**声明的身份**被发现,叫别的名字也能解析。 +**File names play no part in resolution** and can be anything. `.lua` or `..lua` is recommended +(it hits mcpp's fast path), but descriptors are discovered by the **identity they declare**, so another name still +resolves. -`xpm..<裸版本>`: +`xpm..`: -- `url`:字符串,或 `{ GLOBAL=…, CN=… }` 表(本仓统一使用表形式)。 -- `sha256`:必填,等于实际下载字节的摘要。 +- `url`: a string, or a `{ GLOBAL=…, CN=… }` table (this repository uses the table form throughout). +- `sha256`: required, and equal to the digest of the actual downloaded bytes. -`mcpp`(常用键): +`mcpp` (common keys): -| 键 | 说明 | +| Key | Description | |---|---| -| `language` | 通常为 `"c++23"` | -| `import_std` | 多数为 `false` | -| `c_standard` | C 源码:`"c99"` 或 `"c11"` | -| `modules` | module 库:`{ "x.y" }` | -| `include_dirs` | glob 列表,暴露给消费者的头目录 | -| `generated_files` | `{ ["相对路径"]="内容字符串" }`;mcpp ≥ 0.0.85 支持 Lua 长括号 `[==[…]==]` 多行字符串(推荐,可读可 review);转义单行串仍兼容 | -| `scan_overrides` | `{ ["glob"]={ provides={…}, imports={…} } }`;声明式扫描结果,命中文件跳过 M1 文本扫描(适用于带条件 import 守卫的上游模块单元,如 fmt 的 src/fmt.cc);构建期由编译器 P1689 输出自动对账,声明错误响亮失败(mcpp ≥ 0.0.85)| -| `sources` | glob 列表,编入 lib 的源码 | -| `cflags` / `cxxflags` / `ldflags` | 追加至对应规则 | +| `language` | usually `"c++23"` | +| `import_std` | mostly `false` | +| `c_standard` | for C sources: `"c99"` or `"c11"` | +| `modules` | for module libraries: `{ "x.y" }` | +| `include_dirs` | glob list; the header directories exposed to consumers | +| `generated_files` | `{ ["relative/path"]="content string" }`; mcpp >= 0.0.85 supports Lua long-bracket `[==[…]==]` multi-line strings (recommended — readable and reviewable), and escaped single-line strings still work | +| `scan_overrides` | `{ ["glob"]={ provides={…}, imports={…} } }`; declarative scan results — a matching file skips the M1 text scan (for upstream module units with conditional import guards, such as fmt's src/fmt.cc). Reconciled automatically against the compiler's P1689 output at build time, so a wrong declaration fails loudly (mcpp >= 0.0.85) | +| `sources` | glob list; the sources compiled into the lib | +| `cflags` / `cxxflags` / `ldflags` | appended to the corresponding rule | | `targets` | `{ ["name"]={ kind="lib"/"bin", main=…, soname=… } }` | -| `features` | `{ ["f"]={ sources={…}, defines={…}, deps={…}, implies={…}, requires={…} } }`;`defines` 只作用于**包自身**的 TU,消费端若要按 feature 分支须自行声明(见 `tests/examples/openssl`、`openblas` 的 `[target.'cfg(…)'.build] cxxflags`) | -| `deps` | `{ ["ns.name"]="ver" }`,扁平或点号式;feature 内同形 | - -## 索引重定向(`[indices]`) - -测试面要验证的是 **checkout 里的描述符**,而不是已发布的远程索引,这靠 `[indices]` 把命名空间重定向到本仓完成。 - -**根级继承**:workspace 根的 `mcpp.toml` 声明 `[indices] compat = { path = "." }`,相对路径按 **workspace 根**解析(mcpp ≥ 0.0.97,[mcpp#224](https://github.com/mcpp-community/mcpp/issues/224)),成员直接继承,不必各写一份 `path = "../../.."`。 - -**为什么只有一条,而且是 `compat`**: - -- 索引表**按命名空间取键**。声明在一个没有任何依赖会请求的名字下,该索引根本不会被注册,解析会静默回落到已发布的远程索引 —— 此时被测的根本不是这个 checkout。 -- 同一路径声明成多个命名空间确实都会注册,但会变成 N 个各自独立的项目 repo,之后任何查找都以 N 路歧义失败(物理上是同一个描述符;[mcpp#238](https://github.com/mcpp-community/mcpp/issues/238) / [xlings#374](https://github.com/openxlings/xlings/issues/374),在 xlings 0.4.69 后由静默 exit 1 变为响亮报错)。 - -所以根级只能承载一个命名空间,`compat` 是收益最大的那个(13 个成员 vs 其余合计 10 个)。 - -**成员级覆盖**:消费其他命名空间的成员自己声明 `[indices]`,该表**替换**继承来的根级表而非与之合并 —— 这正是每个成员只保留一个项目索引 repo 的机制。 - -**跨命名空间的取舍**:一个成员无法同时从本 checkout 解析两个命名空间。`tests/examples/asio-ssl` 有意利用了这一点:它不写成员级声明、继承根级 `compat`,于是 asio 本身走已发布的远程索引,而它的 `ssl` feature 依赖 `compat.openssl` 从本 checkout 解析 —— 这样**未合并的 compat 描述符可以通过一个已发布的消费者去验证**。反过来,本地 asio 描述符由 `tests/examples/asio-module` 覆盖。 - -**裸名依赖不适用**:重定向按**请求侧**的命名空间取键,而裸写的 `eigen = "5.0.1"` 是以默认命名空间发出的请求,即使最终落到 `compat` 描述符上,也会从远程索引解析。因此各成员一律使用限定写法;裸名解析本身由 mcpp 上游的 e2e 165 覆盖。 - -## index 版本契约(index.toml) - -仓库根的 `index.toml` 声明 `[index] min_mcpp` —— 能解析本索引全部描述符的最老 -mcpp 版本。契约随树旅行:`publish_mcpp_index.sh` 把它打进 artifact,git clone 与 -`[indices] path =` 本地索引天然携带。mcpp ≥ 0.0.85 在打开索引树时检查,违反时报 -`E0006` + 升级指引(调试逃生口 `MCPP_INDEX_FLOOR=ignore`)。 - -规则(由 lint 机械强制,非纪律):**floor 先行、新文法在后**——lint 用 CI pin 的 -mcpp 跑 `xpkg parse`(strict:未知键即失败),所以需要更新文法/键的描述符在 -`MCPP_VERSION` 与 `min_mcpp` 同步提升之前物理上合不进 main。本地复现: -`mcpp xpkg parse pkgs//.lua`。 - -## CI 行为(validate.yml) - -- 触发条件:PR(改动 `pkgs/**/*.lua`、`tests/**`、`README.md` 或本 workflow)、push 至 main、nightly cron、手动触发。 -- `env.MCPP_VERSION` 为全部 job 使用的 mcpp 版本,本地验证应与之对齐。 -- `lint`(始终运行):lua 语法 `loadfile(f,'t')`;须含 `spec=`/`name=`/`xpm=`;禁止前导 v 版本;执行 - `check_mirror_urls.lua`;执行 `check_package_name.lua`(身份形态,见上文「包身份」);再用 CI pin 的 - mcpp 对每个描述符跑 `mcpp xpkg parse`(strict,未知键即失败)。mcpp ≥ 0.0.106 的 `xpkg parse` 自身 - 也强制身份形态,lua lint 因此是更早、更便宜的冗余闸门。 -- `mirror-cn-reachable`(始终运行):逐个 `curl` CN url,均须返回 200。 -- `workspace (linux|macos|windows)`:整个测试面就是一个 mcpp workspace,**唯一的构建/运行通道**—— - 没有任何 shell 驱动的例外(公开模块包 imgui/ffmpeg/opencv/tinyhttps 也是普通成员,经成员级 - `[indices] default = { path = "../../.." }` 从 checkout 解析,mcpp ≥ 0.0.97;消费 `compat` 的 - 成员则继承根级声明,见上文「索引重定向」)。 - - 选择性成员测试:PR 时由 `git diff` 将改动文件映射到受影响成员 - (`pkgs//.lua` → mcpp.toml 引用 `` 的成员;`tests/examples//**` → 成员 ``), - 仅 `mcpp test -p ` 这些成员;workflow 本身、workspace 清单非成员部分、`tools/` 等 - 全局性改动 → `mcpp test --workspace` 全量。push/nightly/dispatch 恒为全量。 - - `~/.mcpp/registry` 缓存携带工具链与已构建的 compat 包,重复运行增量很快。 - -## 本地 lint 复现(等价于 CI lint job) +| `features` | `{ ["f"]={ sources={…}, defines={…}, deps={…}, implies={…}, requires={…} } }`; `defines` applies only to the **package's own** TUs, so a consumer that wants to branch on a feature must declare it itself (see the `[target.'cfg(…)'.build] cxxflags` in `tests/examples/openssl` and `openblas`) | +| `deps` | `{ ["ns.name"]="ver" }`, flat or dotted; the same shape inside a feature | + +## Index redirection (`[indices]`) + +What the test surface has to validate is **the descriptors in the checkout**, not the published remote index, and +`[indices]` is what redirects a namespace into this repository to make that happen. + +**Root-level inheritance**: the workspace root's `mcpp.toml` declares `[indices] compat = { path = "." }`, whose +relative path resolves against the **workspace root** (mcpp >= 0.0.97, +[mcpp#224](https://github.com/mcpp-community/mcpp/issues/224)), and members inherit it directly instead of each +writing their own `path = "../../.."`. + +**Why there is only one, and why it is `compat`**: + +- The index table is **keyed by namespace**. Declared under a name no dependency ever requests, the index is simply + never registered, and resolution silently falls back to the published remote index — at which point what is under + test is not this checkout at all. +- Declaring the same path under several namespaces does register all of them, but they become N independent project + repos, and any lookup afterwards fails with an N-way ambiguity (physically the same descriptor; + [mcpp#238](https://github.com/mcpp-community/mcpp/issues/238) / + [xlings#374](https://github.com/openxlings/xlings/issues/374) — a silent exit 1 before xlings 0.4.69, a loud error + since). + +So the root level can carry exactly one namespace, and `compat` is the one that buys the most (13 members against 10 +for everything else combined). + +**Member-level override**: a member consuming another namespace declares its own `[indices]`, and that table +**replaces** the inherited root-level one rather than merging with it — which is precisely the mechanism that keeps +one project index repo per member. + +**The cross-namespace trade-off**: a single member cannot resolve two namespaces from this checkout. `tests/examples/asio-ssl` +exploits that deliberately: it writes no member-level declaration and inherits the root `compat`, so asio itself comes +from the published remote index while its `ssl` feature dependency `compat.openssl` resolves from this checkout — +which means **an unmerged compat descriptor can be validated through an already-published consumer**. The local asio +descriptor is covered the other way round, by `tests/examples/asio-module`. + +**Bare-name dependencies are out of scope**: the redirect is keyed by the **requesting** side's namespace, and a bare +`eigen = "5.0.1"` is a request issued in the default namespace — so it resolves from the remote index even though it +eventually lands on a `compat` descriptor. Members therefore always use the qualified spelling; bare-name resolution +itself is covered by upstream mcpp's e2e 165. + +## The index version contract (index.toml) + +`index.toml` at the repository root declares `[index] min_mcpp` — the oldest mcpp version able to resolve every +descriptor in this index. The contract travels with the tree: `publish_mcpp_index.sh` packs it into the artifact, and +a git clone or an `[indices] path =` local index carries it naturally. mcpp >= 0.0.85 checks it when opening an index +tree and reports `E0006` plus upgrade guidance on a violation (with `MCPP_INDEX_FLOOR=ignore` as a debugging escape +hatch). + +The rule (mechanically enforced by lint, not by discipline): **floor first, new grammar after** — lint runs +`xpkg parse` with the mcpp version CI pins (strict: an unknown key fails), so a descriptor that needs newer +grammar/keys physically cannot land on main before `MCPP_VERSION` and `min_mcpp` are raised in lock-step. Reproduce +locally with `mcpp xpkg parse pkgs//.lua`. + +## CI behavior (validate.yml) + +- Triggers: a PR (touching `pkgs/**/*.lua`, `tests/**`, either README, `mcpp.toml`, `index.toml` or this workflow), + a push to main, the nightly cron, and manual dispatch. +- `env.MCPP_VERSION` is the mcpp version every job uses; local verification should match it. +- `lint` (always runs): lua syntax via `loadfile(f,'t')`; `spec=`/`name=`/`xpm=` must be present; leading-v versions + are rejected; runs `check_mirror_urls.lua`; runs `check_package_name.lua` (identity shape, see "Package identity" + above); then runs `mcpp xpkg parse` over every descriptor with the mcpp version CI pins (strict — an unknown key + fails). `xpkg parse` in mcpp >= 0.0.106 enforces the identity shape itself, which makes the lua lint an earlier and + cheaper redundant gate. +- `mirror-cn-reachable` (always runs): `curl`s each CN url; all must return 200. +- `workspace (linux|macos|windows)`: the whole test surface is one mcpp workspace and the **only build/run channel** — + there is no shell-driven exception (the public module packages imgui/ffmpeg/opencv/tinyhttps are ordinary members + too, resolving from the checkout through a member-level `[indices] default = { path = "../../.." }`, + mcpp >= 0.0.97; members consuming `compat` inherit the root-level declaration, see "Index redirection" above). + - Selective member testing: on a PR, `git diff` maps changed files to the affected members + (`pkgs//.lua` → members whose mcpp.toml references ``; `tests/examples//**` → member ``), and + only those run through `mcpp test -p `; global changes — the workflow itself, the non-member part of the + workspace manifest, `tools/` and so on — go to a full `mcpp test --workspace`. push/nightly/dispatch are always + full runs. + - The `~/.mcpp/registry` cache carries the toolchains and the already-built compat packages, so a repeat run is + incremental and fast. Its key is computed once, in a step of its own, from `git ls-files -s` over the tracked + inputs — never with `hashFiles()`, which globs the working tree and would re-hash the multi-GB build output under + `tests/examples/*/target` when actions/cache re-evaluates the key in its post (save) step (that blew past the + runner's 120s template-evaluation cap on windows). + +## Reproducing lint locally (equivalent to the CI lint job) ```bash fail=0 @@ -143,18 +187,18 @@ done [ $fail -eq 0 ] && echo "ALL LINT PASS" ``` -## 合并后 +## After the merge -`publish-artifact.yml` 在合并至 `main` 后自动重新发布 mcpp-index artifact 并移动指针,无需发布新的 mcpp 版本。 -在线浏览地址:https://mcpplibs.github.io/mcpp-index/ +`publish-artifact.yml` republishes the mcpp-index artifact and moves the pointer automatically once the change lands +on `main` — no new mcpp release required. Browse online at: https://mcpplibs.github.io/mcpp-index/ -## 案例索引 +## Case index -| 形态 | 描述符 | example | 设计文档 / PR | +| Shape | Descriptor | example | Design doc / PR | |---|---|---|---| -| C 源码 + feature | `pkgs/c/compat.cjson.lua`、`compat.gtest.lua` | `tests/examples/cjson/` | `.agents/docs/2026-06-27-add-cjson-and-nlohmann-json-plan.md` / #48 | -| C++23 module(generated wrapper) | `pkgs/n/nlohmann.json.lua` | `tests/examples/nlohmann.json/` | 同上 / #48 | +| C source + feature | `pkgs/c/compat.cjson.lua`, `compat.gtest.lua` | `tests/examples/cjson/` | `.agents/docs/2026-06-27-add-cjson-and-nlohmann-json-plan.md` / #48 | +| C++23 module (generated wrapper) | `pkgs/n/nlohmann.json.lua` | `tests/examples/nlohmann.json/` | same as above / #48 | | header-only + source-gated feature | `pkgs/c/compat.eigen.lua` | `tests/examples/eigen/` | `.agents/docs/2026-06-28-add-eigen-plan.md` / #50 | -| header-only(纯头) | `pkgs/c/compat.opengl.lua`、`compat.khrplatform.lua` | — | `.agents/docs/2026-06-03-gl-runtime-packages-plan.md` | -| 外部构建系统(`install()` 驱动) | `pkgs/c/compat.openblas.lua`(Make)、`compat.openssl.lua`(Perl Configure + Make) | `tests/examples/openblas/`、`openssl/` | `docs/superpowers/specs/2026-07-26-openssl-asio-tls-design.md` / #124 | -| feature 拉起依赖(跨包) | `pkgs/c/chriskohlhoff.asio.lua` 的 `ssl` feature → `compat.openssl` | `tests/examples/asio-ssl/` | 同上 | +| header-only (pure headers) | `pkgs/c/compat.opengl.lua`, `compat.khrplatform.lua` | — | `.agents/docs/2026-06-03-gl-runtime-packages-plan.md` | +| External build system (`install()`-driven) | `pkgs/c/compat.openblas.lua` (Make), `compat.openssl.lua` (Perl Configure + Make) | `tests/examples/openblas/`, `openssl/` | `docs/superpowers/specs/2026-07-26-openssl-asio-tls-design.md` / #124 | +| A feature pulling in a dependency (cross-package) | the `ssl` feature of `pkgs/c/chriskohlhoff.asio.lua` → `compat.openssl` | `tests/examples/asio-ssl/` | same as above | diff --git a/docs/zh/README.md b/docs/zh/README.md new file mode 100644 index 0000000..3bc1332 --- /dev/null +++ b/docs/zh/README.md @@ -0,0 +1,17 @@ +# mcpp-index 文档 + +[English](../README.md) | **简体中文** + +本目录为面向贡献者的参考文档,供人工与 agent 共同使用。新增一个包的端到端流程定义于 agent skill +[`add-mcpp-index-package`](../../.agents/skills/add-mcpp-index-package/SKILL.md);下列文档提供各环节的细节参考。 + +| 文档 | 内容 | +|------|------| +| [package-types.md](package-types.md) | 四种库形态(C 源码 compat、header-only、C++23 module wrapper、外部 Form-A 模块仓)的描述符模板与样例 | +| [cn-mirror.md](cn-mirror.md) | GitCode `mcpp-res` CN 镜像闭环(`gtc` 工具、闭环校验、注意事项),含无 `mcpp-res` 写权限时的回退方案(plain-string 上游 url) | +| [repository-and-schema.md](repository-and-schema.md) | 仓库布局、描述符 schema 速查、`validate.yml` CI 行为、本地 lint 复现、案例索引 | + +英文版位于 [`docs/`](../)。 + +> 包的字段规范(`mcpp = { … }` 扩展)以上游为准,见 +> `mcpp xpkg parse`(CI 使用的同一个解析器),语义与约束见 mcpp 仓的 [`docs/spec/`](https://github.com/mcpp-community/mcpp/tree/main/docs/spec)。 diff --git a/docs/zh/cn-mirror.md b/docs/zh/cn-mirror.md new file mode 100644 index 0000000..4b68da2 --- /dev/null +++ b/docs/zh/cn-mirror.md @@ -0,0 +1,93 @@ +# CN 镜像闭环(gtc / gitcode / mcpp-res) + +[English](../cn-mirror.md) | **简体中文** + +为每个包配备国内下载镜像,可提升 `mcpp add/build` 在中国大陆的访问速度。其机制为:将 `xpm...url` 由 +单一字符串改写为 `{ GLOBAL = "<上游>", CN = "" }`,解析优先级为 **GLOBAL 优先于 CN**(GLOBAL 仍为 +默认,仅在其不可用时回退至 CN)。解析由 **xlings**(xim 引擎)完成,而非 mcpp 的 C++ parser,因此该机制由 spec +驱动并经由既有引擎生效。 + +- CN 布局:gitcode 组织 **`mcpp-res`**,每库一仓,资产挂载于按版本号打的 release 上。 +- repo slug 取包名去除 `compat.` 或 `mcpplibs.` 前缀后的结果(`compat.eigen` 对应 `eigen`;`nlohmann` 类宜用 + `nlohmann-json` 以避免裸 `json` 的歧义)。 +- CN 资产的公网 URL 约定为: + `https://gitcode.com/mcpp-res//releases/download//-.` + +## 无 `mcpp-res` 写权限时的回退 + +建立镜像需要 gitcode `mcpp-res` 组织的写权限(token)。在不具备该权限时,不应勉强构造镜像表:lint +(`check_mirror_urls.lua`)强制要求,一旦 `url` 写成表形式,其 `CN` 必须为 `https://gitcode.com/mcpp-res/…`, +因此 `{ GLOBAL=上游, CN=上游 }` 会直接导致 lint 失败。正确的回退方式是采用纯字符串 url(仅填上游 release), +lint 对纯字符串 url 不施加镜像约束: + +```lua +-- 回退:无 CN 镜像,GLOBAL 与 CN 均等价于上游 release(使用单一字符串即可) +["0.1.1"] = { url = "https://github.com///releases/download/v0.1.1/-0.1.1.tar.gz", + sha256 = "…" }, +``` + +- CN 用户将回退至上游源,访问较慢但功能不受影响。 +- 既有先例:`pkgs/t/tensorvia-cpu.lua`(用户自有 mcpp 库,无 CN 镜像,三平台均使用上游单一字符串 url)。 +- 后续在获得权限、或由维护者补充镜像后,可将该版本的 `url` 改写为 `{ GLOBAL=…, CN=… }` 表(sha256 保持不变)。 + +如需表达“GLOBAL 与 CN 均指向上游”,应采用上述单一字符串写法,而不应写成 `{ GLOBAL=x, CN=x }` 表(其无法通过 +“CN 必须指向 gitcode”的 lint 检查)。 + +## gtc 工具 + +`gtc`(`tools/gtc`,亦位于 `~/.local/bin/gtc`,python 实现,基于 gitcode API v5)。Token 位于 +`~/.config/gitcode-tool/config.json`,亦可经 `GITCODE_TOKEN` 或 `--token` 覆盖。当前登录用户为 `Sunrisepeak`。 + +```bash +gtc repo create / [--description …] [--private] # owner==login 时走 /user/repos,否则走 /orgs//repos;幂等(422 仅告警) +gtc repo push / [--branch main] +gtc release create / --tag T [--name] [--body-file] [--target] [--prerelease] # 幂等(tag 已存在则跳过) +gtc release upload / --tag T # 非幂等,上传前应先查询现有资产 +gtc release publish / --tag T [--name] [--target] [--asset FILE] # 等价于 create + upload +gtc pr create / --title --head --base [--body-file] +``` + +`gtc` 不能创建 org;`mcpp-res` 已存在。如需新建 org,应调用 `POST /api/v5/orgs` 并同时提供 `name` 与 `path` 字段。 + +## 标准操作(以 slug=`eigen`、ver=`5.0.1` 为例) + +```bash +# 0. 下载 GLOBAL 上游 tarball,计算 sha256(填入描述符三平台);重复计算两次以确认稳定 +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" +sha256sum eigen-5.0.1.tar.gz + +# 1. 建仓(幂等) +gtc repo create mcpp-res/eigen --description "Eigen — CN mirror for mcpp-index" + +# 2. 新仓尚无分支,须先推送一个 init commit,release 方可 target main +mkdir eigen-init && echo "# Eigen — CN mirror" > eigen-init/README.md +gtc repo push mcpp-res/eigen eigen-init --branch main + +# 3. 发布 release 并上传资产(上传与 GLOBAL 相同的文件,以保证字节一致与 sha 相同) +gtc release publish mcpp-res/eigen --tag 5.0.1 --name "Eigen 5.0.1" --target main --asset eigen-5.0.1.tar.gz + +# 4. 闭环校验:CN 返回 200,且与 GLOBAL 字节一致 +CN="https://gitcode.com/mcpp-res/eigen/releases/download/5.0.1/eigen-5.0.1.tar.gz" +curl -fsSL -o cn.tar.gz -w 'CN http=%{http_code}\n' "$CN" +[ "$(sha256sum eigen-5.0.1.tar.gz|cut -d' ' -f1)" = "$(sha256sum cn.tar.gz|cut -d' ' -f1)" ] && echo "BYTE-IDENTICAL" +``` + +## 注意事项 + +- gitcode API 限流为 25 次/分钟/用户,调用间隔宜约 3.2 秒,遇 429 应退避重试。 +- 同名 release 资产不可覆盖,误传后只能经网页删除,因此命名应一次确定(`-.`)。 +- 新仓无分支:未先推送 init commit 时,release `--target main` 将失败。 +- 内容过滤:部分库描述或仓名会被 gitcode 文本过滤拒绝,应改用中性措辞。 +- 必须上传与 GLOBAL 相同的包:不应上传经改动的包,否则 CN 与 GLOBAL 不一致,且除 `mirror-cn-reachable` 外难以察觉。 +- sha 漂移:GitLab 归档偶有重新打包导致 sha 变化,描述符的 sha 必须等于当前 GLOBAL 的实际字节(下载后即用,并重复 + 计算两次)。 +- xlings 按 sha256 全局去重:当 CN 与 GLOBAL 的 sha 相同时,本地“切换 CN 重新测试”会命中去重缓存,不会真正访问 + gitcode,因此校验 CN 应直接使用 `curl`(即上文第 4 步,亦为 CI `mirror-cn-reachable` 所执行)。 + +## CI 保障 + +`.github/workflows/validate.yml` 设有两道校验: + +- `lint` 中的 `tests/check_mirror_urls.lua`:当 url 写成表时,GLOBAL 与 CN 均须存在,且 CN 必须指向 gitcode + `mcpp-res` 镜像。 +- `mirror-cn-reachable`:抽取全部 CN url 并逐个 `curl`,任一非 200 即失败。 diff --git a/docs/zh/package-types.md b/docs/zh/package-types.md new file mode 100644 index 0000000..4ffc00e --- /dev/null +++ b/docs/zh/package-types.md @@ -0,0 +1,210 @@ +# 库形态与描述符模板 + +[English](../package-types.md) | **简体中文** + +编写描述符前,应先判定库所属的形态,再选用对应模板。`mcpp = {}` 内的所有路径均为**相对 verdir 的 GLOB**: +前导 `*` 用于吸收 tarball 的 `-/` wrap 层;`*` 匹配单段,`**` 匹配跨段(例如 `*/blas/*.cpp` 合法)。 + +A–D 是四种**基础**形态,先按它们判定;E–G 是在基础形态之上叠加的处理方式,按需组合。 + +| 形态 | 特征 | 样例 | 关键字段 | +|---|---|---|---| +| **A. C 源码 compat** | 纯 C 或少量源码,用户 `#include ` | `pkgs/c/compat.cjson.lua`、`compat.zlib.lua`、`compat.gtest.lua` | `sources` 与 `c_standard` | +| **B. header-only** | 纯头文件,无需编译 | `pkgs/c/compat.eigen.lua`、`compat.opengl.lua`、`compat.khrplatform.lua` | `include_dirs` 与 anchor 源 | +| **C. C++23 module** | 暴露 `import x.y;` | `pkgs/n/nlohmann.json.lua` | `modules` 与 `generated_files` 或源 `.cppm` | +| **D. 外部 Form-A 模块仓** | 上游自带 mcpp 描述符,独立仓库 | `pkgs/i/imgui.lua`、`pkgs/m/mcpplibs.*` | `mcpp = ""`(Form A) | +| **E. 生成 config 的全源码直编** | 上游用 configure/CMake 生成配置头,此处以 `generated_files` 落一份快照 | `pkgs/c/compat.libpng.lua`、`compat.curl.lua`、`compat.sdl2.lua`、`compat.ffmpeg.lua` | `generated_files` + `include_dirs` | +| **F. 共享库 compat** | 必须是**唯一**的那个 `.so`(会被第三方 `dlopen`) | `pkgs/c/compat.x11.lua` 等 X11 家族、`compat.vulkan.lua`(linux) | `targets = { kind = "shared", soname = … }` | +| **G. 宿主运行时适配** | 驱动之类无法 vendor 的东西,只做符号链接农场 + 元数据 | `pkgs/c/compat.glx-runtime.lua`、`compat.vulkan-runtime.lua` | `runtime.library_dirs` / `capabilities` | + +完整的样例索引见[根 README 的「参考示例」表](../../README.zh-CN.md#参考示例lua-描述符)。 + +A、B、C 三类共用的骨架(`package` 头与 `xpm`)如下: + +```lua +package = { + spec = "1", + namespace = "compat", -- 点分层级路径;compat / nlohmann / mcpplibs 等,决定 import 前缀与依赖 key + name = "", -- 单一原子段,不重复 namespace(SPEC-001 §3.2) + description = "…", + licenses = {"MIT"}, -- SPDX + repo = "https://…", + type = "package", + + xpm = { -- 三平台均需声明;纯源码或纯头时三平台共用同一 url 与 sha256 + linux = { ["1.2.3"] = { url = { GLOBAL = "https://…/v1.2.3.tar.gz", + CN = "https://gitcode.com/mcpp-res//releases/download/1.2.3/-1.2.3.tar.gz" }, + sha256 = "<计算所得>" } }, + macosx = { ["1.2.3"] = { url = { GLOBAL = "…", CN = "…" }, sha256 = "…" } }, + windows = { ["1.2.3"] = { url = { GLOBAL = "…", CN = "…" }, sha256 = "…" } }, + }, + + mcpp = { … 见下文各形态 … }, +} +``` + +身份是 `(namespace, name)` 二元组:层级一律放 `namespace`,`name` 只写一段。文件名不参与解析,推荐 +`pkgs/<首字母>/..lua`(命中 mcpp 的快路径)。详见 +[仓库结构与 schema](repository-and-schema.md#包身份namespace-name)。 + +--- + +## A. C 源码 compat(`compat.cjson` / `compat.zlib`) + +将 C 源码编译为 lib,头文件经 `include_dirs` 暴露,可选组件由 `features` 门控。 + +```lua +mcpp = { + language = "c++23", -- 与既有 compat 对齐;实际的 C 行为由 c_standard 决定 + import_std = false, + c_standard = "c99", -- 或 c11 + include_dirs = { "*" }, -- 暴露顶层头文件(*/foo.h) + sources = { "*/cJSON.c" }, -- 核心源码,始终编译 + targets = { ["cjson"] = { kind = "lib" } }, + features = { -- 可选扩展,默认不编译 + ["utils"] = { sources = { "*/cJSON_Utils.c" } }, + }, + deps = { }, +} +``` + +要点:多源码时可逐个列出(`compat.zlib` 列出了 15 个 `.c`)或使用 glob;需要配置头时可用 `generated_files` 合成 +(`compat.zlib` 使用 `mcpp_generated/include/mcpp_zlib_config.h` 配合 `cflags = {"-include …"}`)。 + +## B. header-only(`compat.eigen` / `compat.opengl`) + +此类库无可编译源码:由 `include_dirs` 暴露头文件,并加入一个 trivial anchor `.c`,以提供一个可构建的 lib 目标。 + +```lua +mcpp = { + language = "c++23", + import_std = false, + c_standard = "c11", + include_dirs = { "*" }, -- 或更精确的 "*/include" / "*/api" + generated_files = { + ["mcpp_generated/_anchor.c"] = "int mcpp_compat__anchor(void) { return 0; }\n", + }, + sources = { "mcpp_generated/_anchor.c" }, + targets = { [""] = { kind = "lib" } }, + -- 若存在额外可编译源码的组件(非纯头),可实现为 source-gated feature: + features = { + ["blas"] = { sources = { "*/blas/*.cpp", "*/blas/f2c/*.c" } }, -- eigen 实例 + }, + deps = { }, +} +``` + +注意:纯头形式的可选项无法隐藏(与核心共享 include 根),因此不应为其勉强构造 feature;只有额外可编译源码才能被门控 +(`compat.eigen` 的 `blas` 即由 C++ 与 f2c 转换的 C 构成,不依赖 Fortran,因此可门控)。 + +## C. C++23 module(`nlohmann.json`) + +使用户可 `import x.y;`。有两种实现路径: + +1. **上游已自带 `.cppm`**:直接 `sources = { "*/path/to/unit.cppm" }`。 +2. **上游 release 不含**(较常见):以 `generated_files` 合成 wrapper(`#include
`、`export module x.y;`、 + `export using …`),基底头 pin 至已发布 tag。应逐字复用上游官方 wrapper,而非自行推断符号清单。 + +```lua +mcpp = { + schema = "0.1", + language = "c++23", + import_std = false, -- wrapper 含上游头,启用 import std 易产生冲突 + modules = { "nlohmann.json" }, + include_dirs = { "*/single_include" }, -- 使 wrapper 内的 #include <…> 可解析 + generated_files = { + ["mcpp_generated/nlohmann.json.cppm"] = "module;\n#include \nexport module nlohmann.json;\n…", + }, + sources = { "mcpp_generated/nlohmann.json.cppm" }, + targets = { ["nlohmann_json"] = { kind = "lib" } }, + deps = { }, +} +``` + +注意:mcpp 段解析器不支持 Lua 长括号 `[[ … ]]`,`generated_files` 的内容必须采用双引号字符串并对 `\n`、`\"` +转义,否则报 `malformed mcpp segment`。消费侧不应将 `import x.y;` 与文本 `#include ` 混用(会与 GCC +modules 冲突),应配合 `import std;`。 + +## D. 外部 Form-A 模块仓(`imgui` / `mcpplibs.*`) + +上游或独立仓库自带 mcpp 描述符,本仓仅充当指针:`mcpp = "<相对或远程路径>"`(Form A,而非内联的 Form B)。新增的 +独立库通常归属于另一仓库(如 `mcpplibs/imgui-m`),本仓只负责登记。写法可参照 `pkgs/i/imgui.lua` 与 +`pkgs/x/xpkg.lua`。 + +--- + +## E. 生成 config 的全源码直编(`compat.curl` / `compat.sdl2`) + +上游用 configure 或 CMake 生成一份配置头,而本仓要的是「列出 .c 文件」。可行的前提是这类库把**未选中的后端 +编成空 TU**(curl 的 `vtls/gtls.c` 从头到尾是 `#ifdef USE_GNUTLS`,SDL 的 `src/video/windows/*.c` 同理),于是 +源码列表可以是朴素的 glob,配置全部落在一份 `generated_files` 快照里。 + +只在**上游有缺口的平台**生成:curl 签入了 `lib/config-win32.h`(Windows 无需生成),SDL 签入了 +`SDL_config_windows.h` / `SDL_config_macosx.h`(只有 linux 落到无用的 `SDL_config_minimal.h`)。生成时务必 +用**本索引的工具链**跑 configure —— 用宿主 `cc` 生成的 curl 配置曾断言 `ssize_t` 不存在,导致 curl 编不过自己 +的配置。 + +## F. 共享库 compat(`compat.x11` 家族 / `compat.vulkan`) + +当这个库会被第三方 `dlopen` 时,它必须是进程里**唯一**的那一个,静态链接会出问题。 + +```lua +targets = { ["vulkan"] = { kind = "shared", soname = "libvulkan.so.1" } }, +``` + +`soname` 不是可选项:SDL2 的 `SDL_CreateWindow(SDL_WINDOW_VULKAN)` 会 `dlopen("libvulkan.so.1")` 并用它解析 +surface 创建。若 loader 是静态的,应用最终会有两个 loader —— 自己那份建 instance,SDL 那份建 surface —— +`createSurface` 拿到一个对方没见过的 instance 而失败。 + +声明位置也有讲究:`kind = "shared"` 会把 `-fPIC` 传播给消费者,而 clang 对 msvc 目标直接拒绝该选项。因此 +`compat.vulkan` 把它写在 **linux 块内**,Windows 走另一套(链接预生成的 import library)。平台块里的 `targets` +会覆盖顶层声明,`compat.ffmpeg` 亦如此。 + +## G. 宿主运行时适配(`compat.glx-runtime` / `compat.vulkan-runtime`) + +GPU 驱动无法打包 —— ICD 必须匹配机器上的内核驱动。本仓的既定立场是把它建模为**宿主能力**,而不是假装厂商 +驱动是可再分发的普通包(见 `.agents/docs/2026-06-03-gl-runtime-packages-plan.md`)。这类包不 vendor 任何东西, +只做符号链接农场加元数据: + +```lua +runtime = { + library_dirs = { "mcpp_generated//lib" }, + capabilities = { "vulkan.icd.driver" }, +}, +``` + +之所以需要它:mcpp 的产物跑在**自带的 glibc** 下(`interp` 指向 `xim-x-glibc`,rpath 只覆盖 mcpp 自己的树), +因此裸 soname 的 `dlopen` 根本不搜索宿主库路径 —— loader 能找到全部 ICD manifest,却一个驱动都打不开。 + +两个反复踩到的细节: + +- **农场里只放带版本号的 soname**(`lib*.so.*`)。`runtime.library_dirs` 同时进**链接行**,一个裸 `libxcb.so` + 会遮蔽本仓自己的 `compat.xcb`,链接报 `undefined reference to XauDisposeAuth`(mcpp#304)。带版本号的名字对 + 链接器不可见,而恰好是 `dlopen` 要的。 +- **闭包必须完整**。农场里有 `libxcb.so.1` 却没有它依赖的 `libXau.so.6`,会遮蔽掉本来能解析的宿主副本,可执行 + 文件直接起不来。 + +--- + +## 最小工程(`tests/examples//`) + +`mcpp.toml`(短式依赖与长式依赖二选一): + +```toml +[package] +name = "-example" +version = "0.1.0" +[toolchain] +default = "gcc@16.1.0" +# `compat` 由 workspace 根的 [indices] 继承,成员无需再写; +# 消费其他命名空间时才在此声明,例如 `[indices] fmtlib = { path = "../../.." }` +# —— 成员级声明会**替换**根级表而非与之合并(这正是保持单个项目索引 repo 的方式)。 +[dependencies.compat] + = "1.2.3" # 或: = { version = "1.2.3", features = ["…"] } +[targets.-example] +kind = "bin" +main = "src/main.cpp" # C 库可使用 .c +``` + +`src/main.cpp` 应包含有效断言并 `return ok ? 0 : 1`,而非仅打印输出。module 库使用 `import std; import x.y;`; +header-only 与 C 库使用文本 `#include`。 diff --git a/docs/zh/repository-and-schema.md b/docs/zh/repository-and-schema.md new file mode 100644 index 0000000..ded14b3 --- /dev/null +++ b/docs/zh/repository-and-schema.md @@ -0,0 +1,166 @@ +# 仓库结构、schema、CI 与关键文件 + +[English](../repository-and-schema.md) | **简体中文** + +## 仓库布局 + +``` +pkgs//.lua 描述符。 取完整包名首字母(compat.* → c,nlohmann.json → n,imgui → i) +mcpp.toml workspace 清单(members 列表)+ 根级 [indices] compat = { path = "." }, + 由成员继承(相对路径按 workspace 根解析,mcpp ≥ 0.0.97) +tests/examples// 每库测试工程(workspace 成员; 为包名去前缀,模块包为 + mcpp.toml -module)。消费 compat 的成员不写 [indices];消费其他 + 命名空间的成员写恰好一条(模块包用 default),该声明**替换** + 根级表而非合并 —— 每个成员最多一个项目级索引 repo 是硬约束, + 详见下文「索引重定向」。依赖按平台自门控 + ([target.'cfg(...)']) + tests/*.cpp 行为断言(独立 main,退出码非 0 即失败) +tests/check_mirror_urls.lua lint:GLOBAL+CN 表完整性,以及 CN 指向 mcpp-res +tests/check_package_name.lua lint:身份形态(name 为单一原子段,层级归 namespace) +tests/list_cn_urls.lua 抽取 CN url,供 mirror-cn-reachable 使用 +README.md 索引说明与贡献入口(英文;中文版为 README.zh-CN.md) +.github/workflows/validate.yml CI:lint / mirror-cn-reachable / workspace(3 平台矩阵) +.agents/docs/-*.md 设计文档惯例 +docs/ 贡献者参考文档(英文);docs/zh/ 为中文版(本目录) +tools/gtc gitcode CLI,见 cn-mirror.md +tools/compat-ffmpeg/ 等 compat 大包的描述符再生成流水线 +.xpkgindex.json 站点配置(标题、链接、install 模板),通常无需改动 +``` + +## 外部仓库与文档 + +- mcpp 本体:https://github.com/mcpp-community/mcpp(本地通常存在 clone: + `/home/speak/workspace/github/mcpp-community/mcpp`)。`mcpp --version` 应与 CI 对齐;feature 与 glob 行为以 + `src/manifest.cppm`、`src/modgraph/scanner.cppm`、`src/build/prepare.cppm` 为准。 +- xpkg 扩展 schema(权威): + https://github.com/mcpp-community/mcpp/tree/main/docs/spec(对应本仓 `.xpkgindex.json` 的 + “mcpp ext” 链接)。V1 xpkg spec 见 `d2learn/xim-pkgindex` 的 `docs/V1/xpackage-spec.md`(url-template 约在第 172 行)。 +- CN 镜像组织:gitcode `mcpp-res`。 + +## 描述符 schema 速查(Form B inline) + +`package` 必填字段:`spec`、`namespace`、`name`、`description`、`licenses`、`repo`、`type="package"`、`xpm`、`mcpp`。 + +### 包身份:`(namespace, name)` + +身份是二元组 —— **`namespace` 是点分层级路径,`name` 是单一原子段**。层级一律放 `namespace`(mcpp SPEC-001 §3.2,见 mcpp 仓的 [`docs/spec/package-identity.md`](https://github.com/mcpp-community/mcpp/blob/main/docs/spec/package-identity.md)): + +```lua +namespace = "compat", name = "zlib" -- ✅ +namespace = "mcpplibs.capi", name = "lua" -- ✅ 多级命名空间 +namespace = "mcpplibs", name = "capi.lua" -- ❌ 短名仍带点 +``` + +最后一种被拒绝而非重新解读:`name` 里多出的点描述的是一个**没人声明过的命名空间**。mcpp 曾按最后一个点切分、静默造出 `(mcpplibs.capi, lua)`,0.0.106 起改为拒绝。 + +**兼容形态**:SPEC-001 之前发布的描述符把命名空间重复写在 `name` 里(`namespace="compat", name="compat.zlib"`),仍被接受 —— 前缀会先剥离再判定,wire key 是字面 `name`,两种写法都可安装。本仓已统一迁到短名形态。 + +**同短名不同命名空间可共存**:本仓现有三对 —— `compat:imgui` 与默认命名空间的 `imgui`、`compat:ffmpeg` 与 `ffmpeg`、`compat:lua` 与 `mcpplibs.capi:lua`。需要 xlings ≥ 0.4.69([xlings#381](https://github.com/openxlings/xlings/issues/381));`(namespace, name)` 唯一即可,`name` 本身不必唯一。 + +**文件名不参与解析**,可以任意。推荐 `.lua` 或 `..lua`(命中 mcpp 的快路径),但描述符按**声明的身份**被发现,叫别的名字也能解析。 + +`xpm..<裸版本>`: + +- `url`:字符串,或 `{ GLOBAL=…, CN=… }` 表(本仓统一使用表形式)。 +- `sha256`:必填,等于实际下载字节的摘要。 + +`mcpp`(常用键): + +| 键 | 说明 | +|---|---| +| `language` | 通常为 `"c++23"` | +| `import_std` | 多数为 `false` | +| `c_standard` | C 源码:`"c99"` 或 `"c11"` | +| `modules` | module 库:`{ "x.y" }` | +| `include_dirs` | glob 列表,暴露给消费者的头目录 | +| `generated_files` | `{ ["相对路径"]="内容字符串" }`;mcpp ≥ 0.0.85 支持 Lua 长括号 `[==[…]==]` 多行字符串(推荐,可读可 review);转义单行串仍兼容 | +| `scan_overrides` | `{ ["glob"]={ provides={…}, imports={…} } }`;声明式扫描结果,命中文件跳过 M1 文本扫描(适用于带条件 import 守卫的上游模块单元,如 fmt 的 src/fmt.cc);构建期由编译器 P1689 输出自动对账,声明错误响亮失败(mcpp ≥ 0.0.85)| +| `sources` | glob 列表,编入 lib 的源码 | +| `cflags` / `cxxflags` / `ldflags` | 追加至对应规则 | +| `targets` | `{ ["name"]={ kind="lib"/"bin", main=…, soname=… } }` | +| `features` | `{ ["f"]={ sources={…}, defines={…}, deps={…}, implies={…}, requires={…} } }`;`defines` 只作用于**包自身**的 TU,消费端若要按 feature 分支须自行声明(见 `tests/examples/openssl`、`openblas` 的 `[target.'cfg(…)'.build] cxxflags`) | +| `deps` | `{ ["ns.name"]="ver" }`,扁平或点号式;feature 内同形 | + +## 索引重定向(`[indices]`) + +测试面要验证的是 **checkout 里的描述符**,而不是已发布的远程索引,这靠 `[indices]` 把命名空间重定向到本仓完成。 + +**根级继承**:workspace 根的 `mcpp.toml` 声明 `[indices] compat = { path = "." }`,相对路径按 **workspace 根**解析(mcpp ≥ 0.0.97,[mcpp#224](https://github.com/mcpp-community/mcpp/issues/224)),成员直接继承,不必各写一份 `path = "../../.."`。 + +**为什么只有一条,而且是 `compat`**: + +- 索引表**按命名空间取键**。声明在一个没有任何依赖会请求的名字下,该索引根本不会被注册,解析会静默回落到已发布的远程索引 —— 此时被测的根本不是这个 checkout。 +- 同一路径声明成多个命名空间确实都会注册,但会变成 N 个各自独立的项目 repo,之后任何查找都以 N 路歧义失败(物理上是同一个描述符;[mcpp#238](https://github.com/mcpp-community/mcpp/issues/238) / [xlings#374](https://github.com/openxlings/xlings/issues/374),在 xlings 0.4.69 后由静默 exit 1 变为响亮报错)。 + +所以根级只能承载一个命名空间,`compat` 是收益最大的那个(13 个成员 vs 其余合计 10 个)。 + +**成员级覆盖**:消费其他命名空间的成员自己声明 `[indices]`,该表**替换**继承来的根级表而非与之合并 —— 这正是每个成员只保留一个项目索引 repo 的机制。 + +**跨命名空间的取舍**:一个成员无法同时从本 checkout 解析两个命名空间。`tests/examples/asio-ssl` 有意利用了这一点:它不写成员级声明、继承根级 `compat`,于是 asio 本身走已发布的远程索引,而它的 `ssl` feature 依赖 `compat.openssl` 从本 checkout 解析 —— 这样**未合并的 compat 描述符可以通过一个已发布的消费者去验证**。反过来,本地 asio 描述符由 `tests/examples/asio-module` 覆盖。 + +**裸名依赖不适用**:重定向按**请求侧**的命名空间取键,而裸写的 `eigen = "5.0.1"` 是以默认命名空间发出的请求,即使最终落到 `compat` 描述符上,也会从远程索引解析。因此各成员一律使用限定写法;裸名解析本身由 mcpp 上游的 e2e 165 覆盖。 + +## index 版本契约(index.toml) + +仓库根的 `index.toml` 声明 `[index] min_mcpp` —— 能解析本索引全部描述符的最老 +mcpp 版本。契约随树旅行:`publish_mcpp_index.sh` 把它打进 artifact,git clone 与 +`[indices] path =` 本地索引天然携带。mcpp ≥ 0.0.85 在打开索引树时检查,违反时报 +`E0006` + 升级指引(调试逃生口 `MCPP_INDEX_FLOOR=ignore`)。 + +规则(由 lint 机械强制,非纪律):**floor 先行、新文法在后**——lint 用 CI pin 的 +mcpp 跑 `xpkg parse`(strict:未知键即失败),所以需要更新文法/键的描述符在 +`MCPP_VERSION` 与 `min_mcpp` 同步提升之前物理上合不进 main。本地复现: +`mcpp xpkg parse pkgs//.lua`。 + +## CI 行为(validate.yml) + +- 触发条件:PR(改动 `pkgs/**/*.lua`、`tests/**`、两份 README 之一、`mcpp.toml`、`index.toml` 或本 workflow)、 + push 至 main、nightly cron、手动触发。 +- `env.MCPP_VERSION` 为全部 job 使用的 mcpp 版本,本地验证应与之对齐。 +- `lint`(始终运行):lua 语法 `loadfile(f,'t')`;须含 `spec=`/`name=`/`xpm=`;禁止前导 v 版本;执行 + `check_mirror_urls.lua`;执行 `check_package_name.lua`(身份形态,见上文「包身份」);再用 CI pin 的 + mcpp 对每个描述符跑 `mcpp xpkg parse`(strict,未知键即失败)。mcpp ≥ 0.0.106 的 `xpkg parse` 自身 + 也强制身份形态,lua lint 因此是更早、更便宜的冗余闸门。 +- `mirror-cn-reachable`(始终运行):逐个 `curl` CN url,均须返回 200。 +- `workspace (linux|macos|windows)`:整个测试面就是一个 mcpp workspace,**唯一的构建/运行通道**—— + 没有任何 shell 驱动的例外(公开模块包 imgui/ffmpeg/opencv/tinyhttps 也是普通成员,经成员级 + `[indices] default = { path = "../../.." }` 从 checkout 解析,mcpp ≥ 0.0.97;消费 `compat` 的 + 成员则继承根级声明,见上文「索引重定向」)。 + - 选择性成员测试:PR 时由 `git diff` 将改动文件映射到受影响成员 + (`pkgs//.lua` → mcpp.toml 引用 `` 的成员;`tests/examples//**` → 成员 ``), + 仅 `mcpp test -p ` 这些成员;workflow 本身、workspace 清单非成员部分、`tools/` 等 + 全局性改动 → `mcpp test --workspace` 全量。push/nightly/dispatch 恒为全量。 + - `~/.mcpp/registry` 缓存携带工具链与已构建的 compat 包,重复运行增量很快。其 key 由独立步骤经 + `git ls-files -s` 就已跟踪的输入算出一次,而**不用** `hashFiles()`:后者按工作树 glob,而 + actions/cache 会在 post(save)步骤重新求值 key,届时 `tests/examples/*/target` 下的数 GB 构建产物 + 也会被一并哈希 —— windows 上曾因此撞破 runner 的 120 秒模板求值上限。 + +## 本地 lint 复现(等价于 CI lint job) + +```bash +fail=0 +for f in pkgs/*/*.lua; do + lua5.4 -e "assert(loadfile('$f','t'))" >/dev/null 2>&1 || { echo "SYNTAX $f"; fail=1; } + for n in 'spec *=' 'name *=' 'xpm *='; do grep -q "$n" "$f" || { echo "MISS $n $f"; fail=1; }; done + grep -nqE '\["v[0-9]+|\["[^"]+"\][[:space:]]*=[[:space:]]*"v[0-9]+' "$f" && { echo "LEADING-V $f"; fail=1; } + lua5.4 tests/check_mirror_urls.lua "$f" >/dev/null 2>&1 || { echo "MIRROR $f"; fail=1; } + lua5.4 tests/check_package_name.lua "$f" || fail=1 +done +[ $fail -eq 0 ] && echo "ALL LINT PASS" +``` + +## 合并后 + +`publish-artifact.yml` 在合并至 `main` 后自动重新发布 mcpp-index artifact 并移动指针,无需发布新的 mcpp 版本。 +在线浏览地址:https://mcpplibs.github.io/mcpp-index/ + +## 案例索引 + +| 形态 | 描述符 | example | 设计文档 / PR | +|---|---|---|---| +| C 源码 + feature | `pkgs/c/compat.cjson.lua`、`compat.gtest.lua` | `tests/examples/cjson/` | `.agents/docs/2026-06-27-add-cjson-and-nlohmann-json-plan.md` / #48 | +| C++23 module(generated wrapper) | `pkgs/n/nlohmann.json.lua` | `tests/examples/nlohmann.json/` | 同上 / #48 | +| header-only + source-gated feature | `pkgs/c/compat.eigen.lua` | `tests/examples/eigen/` | `.agents/docs/2026-06-28-add-eigen-plan.md` / #50 | +| header-only(纯头) | `pkgs/c/compat.opengl.lua`、`compat.khrplatform.lua` | — | `.agents/docs/2026-06-03-gl-runtime-packages-plan.md` | +| 外部构建系统(`install()` 驱动) | `pkgs/c/compat.openblas.lua`(Make)、`compat.openssl.lua`(Perl Configure + Make) | `tests/examples/openblas/`、`openssl/` | `docs/superpowers/specs/2026-07-26-openssl-asio-tls-design.md` / #124 | +| feature 拉起依赖(跨包) | `pkgs/c/chriskohlhoff.asio.lua` 的 `ssl` feature → `compat.openssl` | `tests/examples/asio-ssl/` | 同上 | diff --git a/tools/publish_mcpp_index.sh b/tools/publish_mcpp_index.sh index a157b2f..99c36bb 100755 --- a/tools/publish_mcpp_index.sh +++ b/tools/publish_mcpp_index.sh @@ -25,6 +25,9 @@ info() { echo "[mcpp-index] $*"; } TREE="$OUT/tree"; mkdir -p "$TREE" cp -a "$SRC/pkgs" "$TREE/" [ -f "$SRC/README.md" ] && cp "$SRC/README.md" "$TREE/" || true +# The zh translation ships alongside it — README.md links to it, so leaving it +# out would give the unpacked artifact a dangling language switch. +[ -f "$SRC/README.zh-CN.md" ] && cp "$SRC/README.zh-CN.md" "$TREE/" || true # index.toml carries the index→client version contract (min_mcpp floor); # it must travel with the tree so unpacked snapshots enforce it offline. [ -f "$SRC/index.toml" ] && cp "$SRC/index.toml" "$TREE/" || true