feat(glx-runtime): GL 来自生态而非 /usr/lib —— #352 的另一半 - #179
Merged
Conversation
This package symlinked the HOST's libGL/libEGL out of /usr/lib*. That is what mcpp#352 is: the host's Mesa needs GLIBC_2.43, mcpp's payload glibc is 2.39, and the program linked cleanly then exited 255 with no output at all. It is also the first entry on the xlings hermetic policy's forbidden list -- any .so under /usr/lib* or /lib*. 2026.08.08 takes the same libraries from `xim:graphics`: 22 packages plus two sentinels that probe for a host-side userspace half they do not own (the proprietary NVIDIA driver, WSL2's D3D12) and succeed having linked nothing when it is absent. One dependency covers every host shape and this file gained no conditional. The source is the SUBOS VIEW, not a payload directory. A payload path pins a version, so a consumer's RUNPATH would name mesa 25.0.7.1 forever and break the day it is upgraded; the view is the stable indirection, the role /run/opengl-driver plays on NixOS. The view also carries libc.so.6 and the rest of the C runtime, and those must never reach a consumer's RUNPATH -- the consumer runs under mcpp's payload loader, and a second libc paired with it faults inside the dynamic linker before main with no output. The pattern list is what keeps them out, so it is now a safety boundary with an assertion behind it rather than a convenience. That failure was reproduced on the way here, not imagined. libnvidia* leaves the pattern list: the proprietary driver reaches the subos through xim:nvidia-gl-host-link under the glvnd vendor names already matched, and taking it by its own name too would be a second route to one set of libraries. MCPP_HOST_GL_LIBRARY_PATH still works and is now the only door back to the host. It warns when used, because what it names was built against the host's glibc and loading it under the payload's is the #352 configuration exactly. Verified on an NVIDIA host, not reasoned about: a consumer whose RUNPATH is the generated directory resolves libEGL to it, and GL_RENDERER is "NVIDIA GeForce RTX 4080". Both halves are the criterion -- "a window appeared" is a false pass, since llvmpipe produces one too. The no-libc assertion was checked against the real view: 52 libraries linked, zero C-runtime entries. 2026.06.03 stays published so consumers already pinned to it keep resolving. compat.glfw, the only package that depends on this one, moves to the new pin.
CI: `E_INTERNAL: [glx-runtime] failed:` with an empty message, twice. The
`deps = { runtime = { "xim:graphics" } }` had been placed inside the
2026.08.08 version entry. The descriptor parsed, the graphics stack was never
installed, the subos therefore had no libGL.so.1, and the install failed on
the required-library check -- reporting the library rather than the misplaced
key, which is why the message looked like a missing package instead of a
typo.
Every other recipe in both indexes puts deps at platform level. Moving it
there means the legacy 2026.06.03 entry declares it too and pays for a
download it does not read; the alternative is deleting a published version,
which breaks anyone pinned to it outright.
Sunrisepeak
added a commit
that referenced
this pull request
Aug 8, 2026
重新应用 f44e896(#179),它在 #180 被整体 revert。改动本身是对的: compat.glx-runtime 之前从 /usr/lib* 符号链接**宿主的** libGL/libEGL,而 mcpp#352 正是这么来的 —— 宿主 Mesa 需要 GLIBC_2.43,mcpp 的 payload glibc 是 2.39,程序链接干净、退出 255、一行输出都没有。它也是 xlings hermetic 政策 禁止清单上的第一条。 被 revert 的原因不在这两个文件里。mesa 声明 `xim:glibc@>=2.38`,下限被任何 更高版本满足,于是 xim 在 2.39 旁边装了 2.44;而当时的 mcpp 用 readdir 的第一项 来解析「那个 glibc payload」,编译侧取了 2.44,产物的 interpreter 却仍是 2.39。 二进制开始引用 GLIBC_2.42 符号,而报错落在 asio-module 和 core 上 —— 两个不用 图形、不依赖 mesa、这次改动根本没碰的成员。 引擎侧已在 mcpp 2026.8.8.2 修好(runtime binding 从 subos 读,不再猜)。 真正缺的是**这里**的一个测试。这个仓库的每个测试都在测「它自己那个包」,所以 没有任何一个问出唯一重要的那个问题:装了这个,对**其他所有人**有没有影响? tests/check_graphics_install_side_effects.sh 就问这一个:装图形栈前后,各构建 一次与图形无关的成员,断言产物的 PT_INTERP 与 glibc 符号上界**逐字不变**。 它在任何早于 2026.8.8.2 的 mcpp 上都会失败,这是有意的 —— 图形栈不该落在一个 仍会犯这个错的工具链上。
Sunrisepeak
added a commit
that referenced
this pull request
Aug 8, 2026
重新应用 f44e896(#179),它在 #180 被整体 revert。改动本身是对的: compat.glx-runtime 之前从 /usr/lib* 符号链接**宿主的** libGL/libEGL,而 mcpp#352 正是这么来的 —— 宿主 Mesa 需要 GLIBC_2.43,mcpp 的 payload glibc 是 2.39,程序链接干净、退出 255、一行输出都没有。它也是 xlings hermetic 政策 禁止清单上的第一条。 被 revert 的原因不在这两个文件里。mesa 声明 `xim:glibc@>=2.38`,下限被任何 更高版本满足,于是 xim 在 2.39 旁边装了 2.44;而当时的 mcpp 用 readdir 的第一项 来解析「那个 glibc payload」,编译侧取了 2.44,产物的 interpreter 却仍是 2.39。 二进制开始引用 GLIBC_2.42 符号,而报错落在 asio-module 和 core 上 —— 两个不用 图形、不依赖 mesa、这次改动根本没碰的成员。 引擎侧已在 mcpp 2026.8.8.2 修好(runtime binding 从 subos 读,不再猜)。 真正缺的是**这里**的一个测试。这个仓库的每个测试都在测「它自己那个包」,所以 没有任何一个问出唯一重要的那个问题:装了这个,对**其他所有人**有没有影响? tests/check_graphics_install_side_effects.sh 就问这一个:装图形栈前后,各构建 一次与图形无关的成员,断言产物的 PT_INTERP 与 glibc 符号上界**逐字不变**。 它在任何早于 2026.8.8.2 的 mcpp 上都会失败,这是有意的 —— 图形栈不该落在一个 仍会犯这个错的工具链上。
Sunrisepeak
added a commit
that referenced
this pull request
Aug 8, 2026
两件事,都是评审提问逼出来的。 一、`MCPP_VERSION` 2026.8.6.2 → 2026.8.8.2。不是顺手升级。 `compat.glx-runtime` 依赖 mesa,mesa 声明 `xim:glibc@>=2.38`;下限被任何更高版本 满足,于是安装图形栈会在既有 glibc 旁边再装一个。2026.8.8.2 之前的 mcpp 用 `readdir` 的第一项解析「那个 glibc payload」,编译侧与产物 interpreter 因此可以 指向不同版本 —— 这正是 #179 落地后 asio-module 和 core 变红、整份改动被 #180 revert 的原因。用更旧的 mcpp 重新落地,就是在仍会犯这个错的引擎上复现事故条件。 二、`tests/check_graphics_install_side_effects.sh` 不被任何 workflow 引用。 它是一个永远不会运行的检查 —— 与它自己反对的东西同一形状。现在有专属作业, Linux,装 pin 住的 mcpp 后执行。 顺带回答「为什么触发了全量 CI」:选择规则里 `tests/*.sh` 一律 full。这条规则是为 共享测试骨架写的,而新文件匹配了它。就本 PR 而言全量恰恰是想要的 —— 上次坏掉的 正是与图形无关的成员,只跑图形相关的那几个看不见任何东西。 注意:动 `MCPP_VERSION` 会改变 registry 缓存键(它进 key 也进 restore-keys), 所以这一轮所有 workspace 作业都从冷缓存起步。潜伏的缺陷可能因此「突然出现」—— 那是暴露,不是新增。
Sunrisepeak
added a commit
that referenced
this pull request
Aug 8, 2026
* feat(glx-runtime): GL 来自生态而非 /usr/lib,这次带上缺失的那个测试 重新应用 f44e896(#179),它在 #180 被整体 revert。改动本身是对的: compat.glx-runtime 之前从 /usr/lib* 符号链接**宿主的** libGL/libEGL,而 mcpp#352 正是这么来的 —— 宿主 Mesa 需要 GLIBC_2.43,mcpp 的 payload glibc 是 2.39,程序链接干净、退出 255、一行输出都没有。它也是 xlings hermetic 政策 禁止清单上的第一条。 被 revert 的原因不在这两个文件里。mesa 声明 `xim:glibc@>=2.38`,下限被任何 更高版本满足,于是 xim 在 2.39 旁边装了 2.44;而当时的 mcpp 用 readdir 的第一项 来解析「那个 glibc payload」,编译侧取了 2.44,产物的 interpreter 却仍是 2.39。 二进制开始引用 GLIBC_2.42 符号,而报错落在 asio-module 和 core 上 —— 两个不用 图形、不依赖 mesa、这次改动根本没碰的成员。 引擎侧已在 mcpp 2026.8.8.2 修好(runtime binding 从 subos 读,不再猜)。 真正缺的是**这里**的一个测试。这个仓库的每个测试都在测「它自己那个包」,所以 没有任何一个问出唯一重要的那个问题:装了这个,对**其他所有人**有没有影响? tests/check_graphics_install_side_effects.sh 就问这一个:装图形栈前后,各构建 一次与图形无关的成员,断言产物的 PT_INTERP 与 glibc 符号上界**逐字不变**。 它在任何早于 2026.8.8.2 的 mcpp 上都会失败,这是有意的 —— 图形栈不该落在一个 仍会犯这个错的工具链上。 * ci: mcpp 2026.8.8.2 是这份改动的前置,而那个新检查从没被跑过 两件事,都是评审提问逼出来的。 一、`MCPP_VERSION` 2026.8.6.2 → 2026.8.8.2。不是顺手升级。 `compat.glx-runtime` 依赖 mesa,mesa 声明 `xim:glibc@>=2.38`;下限被任何更高版本 满足,于是安装图形栈会在既有 glibc 旁边再装一个。2026.8.8.2 之前的 mcpp 用 `readdir` 的第一项解析「那个 glibc payload」,编译侧与产物 interpreter 因此可以 指向不同版本 —— 这正是 #179 落地后 asio-module 和 core 变红、整份改动被 #180 revert 的原因。用更旧的 mcpp 重新落地,就是在仍会犯这个错的引擎上复现事故条件。 二、`tests/check_graphics_install_side_effects.sh` 不被任何 workflow 引用。 它是一个永远不会运行的检查 —— 与它自己反对的东西同一形状。现在有专属作业, Linux,装 pin 住的 mcpp 后执行。 顺带回答「为什么触发了全量 CI」:选择规则里 `tests/*.sh` 一律 full。这条规则是为 共享测试骨架写的,而新文件匹配了它。就本 PR 而言全量恰恰是想要的 —— 上次坏掉的 正是与图形无关的成员,只跑图形相关的那几个看不见任何东西。 注意:动 `MCPP_VERSION` 会改变 registry 缓存键(它进 key 也进 restore-keys), 所以这一轮所有 workspace 作业都从冷缓存起步。潜伏的缺陷可能因此「突然出现」—— 那是暴露,不是新增。 * ci: watch the home the build actually uses The side-effect check reported INCONCLUSIVE on its first CI run, correctly: a released mcpp is self-contained and resolves its registry from beside its own executable, so copying the payload tree into ~/.mcpp and pointing MCPP_HOME there watched one home while the build used another. MCPP_HOME is now the tarball root. The check was right; the job was wrong. * ci: 缓存 host tool store,并让 registry 缓存真正生效;补 cmdline / llmapi 成员 三件事,起因是 #181 的 CI 太长。 一、registry 缓存一直是摆设。 Download 步骤把 release 的 registry 拷进 ~/.mcpp/registry,而缓存也覆盖那里 —— 但发布版 mcpp 是 **self-contained**,从自己可执行文件旁边解析 registry。所以每次 构建用的是 <tarball>/registry,缓存恢复和保存的那份从没被读过。 证据取自加入 grpc-codegen 的那次 run:abseil 的源码编译自 `<tarball>/registry/data/xpkgs/compat-x-abseil/...`,而同一个 job 在报告 registry 缓存 **命中** 的情况下重新下载了 xim:glibc@2.44 和 xim:python@3.13.12。 修法是一行 `MCPP_HOME=$HOME/.mcpp`。payload 从此跨 run 复用。 二、host tool store 从不缓存。 同一次 run 实测:建 protoc 636s、建 grpc_cpp_plugin 660s —— 3363s 的成员里占 1296s,而且每个 job 每次 run 都重来。`protobuf-protoc`(945s)和 `grpc-module` (1724s)建的是同一个 protoc。 store 在 `$MCPP_HOME/build-cache/v1/tool`,本机实测 116MB —— 缓存得起。粗粒度滚动 键是安全的:store 按 `<包>@<版本>/<hash>` 内容寻址,且 mcpp 逐字段比对 entry.json(epoch、target、host triple、编译器身份、profile、features、传递依赖 闭包),对不上就重建而不是误用。 刻意不缓存 `build-cache/v1/pkg`:本机 6.0GB,而 Actions 每仓库 10GB —— 塞进去会把 更需要的 registry 缓存挤掉。 三、cmdline 和 llmapi 有描述符却没有成员。 两个都补上,并且是**消费型**测试而不是「能链上就算过」: - cmdline 用 `parse_from` 解析真实命令行,断言 positional、长选项、`--opt=value`; 再断言**缺少必填参数会被拒绝** —— 没有这一条,前面几条对一个「什么都接受」的 解析器同样成立。 - llmapi 全部断言离线。它是 HTTP 客户端,真打端点需要 CI 没有的 key、要花钱、且 会因与包无关的原因失败。测的是依赖边:`:url` 的端点常量、`:types` 的 variant 内容模型、`:errors` 的异常层次 —— 三者都是纯数据/纯逻辑。 两个成员都在本机跑通,并各自验证过把断言改错会变红。 * ci: 每片按耗时升序跑,让「核心已覆盖」成为一个可判断的中间状态 LPT 装箱按降序考虑成员,所以每片交给 run_members.sh 的顺序也是降序 —— 贵的先跑。 反过来。 理由不是「更快发现失败」,而是让维护者能在跑完之前就**做决定**。少数时候,一个 改动在核心已被证明覆盖之后就值得合入,不必等尾巴跑完。 一次全量 linux run 是 13427s 成员墙钟,前四名占 52%(grpc-codegen 3363s、 grpc-module 1724s、opencv-module-dnn 1017s、protobuf-protoc 945s)。按这个顺序, 约 55 个成员在第一个重量级启动前就已报完 —— 于是「除了那四个已知的贵成员之外 全绿」是一个**存在的、早早出现的、可以判断的状态**。贵的先跑则没有这种中间状态: 一小时内什么都说明不了,然后一次性全部结束。 超时的后果按同一逻辑读:分片现在丢的是贵成员而不是便宜成员 —— 那正是维护者本来 就会选择跳过的那一半,数量少,且在 tests/member-timings.tsv 里逐个有名有姓。 装箱与顺序是两个问题,这里只动后者:LPT 仍按降序装箱(否则箱子会不均)。三平台 七个分片逐一比对过成员集合 —— 完全一致。单片路径(`--shard 0/1`)同样升序,所以 本地与 CI 的顺序是同一个。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
这是 #352 的另一半
compat.glx-runtime一直把宿主的libGL/libEGL从/usr/lib*symlink 进来。mcpp#352 就是这个:宿主 Mesa 要GLIBC_2.43,mcpp 载荷 glibc 是 2.39,于是程序链接得干干净净、exit 255、零输出。它同时是 xlings hermetic 策略禁止穿越清单的第一条 ——/usr/lib*/lib*下的任何.so。2026.08.08改从xim:graphics取:22 包 + 两个哨兵(各自探测一个自己不拥有的宿主侧半边 —— NVIDIA 专有驱动、WSL2 的 D3D12,不在就「链接了零个东西然后成功返回」)。一条依赖覆盖五种宿主形态,本文件没有新增任何条件分支。三个非显然的决定
① 来源是 subos 视图,不是载荷目录。 载荷路径钉死版本 —— 消费者的 RUNPATH 会永远写着
mesa/25.0.7.1,升级当天就解析不到。视图是稳定间接层,就是/run/opengl-driver在 NixOS 扮演的角色。② 模式列表从「方便」升级成「安全边界」。 视图里同时有
libc.so.6、crt1.o。它们绝不能进消费者的 RUNPATH:消费者跑在 mcpp 的载荷 loader 下,再配一份别的 libc 会在 main 之前死在动态链接器里、零输出。挡住它们的正是这个模式列表,所以现在后面加了断言。这个失败是在做这件事的路上真撞出来的,不是想象的:
③
libnvidia*从模式列表移除。 专有驱动经xim:nvidia-gl-host-link以 glvnd vendor 名进入 subos,而那些名字上面已经匹配了。再按它自己的名字取一遍,就是同一组库的第二条路径 —— 驱动升级那天两条会不一致。验证:实测,不是推理
在有 NVIDIA 的机器上:
两个判据缺一不可 —— 「窗口出来了」是假绿,llvmpipe 也能出窗口;所以断言的是渲染器身份 + 库的出处。
no-libc 断言对真实视图核验过:链了 52 个库,C 运行时条目 0 个。
CN 镜像资产已上传并回探真实下载 URL 核验(6079 字节,sha256 与 GLOBAL 逐字节一致)——上传回执本身不作为判据。
兼容性
2026.06.03保留发布,已经钉它的消费者继续可解析(已发布的 manifest 改不了)compat.glfw是唯一真正依赖本包的包(另两个只在注释里提到),已改钉新版本MCPP_HOST_GL_LIBRARY_PATH仍可用,现在是回到宿主的唯一一道门,使用时告警 —— 它指向的库是对着宿主 glibc 构建的,在载荷 glibc 下加载正是 #352 的配置配套:mcpp-community/mcpp#376(
mcpp run/test把 subos 声明的环境传给程序)。两者一起才闭环:RUNPATH 由本 PR 给,发现类环境变量由那个 PR 给 —— subos 故意不设LD_LIBRARY_PATH,所以缺任一半都不行。Refs mcpp-community/mcpp#352