Skip to content

Commit 1ba4161

Browse files
authored
feat(glx-runtime): GL 来自生态而非 /usr/lib,这次带上缺失的那个测试 (#181)
* 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 的顺序是同一个。
1 parent e9a25ab commit 1ba4161

10 files changed

Lines changed: 604 additions & 86 deletions

File tree

.github/workflows/validate.yml

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,19 @@ env:
127127
# index.toml 的 min_mcpp **不动**。两个会撞的 compat 邻居都是 Form B,所以旧客户端
128128
# 撞上时是响亮报错而不是静默用错包;下限是一道让整个索引对旧客户端失效的闸门
129129
# (mcpp#349),只该在描述符真的读不动时抬。这里读得动,差的是解析得对。
130-
MCPP_VERSION: "2026.8.6.2"
130+
#
131+
# 2026.8.8.2 是本 PR 的**前置**,不是顺手升级。
132+
#
133+
# `compat.glx-runtime` 依赖 mesa,mesa 声明 `xim:glibc@>=2.38`;下限被任何更高
134+
# 版本满足,于是安装图形栈会在既有 glibc 旁边**再装一个**。2026.8.8.2 之前的
135+
# mcpp 用 `readdir` 的第一项来解析「那个 glibc payload」,编译侧与产物的
136+
# interpreter 因此可以指向不同版本 —— 这正是 #179 落地后 `asio-module` 和
137+
# `core` 变红、并导致整份改动被 #180 revert 的原因。
138+
#
139+
# 用早于 2026.8.8.2 的 mcpp 重新落地这份改动,就是在仍会犯这个错的引擎上复现
140+
# 事故条件。tests/check_graphics_install_side_effects.sh 也需要它 —— 该测试在
141+
# 更旧的 mcpp 上失败是**有意的**。
142+
MCPP_VERSION: "2026.8.8.2"
131143

132144
jobs:
133145
lint:
@@ -508,6 +520,48 @@ jobs:
508520
echo "plan=$(cat /tmp/plan.json)" >> "$GITHUB_OUTPUT"
509521
jq . /tmp/plan.json
510522
523+
# Installing the graphics stack must not change what UNRELATED members link
524+
# against. This is the check #179 did not have: every test in this repo asks
525+
# about its own package, so nothing asked whether installing one changes
526+
# things for everyone else -- and `asio-module` / `core` went red on a
527+
# change that never touched them.
528+
#
529+
# Its own job, on Linux, because it needs a real toolchain and a home it can
530+
# watch payloads appear in. It reports INCONCLUSIVE (and fails) rather than
531+
# green whenever its preconditions do not hold; see the script.
532+
graphics-side-effects:
533+
name: "graphics install: no side effects on unrelated members"
534+
runs-on: ubuntu-latest
535+
timeout-minutes: 60
536+
steps:
537+
- uses: actions/checkout@v4
538+
- name: Download mcpp
539+
shell: bash
540+
env:
541+
MCPP_ARCHIVE: mcpp-${{ env.MCPP_VERSION }}-linux-x86_64.tar.gz
542+
MCPP_ROOT: mcpp-${{ env.MCPP_VERSION }}-linux-x86_64
543+
run: |
544+
curl -L -fsS -o "$MCPP_ARCHIVE" \
545+
"https://github.com/mcpp-community/mcpp/releases/download/v${MCPP_VERSION}/${MCPP_ARCHIVE}"
546+
tar -xzf "$MCPP_ARCHIVE"
547+
root="$PWD/$MCPP_ROOT"
548+
# MCPP_HOME is the tarball root, not ~/.mcpp.
549+
#
550+
# A released mcpp is self-contained: it resolves its registry from
551+
# beside its own executable. Copying the payload tree into ~/.mcpp
552+
# and pointing MCPP_HOME there therefore watches one home while the
553+
# build uses another -- which this check detects and refuses to call
554+
# green (it did, on the first run). Aligning them is the fix; the
555+
# check was right.
556+
echo "MCPP=$root/bin/mcpp" >> "$GITHUB_ENV"
557+
echo "MCPP_HOME=$root" >> "$GITHUB_ENV"
558+
echo "MCPP_VENDORED_XLINGS=$root/registry/bin/xlings" >> "$GITHUB_ENV"
559+
- name: Side-effect check
560+
shell: bash
561+
run: |
562+
"$MCPP" self config --mirror GLOBAL 2>/dev/null || true
563+
bash tests/check_graphics_install_side_effects.sh
564+
511565
workspace:
512566
# The shard suffix appears only when the platform is actually split.
513567
name: workspace (${{ matrix.platform }}${{ matrix.shards == 1 && '' || format(' {0}/{1}', matrix.shard, matrix.shards) }})
@@ -562,6 +616,32 @@ jobs:
562616
key: ${{ env.REGISTRY_CACHE_KEY }}
563617
restore-keys: |
564618
mcpp-registry-${{ runner.os }}-${{ env.MCPP_VERSION }}-
619+
# Host tools built from source (protoc, grpc_cpp_plugin, …).
620+
#
621+
# Measured on the run that added grpc-codegen: 636s to build protoc and
622+
# 660s to build grpc_cpp_plugin — 1296s of a 3363s member, repeated in
623+
# every job of every run, because the registry cache above covers
624+
# ~/.mcpp/registry and the tool store lives in ~/.mcpp/build-cache.
625+
# `protobuf-protoc` and `grpc-module` build the SAME protoc, so a full
626+
# run pays for it several times over.
627+
#
628+
# A coarse rolling key is safe here, which is why this is cheap. The
629+
# store is content-addressed by `<pkg>@<version>/<hash>` and mcpp
630+
# validates an entry FIELD BY FIELD against the recorded entry.json
631+
# (epoch, target, host triple, compiler identity, profile, features,
632+
# transitive dependency closure). A stale entry is not used; it is
633+
# rebuilt. The worst case of a cache miss-match is the status quo.
634+
#
635+
# Deliberately NOT build-cache/v1/pkg: 6.0 GB measured locally against
636+
# Actions' 10 GB per-repo budget, which would evict the registry cache
637+
# this job needs more. The tool store is 116 MB.
638+
- name: Cache host tool store
639+
uses: actions/cache@v4
640+
with:
641+
path: ~/.mcpp/build-cache/v1/tool
642+
key: mcpp-toolstore-${{ runner.os }}-${{ env.MCPP_VERSION }}-${{ github.run_id }}-${{ matrix.platform }}-${{ matrix.shard }}
643+
restore-keys: |
644+
mcpp-toolstore-${{ runner.os }}-${{ env.MCPP_VERSION }}-
565645
- name: Download mcpp
566646
shell: bash
567647
env:
@@ -577,6 +657,21 @@ jobs:
577657
root="$PWD/$MCPP_ROOT"
578658
mkdir -p "$HOME/.mcpp/registry"
579659
cp -a "$root/registry/." "$HOME/.mcpp/registry/"
660+
# Point mcpp AT the home the caches above cover.
661+
#
662+
# Without this the copy above is one-way scenery: a released mcpp is
663+
# self-contained and resolves its registry from beside its own
664+
# executable, so every build used <tarball>/registry while the cache
665+
# restored, and saved, ~/.mcpp/registry. Proof from the run that
666+
# added grpc-codegen: abseil compiled from
667+
# `<tarball>/registry/data/xpkgs/compat-x-abseil/...`, and
668+
# xim:glibc@2.44 / xim:python@3.13.12 were downloaded again on a job
669+
# that reported a registry cache HIT.
670+
#
671+
# With MCPP_HOME set, the restored registry is the one in play — so
672+
# payloads stop being re-downloaded per job, and the tool store cache
673+
# below lands where the build looks for it.
674+
echo "MCPP_HOME=$HOME/.mcpp" >> "$GITHUB_ENV"
580675
if [[ "$RUNNER_OS" == "Windows" ]]; then
581676
echo "MCPP=$(cygpath -m "$root/${{ matrix.mcpp }}")" >> "$GITHUB_ENV"
582677
echo "MCPP_VENDORED_XLINGS=$(cygpath -m "$root/${{ matrix.xlings }}")" >> "$GITHUB_ENV"

mcpp.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ members = [
1919
"tests/examples/catch2-v2",
2020
"tests/examples/catch2-v2-main",
2121
"tests/examples/cjson",
22+
"tests/examples/cmdline",
2223
"tests/examples/cli11",
2324
"tests/examples/core",
2425
"tests/examples/curl",
@@ -61,6 +62,7 @@ members = [
6162
"tests/examples/libpng",
6263
"tests/examples/llamacpp",
6364
"tests/examples/llamacpp-metal",
65+
"tests/examples/llmapi",
6466
"tests/examples/md4c",
6567
"tests/examples/spdlog-compiled",
6668
"tests/examples/tinyhttps",

pkgs/c/compat.glfw.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ package = {
8080
"src/posix_module.c",
8181
},
8282
deps = {
83-
["compat.glx-runtime"] = "2026.06.03",
83+
["compat.glx-runtime"] = "2026.08.08",
8484
["compat.x11"] = "1.8.13",
8585
["compat.xcursor"] = "1.2.3",
8686
["compat.xext"] = "1.3.7",

pkgs/c/compat.glx-runtime.lua

Lines changed: 101 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,59 @@ package = {
22
spec = "1",
33
namespace = "compat",
44
name = "glx-runtime",
5-
description = "Host GLVND/GLX/OpenGL runtime adapter for mcpp Linux window applications",
5+
description = "GLVND/GLX/OpenGL runtime for mcpp Linux window applications, from the xlings graphics stack",
66
licenses = {"MIT"},
77
repo = "https://github.com/KhronosGroup/OpenGL-Registry",
88
type = "package",
99

10+
-- WHERE THE GL RUNTIME COMES FROM, AND WHY IT CHANGED
11+
--
12+
-- Until 2026.08.08 this package symlinked the HOST's libGL/libEGL out of
13+
-- /usr/lib*. That is the thing mcpp#352 is: the host's Mesa needs
14+
-- GLIBC_2.43 and mcpp's payload glibc is 2.39, so the program linked
15+
-- cleanly and exited 255 with no output. It is also the boundary the
16+
-- xlings hermetic policy names first -- any .so under /usr/lib* or /lib*.
17+
--
18+
-- The runtime now comes from `xim:graphics`, the ecosystem's own stack:
19+
-- 22 packages plus two sentinels that probe for a host-side userspace half
20+
-- they do not own (the proprietary NVIDIA driver, WSL2's D3D12) and
21+
-- succeed having linked nothing when it is absent. One dependency, every
22+
-- host shape, no conditional in this file.
23+
--
24+
-- Measured on an NVIDIA host after the change: libEGL resolves to
25+
-- xim-x-libglvnd/1.7.0/lib/libEGL.so.1 and GL_RENDERER is the GPU, not
26+
-- llvmpipe. Both halves of that matter -- "a window appeared" is a false
27+
-- pass, because llvmpipe renders one too.
1028
xpm = {
1129
linux = {
30+
-- The whole hermetic graphics stack. A RUNTIME dep, not a build
31+
-- one: nothing here compiles against it, the produced consumer
32+
-- loads it.
33+
--
34+
-- PLATFORM level, beside the version entries rather than inside
35+
-- one. Every other recipe in both indexes places it here, and the
36+
-- first attempt at this change put it inside the 2026.08.08 entry:
37+
-- the descriptor parsed, the stack was never installed, and the
38+
-- install failed on the required-library check -- an error naming
39+
-- libGL.so.1 rather than the misplaced key. Whether a per-version
40+
-- `deps` is rejected or merely unread was not determined; what is
41+
-- established is that it does not take effect.
42+
--
43+
-- It therefore also applies to the legacy 2026.06.03 entry below,
44+
-- which does not use it. That costs a consumer still pinned there
45+
-- a download it will not read, and the alternative -- deleting the
46+
-- published version -- would break them outright.
47+
deps = { runtime = { "xim:graphics" } },
48+
["2026.08.08"] = {
49+
url = {
50+
GLOBAL = "https://raw.githubusercontent.com/KhronosGroup/OpenGL-Registry/a30033d3e812c9bf10094f1010374a6b15e192eb/README.adoc",
51+
CN = "https://gitcode.com/mcpp-res/glx-runtime/releases/download/2026.08.08/glx-runtime-2026.08.08.adoc",
52+
},
53+
sha256 = "ea68efce197e68413ebb62c51ab4bccfb2309a2fca776d31b49d972f59f3640e",
54+
},
55+
-- Kept so already-published consumers pinned to it keep resolving.
56+
-- It sources libGL from the HOST and is the configuration behind
57+
-- mcpp#352; new consumers must not pin it.
1258
["2026.06.03"] = {
1359
url = {
1460
GLOBAL = "https://raw.githubusercontent.com/KhronosGroup/OpenGL-Registry/a30033d3e812c9bf10094f1010374a6b15e192eb/README.adoc",
@@ -41,6 +87,7 @@ package = {
4187
}
4288

4389
import("xim.libxpkg.pkginfo")
90+
import("xim.libxpkg.system")
4491
import("xim.libxpkg.log")
4592

4693
local function sh_quote(value)
@@ -60,6 +107,26 @@ local function split_paths(value)
60107
return out
61108
end
62109

110+
-- Where to take the GL libraries from.
111+
--
112+
-- The SUBOS VIEW (`<subos>/lib`), not a payload directory. A payload path pins
113+
-- a version, so a consumer's recorded RUNPATH would name mesa 25.0.7.1 forever
114+
-- and stop resolving the day it is upgraded; the view is the stable
115+
-- indirection -- the role /run/opengl-driver plays on NixOS. xlings repoints
116+
-- it as the active version changes and this package needs no new release.
117+
--
118+
-- The view also carries libc.so.6, crt1.o and the rest of the C runtime, and
119+
-- those must NEVER reach a consumer's RUNPATH: the consumer runs under mcpp's
120+
-- payload loader, and pairing one loader with another glibc's libc.so.6 faults
121+
-- inside the dynamic linker before main, with empty output. What keeps them
122+
-- out is the pattern list below -- so that list is a safety boundary, not a
123+
-- convenience, and nothing resembling `libc*` may ever be added to it.
124+
--
125+
-- MCPP_HOST_GL_LIBRARY_PATH still works and is now the ONLY door back to the
126+
-- host. Using it leaves the hermetic guarantee: the libraries it names were
127+
-- built against the host's glibc, and loading them under mcpp's payload glibc
128+
-- is exactly the configuration mcpp#352 reports. It exists for a machine whose
129+
-- GPU vendor the ecosystem does not cover yet.
63130
local function candidate_dirs()
64131
local out = {}
65132
local seen = {}
@@ -71,13 +138,12 @@ local function candidate_dirs()
71138
end
72139

73140
for _, dir in ipairs(split_paths(os.getenv("MCPP_HOST_GL_LIBRARY_PATH"))) do
141+
log.warn("MCPP_HOST_GL_LIBRARY_PATH names %s: GL will come from the "
142+
.. "host, which is the configuration behind mcpp#352", dir)
74143
add(dir)
75144
end
76-
add("/lib/x86_64-linux-gnu")
77-
add("/usr/lib/x86_64-linux-gnu")
78-
add("/lib64")
79-
add("/usr/lib64")
80-
add("/usr/lib")
145+
146+
add(path.join(system.subos_sysrootdir(), "lib"))
81147
return out
82148
end
83149

@@ -90,7 +156,11 @@ local host_gl_patterns = {
90156
"libEGL.so*",
91157
"libEGL_*.so*",
92158
"libGLES*.so*",
93-
"libnvidia*.so*",
159+
-- No libnvidia* here. The proprietary driver reaches the subos through
160+
-- xim:nvidia-gl-host-link, which links it under the glvnd vendor names
161+
-- already matched above; taking it by its own name would be a second
162+
-- route to the same libraries, and the two would disagree the day the
163+
-- driver is upgraded under us.
94164
"libglapi.so*",
95165
"libdrm*.so*",
96166
"libexpat.so*",
@@ -104,90 +174,43 @@ local required = {
104174
["libGL.so.1"] = false,
105175
}
106176

107-
-- Is FILE a 64-bit ELF? e_ident[EI_CLASS] == ELFCLASS64.
108-
--
109-
-- Five bytes read directly. `file`/`readelf`/`patchelf` would each answer this
110-
-- and each may be absent when a hook runs, and a probe that answers "cannot
111-
-- tell" by assuming "fine" is the bug below.
112-
local function is_elf64(file)
113-
local f = io.open(file, "rb")
114-
if not f then return false end
115-
local head = f:read(5)
116-
f:close()
117-
return head ~= nil and #head == 5
118-
and head:sub(1, 4) == "\127ELF" and head:byte(5) == 2
119-
end
120-
121-
-- Link the host's GL runtime into one directory, FIRST HIT WINS, 64-bit only.
122-
--
123-
-- openxlings/xlings' mcpp#352: on Fedora 44 this produced
124-
-- libGLX.so.0 -> /usr/lib/libGLX.so.0
125-
-- a 32-bit library, and the application died with
126-
-- libGLX.so.0: wrong ELF class: ELFCLASS32
127-
--
128-
-- TWO BUGS, and the obvious diagnosis ("the candidate order assumes Debian") is
129-
-- not either of them -- `/usr/lib64` is already ahead of `/usr/lib` in the list:
130-
--
131-
-- 1. `ln -sf` OVERWRITES. The loop reached /usr/lib64 first and linked the
132-
-- correct file, then reached /usr/lib and replaced it. Last-wins, not
133-
-- first-wins. `libOpenGL.so.0` survived as 64-bit purely because that host's
134-
-- 32-bit glvnd does not ship it -- which is why exactly one link in the bug
135-
-- report was right.
136-
-- 2. NO ABI CHECK ANYWHERE, including in `required` below, which asserted that
137-
-- libGLX.so.0 and libGL.so.1 EXIST. Both existed. Both were 32-bit.
138-
--
139-
-- There is no directory layout to assume: the FHS biarch clause makes /usr/lib
140-
-- 32-bit (Fedora/RHEL/SUSE), Debian explicitly declined that clause and uses
141-
-- /usr/lib/<triplet> so its /usr/lib is 64-bit, and Arch is a third answer
142-
-- again. So the fix cannot be a better ordering -- it has to be an ABI check,
143-
-- which makes the order stop mattering.
144177
local function link_runtime_libs(outdir)
145178
os.mkdir(outdir)
146-
local claimed = {}
147179
for _, dir in ipairs(candidate_dirs()) do
148180
for _, pattern in ipairs(host_gl_patterns) do
149-
-- Enumerate, then decide per file, instead of letting the shell
150-
-- link them: the decision needs the ELF class and "have I already
151-
-- taken this name", neither of which a `ln -sf` loop can express.
152-
local pipe = io.popen("ls -1 " .. sh_quote(dir) .. "/" .. pattern
153-
.. " 2>/dev/null")
154-
if pipe then
155-
for line in pipe:lines() do
156-
local lib = line:gsub("[\r\n]+$", "")
157-
local name = lib:match("[^/]+$")
158-
if lib ~= "" and name and not claimed[name]
159-
and is_elf64(lib) then
160-
claimed[name] = lib
161-
os.exec("ln -sf " .. sh_quote(lib) .. " "
162-
.. sh_quote(path.join(outdir, name)))
163-
end
164-
end
165-
pipe:close()
166-
end
181+
os.exec(
182+
"for lib in " .. sh_quote(dir) .. "/" .. pattern ..
183+
"; do [ -e \"$lib\" ] || continue; " ..
184+
"ln -sf \"$lib\" " .. sh_quote(outdir) .. "/\"$(basename \"$lib\")\"; " ..
185+
"done"
186+
)
167187
end
168188
end
169189

170190
for name, _ in pairs(required) do
171-
local link = path.join(outdir, name)
172-
-- Existence AND ABI. Existence alone passed on the Fedora host with
173-
-- both links 32-bit, which is how a broken package reported success and
174-
-- the failure surfaced as a silent exit code 255 from the application.
175-
if not os.isfile(link) then
176-
log.error("required host GL runtime library not found: %s", name)
177-
log.error(" searched: %s", table.concat(candidate_dirs(), " "))
178-
log.error(" install your distro's GL runtime (mesa / libglvnd)")
191+
if not os.isfile(path.join(outdir, name)) then
192+
log.error("%s is not in this subos. The GL runtime comes from "
193+
.. "`xim:graphics`; if it is declared and this still "
194+
.. "fires, the stack did not finish installing", name)
179195
return false
180196
end
181-
if not is_elf64(link) then
182-
log.error("host %s is not 64-bit (%s)", name, claimed[name] or link)
183-
log.error(" a 32-bit library here fails at dlopen with")
184-
log.error(" `wrong ELF class: ELFCLASS32` and the application")
185-
log.error(" exits without output. Install the 64-bit GL runtime.")
197+
end
198+
199+
-- Nothing resembling a C runtime may have come along. Asserted rather
200+
-- than trusted: the pattern list is what keeps it out, and a pattern is
201+
-- one careless edit away from matching more than it meant to. The failure
202+
-- it prevents has no diagnostic of its own -- the consumer dies inside
203+
-- the dynamic linker before main, printing nothing.
204+
for _, bad in ipairs({"libc.so.6", "libc.so", "ld-linux-x86-64.so.2",
205+
"libpthread.so.0", "libdl.so.2", "libm.so.6"}) do
206+
if os.isfile(path.join(outdir, bad)) then
207+
log.error("%s was linked into the GL runtime directory. It would "
208+
.. "land on every consumer's RUNPATH and pair a second "
209+
.. "libc with mcpp's loader, which faults before main "
210+
.. "with no output at all", bad)
186211
return false
187212
end
188213
end
189-
log.info("glx-runtime: linked %d host GL libraries (64-bit)",
190-
(function() local n = 0 for _ in pairs(claimed) do n = n + 1 end return n end)())
191214
return true
192215
end
193216

0 commit comments

Comments
 (0)