Skip to content

Commit f14f2bf

Browse files
committed
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 上都会失败,这是有意的 —— 图形栈不该落在一个 仍会犯这个错的工具链上。
1 parent 297d0a9 commit f14f2bf

3 files changed

Lines changed: 328 additions & 79 deletions

File tree

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)