摘要
mcpp 内部两条规则对同一份 xpkg 描述符的身份口径不一致:
- 身份归一化层认为
namespace = "chriskohlhoff" + name = "asio"(split 形式)与 name = "chriskohlhoff.asio"(FQN 形式)等价、都合法;
- 安装目标构造层却硬假设描述符的
name 字面值就等于 ns + "." + shortName。
结果:一个用 split 形式书写的索引包能通过 mcpp xpkg parse、能通过 mcpp 自己的身份校验闸门,却没有任何消费端写法能把它装上 —— 一律 E_NOT_FOUND。
真实事故:mcpp-index 的 pkgs/c/chriskohlhoff.asio.lua 把 name 从 "chriskohlhoff.asio" 改成 "asio"(namespace 不变),lint job 全绿,三个平台的 workspace job 全部在跑满 20~58 分钟后挂掉。
复现
索引侧 pkgs/c/chriskohlhoff.asio.lua:
package = {
spec = "1",
namespace = "chriskohlhoff",
name = "asio", -- split 形式
xpm = { linux = { ["1.38.1"] = { url = ..., sha256 = ... } }, ... },
mcpp = { ... },
}
消费端 mcpp.toml:
[indices]
chriskohlhoff = { path = "../../.." }
[dependencies.chriskohlhoff]
asio = "1.38.1"
mcpp build(mcpp 0.0.102,vendored xlings 0.4.67):
error: xlings install_packages failed (exit 1) for 'chriskohlhoff.asio@1.38.1'
with 1 index repo configured [chriskohlhoff -> <index path>]
xlings reported: E_NOT_FOUND: package 'chriskohlhoff:chriskohlhoff.asio@1.38.1' not found
把 name 改回 "chriskohlhoff.asio",同一条命令、同一个 pin 立即通过(消费端一个字没动)。
根因
(1) 身份归一化接受 split 形式 —— src/manifest/xpkg.cppm:649-671 canonical_xpkg_identity:
// Step 2 — A declared name already prefixed with the namespace IS the FQN;
// otherwise the namespace is prepended.
std::string prefix = ns + ".";
fqn = name.starts_with(prefix) ? name : ns + "." + name;
(ns="chriskohlhoff", name="asio") 和 (ns="chriskohlhoff", name="chriskohlhoff.asio") 归一化到同一身份。设计文档 .agents/docs/2026-06-26-identity-first-resolution-no-filename.md 也明确写了两种拼写等价。所以 mcpp xpkg parse 打印的是 package chriskohlhoff.asio (namespace 'chriskohlhoff') —— 看起来完全正常。
(2) 安装目标构造假设 name 已是 FQN 形式 —— src/build/prepare.cppm:1713-1717 的注释把这个假设写得很明白:
// xlings resolves packages by the full qualified name (ns.shortName)
// as it appears in the index's name field. Use fqname, not the
// map key (which may be a bare short name for default-ns deps).
auto fqname = ns.empty() ? shortName : std::format("{}.{}", ns, shortName);
以及 :1752-1757:
// For custom indices, use indexName:fullPackageName@version so
// xlings resolves the package by the descriptor's name field while
// still selecting the project-added index.
target = std::format("{}:{}@{}", idxSpec->name, fqname, version);
而 xlings/libxpkg 建索引时 key 就是 package.name 的字面值(libxpkg/src/xpkg-loader.cppm:643-647,index.entries[pkg.name])—— 也就是说 libxpkg 完全是按 mcpp 注释里写的契约在做事,偏差在 mcpp 这一侧:它接受了 split 形式,却按 FQN 形式去点名。
(3) mcpp 手里其实有 name 的字面值,却没用。 src/build/prepare.cppm:1580-1588 loadVersionDep 在调 xlings 之前就已经把描述符读进来了(readLuaContent() → read_xpkg_lua_from_path),并靠 canonical_xpkg_identity 校验才放行 —— 把 name 改成一个真正不匹配的值(如 "ZZZ")时,报的是 mcpp 自己的 dependency '...': not found in local index,根本走不到 xlings。所以 luaContent(含 name = "asio")在作用域内,只是构造 target 时没有使用。
消费端无解
实测四种写法(mcpp 0.0.102,索引为上述 split 形式描述符),没有任何一种能装上:
| 消费端写法 |
发给 xlings 的 target |
结果 |
[dependencies.chriskohlhoff] + asio |
chriskohlhoff:chriskohlhoff.asio@1.38.1 |
E_NOT_FOUND |
[dependencies.chriskohlhoff] + "chriskohlhoff.asio" |
— |
ns 拼两遍 → chriskohlhoff.chriskohlhoff.asio,mcpp 侧即失败 |
[dependencies] + "chriskohlhoff.asio"(点式选择器) |
chriskohlhoff:chriskohlhoff.asio@1.38.1 |
E_NOT_FOUND(归一化回 ns/short,同第一行) |
[indices] default + 裸 asio |
— |
身份闸门失败(描述符 ns=chriskohlhoff ≠ 默认 ns) |
原因是两条约束同时成立:身份闸门要求消费端的 ns == 描述符的 namespace;xlings 的 key == 描述符的 name。于是 fqname 恒为 chriskohlhoff.asio 而 key 恒为 asio,消费端能调的 ns 和短名都被钉死,够不着。等价于:name 必须 == namespace + "." + <短名>,而这一点目前没有任何地方强制。
期望
二选一(倾向 B):
A. 让 target 用描述符声明的 name。 loadVersionDep 里 luaContent 已在作用域内,manifest/xpkg.cppm 也已有取 name 的现成函数;构造 target 时改用它,split 形式就真正可用了。代价:同一个包在索引里出现两种合法名字,mcpp search、store 目录名(package_store_name = ns + "-x-" + name)、错误信息都会跟着分叉。
B. 收紧规范:index 描述符的 name 必须写 FQN 形式,split 形式在 mcpp xpkg parse 报错。 也就是把 canonical_xpkg_identity 的 Step 2 从"容忍"改成"校验",让这类描述符在 lint 阶段(秒级)就被拦下,而不是三个平台跑满一小时后才炸。这也与 prepare.cppm 现有注释里的契约一致。
不论选哪个,mcpp xpkg parse 都应该能发现这个问题 —— 目前它只在 name 缺失时报错(src/cli/cmd_xpkg.cppm:80-85),对 split 形式静默放行,索引 CI 因此完全失去了防护。
环境
- mcpp 0.0.102(vendored xlings 0.4.67)复现;0.0.103(xlings 0.4.68)解析路径无变更(
git diff v0.0.102..v0.0.103 -- src/pm src/build/prepare.cppm src/manifest/xpkg.cppm 只动了 index_spec.cppm 的 artifact/source 字段),同样不可用。
- 注意一个容易误判的陷阱:如果本机全局
~/.mcpp/registry/data/mcpplibs 还停在改动之前的快照(旧描述符里 name 仍是 FQN 形式),本地 mcpp build 会从那份旧索引满足请求而显得正常,只有干净 registry 的 CI 会炸。排查时可以用一个只存在于 path index 的版本号做探针来排除遮蔽。
- 事故现场:mcpplibs/mcpp-index run 30121144277(linux/macos/windows 三平台同一处失败)。
摘要
mcpp 内部两条规则对同一份 xpkg 描述符的身份口径不一致:
namespace = "chriskohlhoff"+name = "asio"(split 形式)与name = "chriskohlhoff.asio"(FQN 形式)等价、都合法;name字面值就等于ns + "." + shortName。结果:一个用 split 形式书写的索引包能通过
mcpp xpkg parse、能通过 mcpp 自己的身份校验闸门,却没有任何消费端写法能把它装上 —— 一律E_NOT_FOUND。真实事故:mcpp-index 的
pkgs/c/chriskohlhoff.asio.lua把name从"chriskohlhoff.asio"改成"asio"(namespace 不变),lint job 全绿,三个平台的 workspace job 全部在跑满 20~58 分钟后挂掉。复现
索引侧
pkgs/c/chriskohlhoff.asio.lua:消费端
mcpp.toml:mcpp build(mcpp 0.0.102,vendored xlings 0.4.67):把
name改回"chriskohlhoff.asio",同一条命令、同一个 pin 立即通过(消费端一个字没动)。根因
(1) 身份归一化接受 split 形式 ——
src/manifest/xpkg.cppm:649-671canonical_xpkg_identity:(ns="chriskohlhoff", name="asio")和(ns="chriskohlhoff", name="chriskohlhoff.asio")归一化到同一身份。设计文档.agents/docs/2026-06-26-identity-first-resolution-no-filename.md也明确写了两种拼写等价。所以mcpp xpkg parse打印的是package chriskohlhoff.asio (namespace 'chriskohlhoff')—— 看起来完全正常。(2) 安装目标构造假设
name已是 FQN 形式 ——src/build/prepare.cppm:1713-1717的注释把这个假设写得很明白:以及
:1752-1757:而 xlings/libxpkg 建索引时 key 就是
package.name的字面值(libxpkg/src/xpkg-loader.cppm:643-647,index.entries[pkg.name])—— 也就是说 libxpkg 完全是按 mcpp 注释里写的契约在做事,偏差在 mcpp 这一侧:它接受了 split 形式,却按 FQN 形式去点名。(3) mcpp 手里其实有
name的字面值,却没用。src/build/prepare.cppm:1580-1588loadVersionDep在调 xlings 之前就已经把描述符读进来了(readLuaContent()→read_xpkg_lua_from_path),并靠canonical_xpkg_identity校验才放行 —— 把name改成一个真正不匹配的值(如"ZZZ")时,报的是 mcpp 自己的dependency '...': not found in local index,根本走不到 xlings。所以luaContent(含name = "asio")在作用域内,只是构造 target 时没有使用。消费端无解
实测四种写法(mcpp 0.0.102,索引为上述 split 形式描述符),没有任何一种能装上:
[dependencies.chriskohlhoff]+asiochriskohlhoff:chriskohlhoff.asio@1.38.1[dependencies.chriskohlhoff]+"chriskohlhoff.asio"chriskohlhoff.chriskohlhoff.asio,mcpp 侧即失败[dependencies]+"chriskohlhoff.asio"(点式选择器)chriskohlhoff:chriskohlhoff.asio@1.38.1[indices] default+ 裸asio原因是两条约束同时成立:身份闸门要求消费端的 ns == 描述符的
namespace;xlings 的 key == 描述符的name。于是 fqname 恒为chriskohlhoff.asio而 key 恒为asio,消费端能调的 ns 和短名都被钉死,够不着。等价于:name必须 ==namespace + "." + <短名>,而这一点目前没有任何地方强制。期望
二选一(倾向 B):
A. 让 target 用描述符声明的
name。loadVersionDep里luaContent已在作用域内,manifest/xpkg.cppm也已有取name的现成函数;构造 target 时改用它,split 形式就真正可用了。代价:同一个包在索引里出现两种合法名字,mcpp search、store 目录名(package_store_name=ns + "-x-" + name)、错误信息都会跟着分叉。B. 收紧规范:index 描述符的
name必须写 FQN 形式,split 形式在mcpp xpkg parse报错。 也就是把canonical_xpkg_identity的 Step 2 从"容忍"改成"校验",让这类描述符在 lint 阶段(秒级)就被拦下,而不是三个平台跑满一小时后才炸。这也与prepare.cppm现有注释里的契约一致。不论选哪个,
mcpp xpkg parse都应该能发现这个问题 —— 目前它只在name缺失时报错(src/cli/cmd_xpkg.cppm:80-85),对 split 形式静默放行,索引 CI 因此完全失去了防护。环境
git diff v0.0.102..v0.0.103 -- src/pm src/build/prepare.cppm src/manifest/xpkg.cppm只动了index_spec.cppm的 artifact/source 字段),同样不可用。~/.mcpp/registry/data/mcpplibs还停在改动之前的快照(旧描述符里name仍是 FQN 形式),本地mcpp build会从那份旧索引满足请求而显得正常,只有干净 registry 的 CI 会炸。排查时可以用一个只存在于 path index 的版本号做探针来排除遮蔽。