Repository navigation
xpkg store 的已安装查找忽略 namespace:同短名同版本的包会静默顶替,install() 被跳过 #533
Description
概述
xpkg store 的目录是带命名空间的(<ns>-x-<name>/<version>/),但判断「这个包装过没有」的那层查找不比对 namespace,只看 (name, version)。于是不同命名空间下同短名同版本的两个包会互相顶替 —— 而且失败是静默的,最终报出来的错误和真正的原因毫无关系。
实测环境:mcpp 2026.8.29.1,xlings 2026.8.27.4。
复现
compat.libdrm 是一个源码构建包,install() 里建两个 include 根(libdrm 的公开头在根、uapi 头在 libdrm/ 子目录)。把它的版本定为上游版本 2.4.123:
-- pkgs/c/compat.libdrm.lua
["2.4.123"] = {
url = { GLOBAL = "https://dri.freedesktop.org/libdrm/libdrm-2.4.123.tar.xz" },
sha256 = "a2b98567a149a74b0f50e91e825f9c0315d86e7be9b74394dae8b298caadb79e",
},在任何装了 Mesa 的机器上构建都会失败,因为 xim:mesa 的依赖里有 xim:libdrm@>=2.4,所以 store 里一定有 xim-x-libdrm/2.4.123。
发生了什么
$ mcpp test
Compiling compat.libdrm v2.4.123 <- 看起来解析成功
error: build failed
failed: bin/libdrm.so
-shared @bin/libdrm.so.rsp -o bin/libdrm.so ... -Wl,-soname,libdrm.so.2
/bin/sh: 1: -shared: not found
/bin/sh: 1: -shared: not found 与真正的原因隔了三层:
- store 认为
libdrm@2.4.123已安装(它看到的是xim-x-libdrm/2.4.123),于是跳过install(); - 但版本目录照样被建出来并标记为装好了:
$ ls -a <store>/compat-x-libdrm/2.4.123/
. .. .mcpp_ok .xpkg-install.json mcpp_generated/
.mcpp_ok、.xpkg-install.json 都在,mcpp_generated/ 是 mcpp 自己的 generated_files 写的 —— 只有描述符 install() 该建的那棵树不存在;
- 于是
sources一个都没匹配上,mcpp 生成了一条零输入的c_shared边:
build bin/libdrm.so : c_shared # 对照正常情况:c_shared obj/a.o obj/b.o ...- 因为没有对象要编,
cc变量根本没被写进build.ninja(只有cxx),$cc -shared ...展开成-shared ...,shell 去执行-shared。
关键点:与是否声明该依赖无关
这一点值得强调 —— 我最初以为是「本包声明了 xim:libdrm 才会撞」,不是。上面这个 compat.libdrm 完全没有 xim:* 依赖(纯源码构建,deps = {}),撞的是 store 里碰巧存在的任何同名同版本包。
判定方法
把版本号临时改成一个不可能撞的值:
-["2.4.123"] = {
+["2.4.123-probe"] = {install() 立刻被调用,而且里面的错误会大声报出来:
error: xlings install_packages failed (exit 1) for 'compat.libdrm@2.4.123-probe'
xlings reported: E_INTERNAL: [libdrm] failed: install hook failed:
compat.libdrm.lua:219: attempt to call a nil value (field 'tmpdir')
同一份描述符,只差版本号:一个静默跳过、报无关错误,一个正常执行、错误可见。
影响
这条规则实际上是在说:索引里任何 compat 包的 <短名>@<版本> 都不能与生态里某个 xim:* 包相同,而包作者无从知道生态里有什么、将来会加什么。
具体到这次的图形栈,四个包全部被迫改版本号:
| 包 | 想用 | 实际只能用 | 撞的是 |
|---|---|---|---|
compat.libdrm |
2.4.123 |
2.4.123.1 |
xim:libdrm@2.4.123 |
compat.wayland |
1.23.1 |
1.23.1.1 |
xim:wayland@1.23.1 |
freedesktop.wayland(模块层) |
1.23.1 |
1.23.1.2 |
上面两个 |
compat.libffi |
3.4.4(与生态同版) |
3.4.8 |
xim:libffi@3.4.4 |
compat.expat 同理只能取 2.7.1 而不是生态的 2.6.2。
也就是说:一个包想和生态用同一个上游版本,是表达不出来的 —— 而这恰恰是最常见、最应该被鼓励的情形(消费者和 Mesa 用同一份 libdrm)。
建议
store 的已安装查找按 (namespace, name, version) 匹配,与目录布局 <ns>-x-<name>/<version>/ 一致。
这是 xlings#381(index 侧按 (namespace, name) 建键)在 store 侧的同一个缺口。
在修好之前,至少让它不要静默:如果查找命中了一个 namespace 不同的包,要么报错,要么在跳过 install() 时打印出「命中的是 <ns>-x-<name>/<version>」。现在的表现是把一个包管理器的身份问题伪装成了一条链接器命令行错误。
相关
- 索引侧的四个包与形态讨论:mcpplibs/mcpp-index(源码构建的图形栈)
- 顺带一个小的:Form A 的纯 C 库包(
[targets.x] kind = "shared",没有任何.cppm)会稳定报
warning: src/<name>.cppm: lib target without conventional lib root。C 库没有 lib root 可言,这个告警会传染给每个消费者。
Activity
chore(xlings): raise the floor to 2026.8.30.2 — the store-identity fix
fix(build): a package's generated inputs come before its compiles; an…
Sunrisepeak commentedon Aug 30, 2026
已修复并发布,分两个仓库。已在沙箱里对已发布产物核验过(见下)。
根因确实在 xlings 的 store 侧,但位置和你写的不同
你定位的
installer.cppm:903是七月的快照。上游后来把「这个包装过没有」的四个回答者收敛进了一个install_state模块 —— 而这条回落活了下来,成了那个模块存在的意义所要消灭的第五个回答者,位置搬到了installer.cpp:2628:主路径 (
catalog.cpp:302) 早就是带命名空间的;installation_state也收命名空间参数。只有这条回落不是,而它正是「没有installedhook」的源码构建包会走的那条。修法:
coordinate_from_payload_path自己的头注释就写着答案 —— 安装器写下了那个路径,所以<...>/xpkgs/<ns>-x-<package>/<version>不是关于包的证据,它就是安装时记录下的包身份。新的payload_path_names_another_package读它,回落对能证明属于别人的 DB 条目不再采信。刻意做成「拒绝」而不是「要求」:路径解析不出 store 坐标的(自管理工具、迁移过的载荷、
xpkgs/之外的任何东西)一律照旧接受。只有能证明归属不同的那一格改变了行为。→ openxlings/xlings#576,已发布 v2026.8.30.2
mcpp 侧另有两条独立缺陷,你报告里那条错误消息是其中之一
/bin/sh: 1: -shared: not found我用纯 mcpp 输入复现了 —— 一个匹配到零源码的kind = "shared",不碰 xlings。而且它有个没人报的静默孪生:kind = "lib"的同一缺陷退 0、写出 8 字节.a、Finished dev—— 消费者要到后面才因 undefined symbol 失败,离成因更远。所以修法是在 plan 期拒绝零输入链接单元(点名 target),而不是给链接器一条更好的消息。另外:
.mcpp_ok不再凭「callee 退 0 + 目录存在」落章 —— 那个目录是安装器动工之前就建好的。现在要求至少有一项不是 mcpp/xlings 自己写的。并且这个判据也跑在快路径上,所以已经被这个 bug 污染过的 store 会在下次构建自愈,不需要谁知道该删哪个目录。顺带你提的那条 lib root 告警也修了:谓词从「产不产库」收窄到「是不是模块库」。
→ #536,已发布 2026.8.30.1,内部 xlings floor 抬到 2026.8.30.2
沙箱核验(已发布产物,从索引装)
索引仍然不应该故意发布撞名的
<短名>@<版本>。 floor 以下的客户端照样静默跳过install(),而索引是所有已装客户端都要消费的数据。这个修复针对的是误撞 —— 按你机器上的数据,那已经是 20 个短名宽。所以你表格里那四个包的版本号,我的建议是不动:它们现在能用,而裸版本钉是精确的,回退要穷举每个消费者逐个重钉,期间旧客户端会静默失败。新包在 floor 被采纳后再用上游版本号,按包逐个判断。理由写在
.agents/docs/2026-08-30-cross-repo-fix-plan-532-533-534.md§1 和 §6.2。如果你认为这四个包值得回退,那是个产品判断,我把代价列在那份文档里了。