|
| 1 | +# 2026-05-08 — 包索引仓库配置 (Package-Index Repo Configuration) |
| 2 | + |
| 3 | +> **状态**:设计稿 (待实现) |
| 4 | +> **依赖**:命名空间支持(PR-A,issue/PR 待合) |
| 5 | +> **目标读者**:mcpp 维护者 + 早期采用者 |
| 6 | +
|
| 7 | +## 1. 背景与动机 |
| 8 | + |
| 9 | +mcpp 当前**硬编码**单一包索引仓库 `mcpp-community/mcpp-index.git`,在 |
| 10 | +`fetcher` 启动时按固定 URL 拉取,且不带版本锁定。这带来三个问题: |
| 11 | + |
| 12 | +1. **不可复现**——索引仓的 `main` 分支随时变;同一份 `mcpp.toml` 在不 |
| 13 | + 同时间 `mcpp build` 可能拿到不同版本的 `gtest@1.15.2`(如果索引里 |
| 14 | + 修改了 sha256 / 重写了 url)。 |
| 15 | +2. **不能私有化**——企业内部要发布私有包,只能 fork 整个 mcpp 然后改 |
| 16 | + 常量,迁移成本高。 |
| 17 | +3. **不能多源**——命名空间已在 PR-A 引入,但每个 namespace 仍只能落 |
| 18 | + 在同一个官方仓里。`mcpp.toml` 里写 `[dependencies.acme] foo = ".."` |
| 19 | + 时,mcpp 不知道去哪找 `acme` 的索引。 |
| 20 | + |
| 21 | +xim/xlings 多年前就解决过类似问题——`xim-pkgindex-*` 多仓 + 每仓自带 |
| 22 | +namespace。**mcpp 这次的方案要尽量复用 xpkg 模型**(参见 |
| 23 | +2026-05-08 的 namespace 设计),同时在工程描述层面给用户暴露一个 |
| 24 | +极简的 TOML 配置入口。 |
| 25 | + |
| 26 | +## 2. 用户接口 |
| 27 | + |
| 28 | +### 2.1 `mcpp.toml` 的 `[indices]` 段 |
| 29 | + |
| 30 | +```toml |
| 31 | +[indices] |
| 32 | +# 1. 默认官方索引(隐式存在,显式声明可锁定 commit)。 |
| 33 | +mcpp = { url = "https://github.com/mcpp-community/mcpp-index.git", rev = "abc123def" } |
| 34 | + |
| 35 | +# 2. 第二方索引(开源生态)。 |
| 36 | +mcpplibs = { url = "https://github.com/mcpplibs/mcpp-index.git", tag = "v0.3.0" } |
| 37 | + |
| 38 | +# 3. 私有索引(企业内网)。短形式 = 跟踪默认分支(等价 branch = "main")。 |
| 39 | +acme = "git@gitlab.example.com:platform/mcpp-index.git" |
| 40 | + |
| 41 | +# 4. 跟踪特定分支(适合开发期,生产请改 tag/rev)。 |
| 42 | +acme-edge = { url = "git@gitlab.example.com:platform/mcpp-index.git", branch = "edge" } |
| 43 | +``` |
| 44 | + |
| 45 | +**键 = 命名空间名**。表内字段: |
| 46 | + |
| 47 | +| 字段 | 类型 | 说明 | |
| 48 | +|---|---|---| |
| 49 | +| `url` | string | git URL(必填,除非整个表用短形式) | |
| 50 | +| `rev` | string | 完整 commit SHA(40 字符)或唯一前缀。最强锁定。 | |
| 51 | +| `tag` | string | 标签名。等同 `rev=tag^{}`。 | |
| 52 | +| `branch` | string | 分支名。**追踪式**——`mcpp index update` 会拉新。 | |
| 53 | +| `path` | string | 本地路径(可选,绕过 git,适合开发本地索引)。 | |
| 54 | + |
| 55 | +**精确性等级**(从强到弱):`rev > tag > branch`。`rev` 哈希提供完 |
| 56 | +全可复现;`tag` 默认假设不可变(警告但允许 force-pushed tag); |
| 57 | +`branch` 写入 `mcpp.lock` 时会快照实际 sha,即"声明上跟踪 branch,但 |
| 58 | +本次构建用的是 sha X"。 |
| 59 | + |
| 60 | +短形式 `acme = "<url>"` ≡ `acme = { url = "<url>", branch = "main" }`(以 |
| 61 | +仓库默认分支为准)。 |
| 62 | + |
| 63 | +### 2.2 默认索引(`[indices]` 缺失时) |
| 64 | + |
| 65 | +``` |
| 66 | +mcpp = "https://github.com/mcpp-community/mcpp-index.git" (默认分支) |
| 67 | +``` |
| 68 | + |
| 69 | +显式声明 `[indices.mcpp]` 后,该项**完全替换**默认值——这意味着 |
| 70 | +锁定官方索引到固定 commit 的写法就是: |
| 71 | + |
| 72 | +```toml |
| 73 | +[indices] |
| 74 | +mcpp = { rev = "abc123def" } # url 省略 → 用默认 URL |
| 75 | +``` |
| 76 | + |
| 77 | +`url` 字段在 mcpp 内置默认值后允许省略,这样大多数用户只需要写一行 |
| 78 | +即可锁版本。 |
| 79 | + |
| 80 | +### 2.3 全局配置兜底(`~/.mcpp/config.toml`) |
| 81 | + |
| 82 | +为了避免每个项目都重复写企业内网索引,mcpp 同时读 `~/.mcpp/config.toml` |
| 83 | +里的 `[indices]`(已存在,扩展): |
| 84 | + |
| 85 | +```toml |
| 86 | +# ~/.mcpp/config.toml |
| 87 | +[indices] |
| 88 | +acme = { url = "git@gitlab.example.com:platform/mcpp-index.git", branch = "main" } |
| 89 | +``` |
| 90 | + |
| 91 | +合并规则:**项目 `mcpp.toml` > 全局 `config.toml` > 内置默认**。命名 |
| 92 | +空间冲突以最近一层为准。 |
| 93 | + |
| 94 | +### 2.4 CLI 命令 |
| 95 | + |
| 96 | +| 命令 | 行为 | |
| 97 | +|---|---| |
| 98 | +| `mcpp index list` | 列出所有索引(标注来源:project / global / built-in)。 | |
| 99 | +| `mcpp index update [<ns>...]` | 拉取索引最新提交。`branch` 跟踪式按 `git pull --ff-only`;`rev`/`tag` no-op。 | |
| 100 | +| `mcpp index pin <ns> [<rev>]` | 把当前索引解析到的 commit 写回 `mcpp.toml` 作为 `rev`。空 `<rev>` = 用当前 HEAD。 | |
| 101 | +| `mcpp index unpin <ns>` | 反向操作:删除 `rev`,改回 `branch="main"`。 | |
| 102 | + |
| 103 | +## 3. 内部模型 |
| 104 | + |
| 105 | +### 3.1 数据结构 |
| 106 | + |
| 107 | +```cpp |
| 108 | +// src/manifest.cppm |
| 109 | +struct IndexSpec { |
| 110 | + std::string namespace_; // 表 key |
| 111 | + std::string url; // 解析时填补默认 URL |
| 112 | + std::string rev; // 完整 sha(若有) |
| 113 | + std::string tag; // 若 rev 为空但 tag 非空 |
| 114 | + std::string branch; // 若 rev/tag 都空 |
| 115 | + std::filesystem::path path; // 本地索引(测试用) |
| 116 | +}; |
| 117 | +struct Manifest { |
| 118 | + ... |
| 119 | + std::map<std::string, IndexSpec> indices; // ns → spec |
| 120 | +}; |
| 121 | +``` |
| 122 | +
|
| 123 | +### 3.2 索引存储 |
| 124 | +
|
| 125 | +``` |
| 126 | +~/.mcpp/index/ |
| 127 | +├── mcpp/ # ns dir |
| 128 | +│ ├── abc123def.../ # commit-pinned checkout |
| 129 | +│ └── HEAD -> ./abc123def.../ # symlink to active checkout |
| 130 | +├── mcpplibs/ |
| 131 | +│ └── ... |
| 132 | +└── acme/ |
| 133 | + └── ... |
| 134 | +``` |
| 135 | +
|
| 136 | +每个 `<ns>/<sha>/` 是一次 `git clone --no-checkout` + `git checkout <sha>` |
| 137 | +的结果。同一 ns 多个 sha 共存,旧的可被 `mcpp index gc` 回收。 |
| 138 | +
|
| 139 | +`HEAD` symlink 指向当前 `mcpp.toml` 解析到的 commit——fetcher 直接 |
| 140 | +读 `~/.mcpp/index/<ns>/HEAD/pkgs/...`,不感知具体 sha。 |
| 141 | +
|
| 142 | +### 3.3 锁定到 mcpp.lock |
| 143 | +
|
| 144 | +`mcpp.lock` 现状只锁包版本,不锁索引 commit。新增 `[indices.<ns>]` 段: |
| 145 | +
|
| 146 | +```toml |
| 147 | +# mcpp.lock |
| 148 | +version = 2 |
| 149 | +
|
| 150 | +[indices.mcpp] |
| 151 | +url = "https://github.com/mcpp-community/mcpp-index.git" |
| 152 | +rev = "abc123def0123456789abcdef0123456789abcd" # 全 40 字符 |
| 153 | +
|
| 154 | +[indices.mcpplibs] |
| 155 | +url = "https://github.com/mcpplibs/mcpp-index.git" |
| 156 | +rev = "0123456789abcdef0123456789abcdef01234567" |
| 157 | +
|
| 158 | +[[package]] |
| 159 | +name = "gtest" |
| 160 | +namespace = "mcpp" |
| 161 | +version = "1.15.2" |
| 162 | +source = "index+mcpp@abc123def..." |
| 163 | +``` |
| 164 | + |
| 165 | +`source` 字段从 `mcpp-index+<url>` 升级为 `index+<ns>@<short-sha>`,显 |
| 166 | +式记录是哪个索引、哪个 commit 给的版本。 |
| 167 | + |
| 168 | +`mcpp build` 流程: |
| 169 | +1. 读 `mcpp.toml` 与 `mcpp.lock`。 |
| 170 | +2. 对每个 `[indices.<ns>]`: |
| 171 | + - `mcpp.lock` 已锁 → 直接用锁文件里的 sha,**不联网**。 |
| 172 | + - `mcpp.lock` 未锁 → 按 `mcpp.toml` 里的 spec 解析(rev/tag/branch), |
| 173 | + 拉取 / 复用本地缓存,写回 lock。 |
| 174 | +3. 对每个 dep 按 `(namespace, name)` 路由到对应索引。 |
| 175 | + |
| 176 | +`mcpp update` 显式忽略 lock 中的索引 sha,重新解析,然后写回新 sha—— |
| 177 | +这是**唯一**会让索引 sha 漂移的命令。 |
| 178 | + |
| 179 | +### 3.4 索引内部布局(对 mcpp-index 仓自身的要求) |
| 180 | + |
| 181 | +每个索引仓的根布局(本 PR 内不强制,但作为推荐): |
| 182 | + |
| 183 | +``` |
| 184 | +<index-root>/ |
| 185 | +├── pkgs/ |
| 186 | +│ ├── <name-letter>/<name>.lua # 用 name 首字母分桶,跟当前一致 |
| 187 | +│ └── ... |
| 188 | +├── mcpp-index.toml # 索引元数据(可选) |
| 189 | +└── README.md |
| 190 | +``` |
| 191 | + |
| 192 | +`mcpp-index.toml`(未来扩展点): |
| 193 | + |
| 194 | +```toml |
| 195 | +spec_version = 1 |
| 196 | +namespace = "mcpplibs" # 仓内默认 namespace,描述符没写时兜底 |
| 197 | +description = "mcpplibs C++ modular packages" |
| 198 | +maintainers = ["..."] |
| 199 | +``` |
| 200 | + |
| 201 | +每个 lua 描述符里的 `namespace` 字段(xpkg 标准)优先;缺失则用仓的 |
| 202 | +默认 namespace;再缺失则用查询时的 namespace。 |
| 203 | + |
| 204 | +## 4. 解析流程图 |
| 205 | + |
| 206 | +``` |
| 207 | +mcpp build |
| 208 | + │ |
| 209 | + ├── 1. parse mcpp.toml → Manifest (含 indices, dependencies) |
| 210 | + ├── 2. parse mcpp.lock → 已锁 sha 表 |
| 211 | + ├── 3. for each ns in deps.namespaces: |
| 212 | + │ a. 找对应 [indices.<ns>] 的 IndexSpec |
| 213 | + │ b. 若 lock 有 sha → ensure_checkout(ns, sha) |
| 214 | + │ c. 若 lock 无 sha → resolve_to_sha(spec) → ensure_checkout |
| 215 | + │ d. 写回 lock |
| 216 | + ├── 4. for each (ns, name) in deps: |
| 217 | + │ a. read ~/.mcpp/index/<ns>/HEAD/pkgs/<l>/<name>.lua |
| 218 | + │ b. semver-resolve version |
| 219 | + │ c. fetch source, build, ... |
| 220 | + └── 5. emit ninja, link |
| 221 | +``` |
| 222 | + |
| 223 | +## 5. 兼容性 / 迁移 |
| 224 | + |
| 225 | +### 5.1 现有项目(无 `[indices]`) |
| 226 | + |
| 227 | +`mcpp.toml` 不含 `[indices]` → mcpp 自动注入内置默认: |
| 228 | + |
| 229 | +```cpp |
| 230 | +m.indices["mcpp"] = IndexSpec { |
| 231 | + .namespace_ = "mcpp", |
| 232 | + .url = "https://github.com/mcpp-community/mcpp-index.git", |
| 233 | + .branch = "", // 等同当前行为:用仓的默认分支 + 不锁定 |
| 234 | +}; |
| 235 | +``` |
| 236 | + |
| 237 | +行为与今天**完全一致**(包括非确定性问题,但这是已知现状)。 |
| 238 | + |
| 239 | +### 5.2 `mcpp.lock` schema bump |
| 240 | + |
| 241 | +`version = 1` → `version = 2`。读到 v1 时: |
| 242 | +- 把所有包的 `source = "mcpp-index+<url>"` 当作"无索引锁",触发一次 |
| 243 | + 在线解析后写回 v2 形式。 |
| 244 | +- 不删除现有 v1 包条目。 |
| 245 | + |
| 246 | +### 5.3 与命名空间 PR (PR-A) 的关系 |
| 247 | + |
| 248 | +PR-A 已经引入 `(namespace, name)`。本 PR 把 namespace 跟"索引来源"挂 |
| 249 | +钩——`namespace == ns of an [indices.<ns>] entry`。 |
| 250 | + |
| 251 | +### 5.4 与 xim 的关系 |
| 252 | + |
| 253 | +`xlings install` 仍然管全局工具链(gcc / mcpp 自身),不参与 mcpp-index |
| 254 | +的拉取——mcpp 直接用 git。这避免了"用 xim-pkgindex 装 mcpp-index"这 |
| 255 | +种循环依赖。 |
| 256 | + |
| 257 | +## 6. 错误处理 |
| 258 | + |
| 259 | +| 场景 | 行为 | |
| 260 | +|---|---| |
| 261 | +| 引用未声明的 namespace(如 `acme:foo` 但没 `[indices.acme]`) | 报错并提示"add `[indices.acme]` to mcpp.toml or `~/.mcpp/config.toml`" | |
| 262 | +| 索引 git URL 网络不通(首次拉取) | 报错;若 `~/.mcpp/index/<ns>/<sha>/` 存在则降级到离线缓存 | |
| 263 | +| `rev` / `tag` 在远端不存在 | 报错;不要静默切换到默认分支 | |
| 264 | +| `mcpp.lock` 锁的 sha 在本地缓存被人为删了 | 触发一次拉取重建 | |
| 265 | +| 同一 namespace 在 project + global config 都声明 | 用 project 的(无 warning,符合 cargo 行为) | |
| 266 | + |
| 267 | +## 7. 落地步骤 |
| 268 | + |
| 269 | +按 PR 拆分,每一步独立可测试: |
| 270 | + |
| 271 | +### PR-1:Manifest schema + 解析 |
| 272 | + |
| 273 | +- `Manifest::indices` 字段 |
| 274 | +- `[indices]` 段 toml 解析(短形式 + inline 表 + ns key) |
| 275 | +- 单元测试覆盖 5+ 种写法(短/长/rev/tag/branch/path) |
| 276 | +- 不接 fetcher,不影响构建(默认值兜底) |
| 277 | + |
| 278 | +### PR-2:Index 存储与解析 |
| 279 | + |
| 280 | +- `~/.mcpp/index/<ns>/<sha>/` 布局 + 拉取 |
| 281 | +- `resolve_to_sha`(rev/tag/branch → 实际 sha) |
| 282 | +- `ensure_checkout`(幂等) |
| 283 | +- 单元 + e2e:本地 path 索引(避免 e2e 依赖网络) |
| 284 | + |
| 285 | +### PR-3:fetcher 改造为按 namespace 路由 |
| 286 | + |
| 287 | +- `fetcher::open(namespace, name)` 替代当前的全局 `pkgs/<l>/<name>.lua` 查询 |
| 288 | +- 兼容 layer:`namespace == "mcpp"` 时仍能读老式平铺索引 |
| 289 | +- e2e:多 namespace 多索引混合工程 |
| 290 | + |
| 291 | +### PR-4:Lockfile schema v2 |
| 292 | + |
| 293 | +- 读写 `[indices.<ns>]` 段 |
| 294 | +- v1 → v2 自动迁移 |
| 295 | +- `mcpp.lock` 里 dep 条目带 `namespace` 字段 |
| 296 | + |
| 297 | +### PR-5:CLI |
| 298 | + |
| 299 | +- `mcpp index list/update/pin/unpin` |
| 300 | +- 错误信息友好化 |
| 301 | + |
| 302 | +### PR-6:文档 |
| 303 | + |
| 304 | +- `docs/40-package-index-config.md` |
| 305 | +- `mcpp-index` README 更新(声明 `mcpp-index.toml` 期望) |
| 306 | + |
| 307 | +## 8. 设计决策的取舍 |
| 308 | + |
| 309 | +| 决策 | 选 | 弃 | 原因 | |
| 310 | +|---|---|---|---| |
| 311 | +| 锁定粒度 | commit sha | 索引仓的"语义版本" | 索引仓不打 release,sha 是唯一稳定标识 | |
| 312 | +| 短形式 vs inline 表 | 都支持 | 强制 inline 表 | 短形式覆盖 80% 常见情况 | |
| 313 | +| 默认分支跟踪 | 允许但不推荐 | 全部强制 rev | 开发期需要追上游;生产用 `mcpp index pin` | |
| 314 | +| 索引存储位置 | `~/.mcpp/index/` | 跟项目 target 走 | 多项目共享缓存,git clone 一次即可 | |
| 315 | +| 索引仓元数据文件 | `mcpp-index.toml`(未来) | 现在不要求 | 优先收敛 PR-1~PR-3,元数据是后话 | |
| 316 | +| 多 namespace 同 url | 允许(各自独立 checkout) | 强制去重 | 极少见,实现复杂 | |
| 317 | +| 全局 config.toml | 与项目 toml 合并 | 仅项目 toml | 企业内网索引复用 | |
| 318 | + |
| 319 | +## 9. 未决问题(PR 实现前需确认) |
| 320 | + |
| 321 | +1. **`mcpp index pin` 是否要强制全部 namespace 一起 pin**? |
| 322 | + 倾向:默认只 pin 当前 ns;`--all` 一并 pin。 |
| 323 | + |
| 324 | +2. **跨 namespace 的 deps 重复名怎么处理**?例 `acme:cmdline` 和 |
| 325 | + `mcpplibs:cmdline` 在同一项目共存——理论支持,但实际 BMI / link |
| 326 | + 名字会冲突。倾向:link 时注入 namespace 前缀(`libacme-cmdline.a`)。 |
| 327 | + 本 PR 不处理,留给"namespace 隔离链接"独立 PR。 |
| 328 | + |
| 329 | +3. **是否允许把 `[dependencies]` 里的 dep 显式绑定到非默认 ns 的索 |
| 330 | + 引**?例如: |
| 331 | + |
| 332 | + ```toml |
| 333 | + [dependencies.acme] |
| 334 | + foo = { version = "1.0.0", index = "acme-edge" } |
| 335 | + ``` |
| 336 | + |
| 337 | + 倾向:**不支持**,保持 namespace 与索引一一对应。需要切换索引就 |
| 338 | + 换 namespace。 |
| 339 | + |
| 340 | +## 10. 不在范围内 |
| 341 | + |
| 342 | +- 索引内部权限控制(读写 ACL)——交给 git server。 |
| 343 | +- 索引仓的镜像 / CDN——交给 git host。 |
| 344 | +- 索引签名校验 / supply-chain 防护——独立大 PR,后续设计。 |
| 345 | +- xpkg 描述符的 `namespace` 字段强制 schema 验证——等命名空间 PR-B |
| 346 | + 整理 mcpp-index 时一并处理。 |
| 347 | + |
| 348 | +--- |
| 349 | + |
| 350 | +**附录 A**:与 cargo `[source]` / npm `registry` 的对比简记 |
| 351 | + |
| 352 | +| 维度 | mcpp `[indices]` | cargo `[source]` | npm `.npmrc` | |
| 353 | +|---|---|---|---| |
| 354 | +| 锁定粒度 | git commit sha | crates.io 时间戳 | tarball sha512 | |
| 355 | +| 多源 | namespace 路由 | replace-with 链 | scoped registry | |
| 356 | +| 私有 | git URL + ssh | sparse-index + token | scoped registry + token | |
| 357 | +| 离线 | 本地 path 索引 + 缓存 | `--offline` + 缓存 | `--offline` + 缓存 | |
| 358 | + |
| 359 | +mcpp 的优势:**git 原生**——不需要发明索引格式 / 索引服务,clone 一 |
| 360 | +个仓就是一个索引;劣势:无 fast-path 增量(每次拉全仓)。规模大到这 |
| 361 | +个成为瓶颈时再考虑 sparse-index / 索引镜像。 |
0 commit comments