Repository navigation
descriptor spec 与安装目标口径不一致:package.name 写成 split 形式(namespace + 短名)的索引包无法被任何消费端安装,且 lint 静默放行 #278
Description
摘要
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 三平台同一处失败)。
Activity
Sunrisepeak commentedon Jul 24, 2026
补一个让这个坑更容易踩到的路径:
mcpp emit xpkg本身会生成 split 形式的雏形。src/pm/publisher.cppm:133只写name = manifest.package.name(项目名,裸的),并且完全不输出namespace字段。而索引要求 namespaced 包声明 namespace,于是维护者把包归档进 index 时手工补一行namespace = "<org>"—— 补完的那一刻,描述符就变成了 split 形式,mcpp xpkg parse照样 OK,安装却已经坏了。mcpp-index 里
pkgs/t/tensorvia-cpu.lua正是这么来的(文件头还留着-- AUTO-GENERATED by mcpp emit xpkg. Do not edit by hand.,namespace = "aimol"是后补的),它今天没有被任何 workspace member 消费,所以一直没暴露,但同样装不上。所以如果采纳收紧方案(B),建议一并让
mcpp emit xpkg支持/输出 namespace,并把name直接写成 FQN 形式,否则"生成 → 手工补 namespace → 破"这条路径还会继续复现。索引侧的临时守卫:mcpplibs/mcpp-index#116(lint 在
namespace非空时强制name以"<namespace>."开头)。fix(pkgs): chriskohlhoff.asio name 回归 FQN 形式 + lint 守卫 (mcpp#278) (#116)
docs: check off #278 implementation plan; record T6/T8 actual outcomes
docs: record #278 measured verification results and the two implement…
fix: #278 package identity convergence — index side + dependency side…
feat: SPEC-001 package identity — name is one segment, wire key is th…
feat(pm): give namespace omission a one-release exit ramp
feat: converge template identity, runtime contracts, and release state (
feat: search/add 显示包的可用版本 (#487)
feat: mcpp clean --stale, search/add showing what a package publishes…