Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions .agents/docs/2026-08-06-add-cli11-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# 新增 CLI11 收录方案

**日期**: 2026-08-06
**本仓**: `mcpp-community/mcpp-index`(github 别名 `mcpplibs/mcpp-index`)
**来源**: 社区 PR [#169](https://github.com/mcpplibs/mcpp-index/pull/169)(@tz12323 提供描述符初稿),
本文档记录补齐部分:CN 镜像、workspace 成员、描述符修正与验证结论。

**目标**:
1. 收录 [`CLIUtils/CLI11`](https://github.com/CLIUtils/CLI11) 2.7.2 —— header-only 命令行解析器,
`compat` 形态,文件 `pkgs/c/compat.CLI11.lua`。
2. 建立 CN 镜像 `gitcode.com/mcpp-res/cli11`。
3. 添加 `tests/examples/cli11/` 测试工程并登记为 workspace 成员。

---

## 1. 形态判定

| 库 | 形态 | 最新 tag | 收录版本 | License |
|---|---|---|---|---|
| CLI11 | B. header-only | `v2.7.2` | `2.7.2` | BSD-3-Clause |

上游 tarball 顶层是 `CLI11-2.7.2/` wrap 层,头文件在 `include/CLI/` 下,实现头在
`include/CLI/impl/*_inl.hpp`。默认模式下所有定义带 `CLI11_INLINE`,`CLI/CLI.hpp` 把
impl 头一并拉进来 —— **没有需要编译的库源码**,所以走 shape B:`include_dirs` 暴露头
文件根,再配一个 anchor TU 让 mcpp 有可构建的 `lib` target。

原 PR 写的是 `include_dirs = { "include" }`,这是错的:`mcpp` 段里的路径都是**相对
verdir 的 glob**,必须由前导 `*` 吸收 `CLI11-<tag>/` 这一层,即 `*/include`。按原样
构建时消费者的 `#include <CLI/CLI.hpp>` 找不到头文件。

### 1.1 两个不收的上游额外件

- `src/Precompile.cpp` —— 上游的预编译模式。只有在 `CLI11_COMPILE` 同时到达**消费者**
的 TU 时才有意义(否则头文件里的 inline 定义照常生效,编出来的那份是死重量)。
那是 interface define,不是 sources 门控,feature 表达不了完整语义,故不做半吊子实现。
- `src/modules/CLI11.cppm` —— 上游自带的 C++20 模块接口。模块层是另一种包形态
(参见 `pkgs/n/nlohmann.json.lua`),不是 compat 包的 feature。日后要 `import CLI11;`
应另开一个模块层描述符。

因此本包**没有 feature**,也就没有需要做负向验证的门控。

## 2. CN 镜像

slug 取包名去掉 `compat.` 前缀后小写:`cli11`(与既有 `mcpp-res` 仓库的小写风格一致)。

```
GLOBAL: https://github.com/CLIUtils/CLI11/archive/refs/tags/v2.7.2.tar.gz
CN: https://gitcode.com/mcpp-res/cli11/releases/download/2.7.2/cli11-2.7.2.tar.gz
sha256: 46eef3101da70852ec7af026e09d485ccee81813331c8c6052d39344443b83da
```

执行:`gtc repo create` → 先推 init commit(新仓无分支,release 无法 target main)
→ `gtc release publish --tag 2.7.2 --asset cli11-2.7.2.tar.gz`。

闭环验证:CN url `http=200`,`size=1446996`,sha256 与 GLOBAL **字节一致**。
纯头文件包,三平台共用同一 url 与 sha。

## 3. 测试工程

`tests/examples/cli11/`,依赖 `[dependencies.compat] CLI11 = "2.7.2"`。
`compat` 的 `[indices]` 重定向由 workspace 根继承,不在成员里重复声明(单条是硬约束)。
依赖写**限定形式**:索引表按请求的 namespace 取键,裸名会走默认命名空间、从线上索引
解析,被测的就不再是本 checkout。

`tests/parse.cpp` 断言四件事加两条失败路径:

- 取值 / 默认值保持 / flag / 多值选项(`expected(3)`)/ 子命令的一次完整 parse;
- 未声明的选项必须抛 `CLI::ParseError`(证明 parse 真的跑了,而不是静默无动作);
- `check(CLI::Range(1,10))` 越界必须抛 `CLI::ValidationError`(带到 Validators 那一组头文件);
- `CLI11_VERSION == "2.7.2"`(证明解开的是这个归档);
- 调用包自身的 anchor 符号 `mcpp_compat_cli11_headers_anchor()`。

最后一条是有意的:header-only 包的 lib target 里就只有这一个 TU,引用它能证明包**确实
被编译并链接**了,而不是只有头文件被 include(绿 CI 不等于包被编译)。

## 4. 验证结果

本地用 CI 同版本 mcpp `2026.8.6.1`,`MCPP_INDEX_MIRROR=CN`(CI 侧走 GLOBAL),
清掉 `target/` 与 `.mcpp/` 冷启动:

```
Downloading compat.CLI11 v2.7.2
Compiling cli11-tests v0.1.0 (.)
Compiling compat.CLI11 v2.7.2
Compiling parse (test)
Running bin/parse
parse ... ok (0.02s)

test result ok. 1 passed; 0 failed; finished in 87.28s (build 3.06s + run 0.02s)
```

产出 obj 两个:`obj/parse.o` 与 `obj/compat_CLI11/mcpp_generated/cli11_anchor.o`。

本地复现 `validate.yml` 的 lint,全部通过:lua 语法、必填字段、无前导 `v`、
`check_mirror_urls.lua`、`check_package_name.lua`、`check_cross_package_refs.lua`、
`mcpp xpkg parse`(解析器语法,严格模式)。

## 5. 文件清单

1. `pkgs/c/compat.CLI11.lua` — 包描述符(修正 `include_dirs` glob、CN 镜像 url、
`language` 对齐仓内其余描述符的 `c++23`)
2. `tests/examples/cli11/mcpp.toml` — 测试工程配置
3. `tests/examples/cli11/tests/parse.cpp` — 测试代码
4. `mcpp.toml` — 登记 workspace 成员
5. `README.md` / `README.zh-CN.md` — 参考样例表各加一行
6. `.agents/docs/2026-08-06-add-cli11-plan.md` — 本文档
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Two kinds of packages live here:
| C++-source compat, one depending on the other | [`compat.abseil`](pkgs/c/compat.abseil.lua) (151 TUs; a wildcard over `absl/**` trimmed by upstream's test/benchmark naming conventions) · [`compat.protobuf`](pkgs/c/compat.protobuf.lua) (the libprotobuf runtime, 79 TUs transcribed from upstream's own `src/file_lists.cmake`; declares `compat.abseil` as a dependency because protobuf's public headers include `absl/…`, and its `gzip` feature defines `HAVE_ZLIB` and pulls `compat.zlib`, while `upb` adds protobuf's 64-TU C runtime out of the same tarball. It also exposes **`protoc`** as a `kind = "bin"` target, so a consumer writing `tools = ["protoc"]` gets the compiler built for its own machine out of the same package it links — making a generator/runtime version mismatch inexpressible) · [`compat.re2`](pkgs/c/compat.re2.lua) (22 TUs, upstream's own `RE2_SOURCES`) |
| C++-source compat, zero-dep client + optional components | [`compat.websocket`](pkgs/c/compat.websocket.lua) (IXWebSocket 12.0.1 — a pure RFC 6455 client compiled from upstream's `IXWEBSOCKET_SOURCES` minus the four server TUs, so the **base build has zero external dependencies**: TLS off (the OpenSSL/MbedTLS/AppleSSL TUs aren't built) and `IXWEBSOCKET_USE_ZLIB` unset, so the gzip codec compiles to a no-op. Two optional features add on top: `server` (the four server TUs — `IXWebSocketServer`, `IXSocketServer`, `IXHttpServer`, `IXWebSocketProxyServer` — needing nothing external, and it **implies `zlib`** because upstream's server advertises permessage-deflate by default, which the transport negotiates regardless of the define) and `zlib` (deps `compat.zlib` and turns the codec into real per-message-deflate compression). The default-feature test brings its own minimal RFC 6455 echo server on loopback sockets (handshake, masking, fragmentation and close all exercised offline); a second member, `websocket-features`, runs a real `ix::WebSocketServer` and asserts the compression is observable on the wire — a 64 KiB repeated payload round-trips with `wireSize` = 80) |
| header-only (with `features`) | [`compat.eigen`](pkgs/c/compat.eigen.lua) |
| header-only, nothing to gate | [`compat.CLI11`](pkgs/c/compat.CLI11.lua) (a command line parser whose every definition is `CLI11_INLINE`, so the package is `*/include` plus an anchor TU. Upstream's two extras stay out: `src/Precompile.cpp` only means anything when `CLI11_COMPILE` also reaches the CONSUMER's translation units — an interface define, not a sources-only gate — and `src/modules/CLI11.cppm` is a module layer, which is a package shape of its own rather than a feature of the compat package) |
| 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) · [`compat.c-ares`](pkgs/c/compat.c-ares.lua) (91 TUs; the release tarball already ships `ares_build.h` and a Windows config, so only `ares_config.h` is snapshotted per OS) |
| Upstream codegen frozen into the mirror archive | [`compat.godot-cpp`](pkgs/c/compat.godot-cpp.lua) (two versions: `4.5.0` = the `godot-4.5-stable` bindings, `10.0.0-rc1` = godot-cpp's own 10.x line, whose bindings target Godot 4.6. The ~1000 GDExtension classes under `gen/` exist in no upstream tag archive — upstream's `binding_generator.py` emits them at build time. Running it once offline and publishing upstream's tree byte-for-byte **plus** `gen/` keeps Python off the consumer side entirely; `tools/godot-cpp/repack.sh` reproduces the archive deterministically and refuses to publish if any upstream file differs) |
Expand Down
1 change: 1 addition & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上
| C++ 源码 compat(彼此依赖) | [`compat.abseil`](pkgs/c/compat.abseil.lua)(151 TU;对 `absl/**` 取通配后,按上游自身的 test/benchmark 命名约定裁剪) · [`compat.protobuf`](pkgs/c/compat.protobuf.lua)(libprotobuf 运行时,79 TU 逐条转录自上游 `src/file_lists.cmake`;因 protobuf 公开头文件 include 了 `absl/…`,故显式依赖 `compat.abseil`;`gzip` feature 定义 `HAVE_ZLIB` 并拉入 `compat.zlib`,`upb` feature 则从同一个 tarball 里再编出 protobuf 的 64 TU C 运行时;还以 `kind = "bin"` target 暴露 **`protoc`**,消费者写 `tools = ["protoc"]` 即可从「自己链接的那个包」拿到为本机构建的编译器,使生成器与运行时的版本错配无法表达) · [`compat.re2`](pkgs/c/compat.re2.lua)(22 TU,取自上游自身的 `RE2_SOURCES`) |
| C++ 源码 compat(零依赖客户端 + 可选组件) | [`compat.websocket`](pkgs/c/compat.websocket.lua)(IXWebSocket 12.0.1 —— 从上游 `IXWEBSOCKET_SOURCES` 剔掉 4 个 server TU 后直编的纯 RFC 6455 客户端,**基座零外部依赖**:TLS 关闭(OpenSSL/MbedTLS/AppleSSL 三组 TU 均不编),`IXWEBSOCKET_USE_ZLIB` 不定义(gzip codec 编译为 no-op)。两个可选 feature 在基座上叠加:`server`(4 个 server TU —— `IXWebSocketServer`/`IXSocketServer`/`IXHttpServer`/`IXWebSocketProxyServer`,零新增外部依赖,且 **implies `zlib`** —— 因为上游 server 默认就宣称 permessage-deflate,而 transport 的协商不受宏门控)与 `zlib`(依赖 `compat.zlib`,把 codec 变成真正的 permessage-deflate 压缩)。默认构建的测试自带基于 loopback 原始 socket 的最小 RFC 6455 echo server(握手/掩码/分片/关闭全部离线实测);第二个成员 `websocket-features` 跑真实的 `ix::WebSocketServer`,并断言压缩在线路上可观测 —— 64 KiB 重复载荷往返,`wireSize` = 80) |
| header-only(含 `features`) | [`compat.eigen`](pkgs/c/compat.eigen.lua) |
| header-only(无可门控组件) | [`compat.CLI11`](pkgs/c/compat.CLI11.lua)(命令行解析器,全部定义都是 `CLI11_INLINE`,故整包就是 `*/include` 加一个 anchor TU。上游两个额外件都不收:`src/Precompile.cpp` 只有在 `CLI11_COMPILE` 同时到达**消费者** TU 时才有意义 —— 那是 interface define,不是 sources 门控;`src/modules/CLI11.cppm` 属于模块层,是另一种包形态,而非 compat 包的 feature) |
| 运行时 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.c-ares`](pkgs/c/compat.c-ares.lua)(91 TU;release tarball 已自带 `ares_build.h` 与 Windows 配置,故只需按 OS 冻结 `ares_config.h`) |
| 上游 codegen 前置冻结进镜像归档 | [`compat.godot-cpp`](pkgs/c/compat.godot-cpp.lua)(两个版本:`4.5.0` 是 `godot-4.5-stable` 的绑定,`10.0.0-rc1` 是 godot-cpp 自己的 10.x 线、对应 Godot 4.6。`gen/` 下约 1000 个 GDExtension 类不在任何上游 tag 归档里,由上游 `binding_generator.py` 在构建时生成。改为离线跑一次,把上游源码树逐字节原样 **加上** `gen/` 一起发布,消费侧就完全不需要 Python;`tools/godot-cpp/repack.sh` 可确定性复现该归档,且上游文件一旦有出入即拒绝打包) |
Expand Down
1 change: 1 addition & 0 deletions mcpp.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ members = [
"tests/examples/catch2-v2",
"tests/examples/catch2-v2-main",
"tests/examples/cjson",
"tests/examples/cli11",
"tests/examples/core",
"tests/examples/curl",
"tests/examples/eigen",
Expand Down
98 changes: 98 additions & 0 deletions pkgs/c/compat.CLI11.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
-- Form B inline descriptor for CLI11 — a modern C++ command line parser.
--
-- CLI11 is HEADER-ONLY in its default mode: every definition under
-- `include/CLI/` is marked `CLI11_INLINE`, and `CLI/CLI.hpp` pulls the
-- implementation headers under `include/CLI/impl/` in behind it. There is
-- nothing to compile for normal use, so this package exposes the header root
-- on the include path and carries a tiny anchor translation unit so mcpp
-- always has a buildable `lib` target (same shape as compat.eigen /
-- compat.opengl).
--
-- Consumers write:
--
-- #include <CLI/CLI.hpp>
--
-- and need no feature or extra configuration.
--
-- All `mcpp` paths are GLOBS relative to the verdir, so the leading `*`
-- absorbs the GitHub archive's `CLI11-<tag>/` wrap layer: the header root is
-- `*/include`, NOT `include`.
--
-- Two upstream extras are deliberately NOT packaged here:
--
-- * `src/Precompile.cpp` — upstream's precompiled mode. It only does
-- anything when `CLI11_COMPILE` is defined, and that define has to reach
-- the CONSUMER's translation units as well (otherwise the headers keep
-- their inline definitions and the compiled ones are dead weight). That
-- makes it an interface define, not a sources-only gate, so it is left
-- out rather than half-expressed.
-- * `src/modules/CLI11.cppm` — upstream's C++20 module interface. A module
-- layer is a separate package shape (see pkgs/n/nlohmann.json.lua), not a
-- feature of the compat package.
package = {
spec = "1",
namespace = "compat",
name = "CLI11",
description = "Modern C++ command line parser (header-only)",
licenses = { "BSD-3-Clause" },
repo = "https://github.com/CLIUtils/CLI11",
type = "package",

-- Pure headers: one archive and one sha256 serve all three platforms.
xpm = {
linux = {
["2.7.2"] = {
url = {
GLOBAL = "https://github.com/CLIUtils/CLI11/archive/refs/tags/v2.7.2.tar.gz",
CN = "https://gitcode.com/mcpp-res/cli11/releases/download/2.7.2/cli11-2.7.2.tar.gz",
},
sha256 = "46eef3101da70852ec7af026e09d485ccee81813331c8c6052d39344443b83da",
},
},

macosx = {
["2.7.2"] = {
url = {
GLOBAL = "https://github.com/CLIUtils/CLI11/archive/refs/tags/v2.7.2.tar.gz",
CN = "https://gitcode.com/mcpp-res/cli11/releases/download/2.7.2/cli11-2.7.2.tar.gz",
},
sha256 = "46eef3101da70852ec7af026e09d485ccee81813331c8c6052d39344443b83da",
},
},

windows = {
["2.7.2"] = {
url = {
GLOBAL = "https://github.com/CLIUtils/CLI11/archive/refs/tags/v2.7.2.tar.gz",
CN = "https://gitcode.com/mcpp-res/cli11/releases/download/2.7.2/cli11-2.7.2.tar.gz",
},
sha256 = "46eef3101da70852ec7af026e09d485ccee81813331c8c6052d39344443b83da",
},
},
},

mcpp = {
-- Applies to this package's OWN translation unit (the anchor below);
-- a consumer compiles the headers at whatever standard it declares,
-- and CLI11 itself needs only C++11. Kept at c++23 like every other
-- descriptor in this index.
language = "c++23",
import_std = false,
-- Upstream headers live in `include/CLI/`, so the include ROOT is
-- `include/` — that is what makes `#include <CLI/CLI.hpp>` resolve.
include_dirs = { "*/include" },
-- Header-only: a trivial anchor TU gives mcpp a buildable lib target.
-- The basename is unique across the index on purpose — mcpp's obj/
-- directory is flat and keyed by basename.
generated_files = {
["mcpp_generated/cli11_anchor.cpp"] = [==[
int mcpp_compat_cli11_headers_anchor(void) {
return 0;
}
]==],
},
sources = { "mcpp_generated/cli11_anchor.cpp" },
targets = { ["CLI11"] = { kind = "lib" } },
deps = { },
},
}
17 changes: 17 additions & 0 deletions tests/examples/cli11/mcpp.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# CLI11 test project: consumes compat.CLI11 (header-only command line parser)
# from this repo's own index and asserts real argv parsing under `mcpp test`.
# Part of the mcpp-index self-referential workspace: the `compat` redirect is
# declared at the workspace root and inherited here, so the dependency
# resolves to the checked-in recipe (pkgs/c/compat.CLI11.lua) rather than to
# the published remote index.
#
# The dependency is spelled QUALIFIED (`[dependencies.compat]`) on purpose:
# the index redirect is keyed by the REQUEST's namespace, so a bare
# `CLI11 = "2.7.2"` would ask under the default namespace, resolve from the
# published index, and quietly stop testing this checkout.
[package]
name = "cli11-tests"
version = "0.1.0"

[dependencies.compat]
CLI11 = "2.7.2"
Loading
Loading