Skip to content

Commit 346d303

Browse files
committed
Revert "feat(glx-runtime): GL 来自生态而非 /usr/lib"
main is red at f44e896 and the previous run was green, so this is mine. The failures are not graphics ones -- asio-module and core fail too, all with the same shape: .../xim-x-glibc/2.39/lib64/libc.so.6: version `GLIBC_2.42' not found (required by <the test binary>) mesa declares `xim:glibc@>=2.38`, a floor rather than a pin, so pulling `xim:graphics` in as a dependency installed glibc 2.44 alongside 2.39. mcpp's payload probe takes the HIGHEST version it finds, so every build in that home started compiling and linking against 2.44 -- while the gcc payload's baked-in interpreter and specs still name 2.39. The result is binaries that reference GLIBC_2.42/2.43 symbols and a runtime that cannot provide them, in packages that have nothing to do with graphics. So the dependency did not just add a graphics stack: it silently moved the glibc every build in the home compiles against, and only the compile half moved. That is a real defect in how mcpp picks a payload version, not something to paper over in this recipe -- and it has to be fixed on the mcpp side before this change can land again. The GL work itself was verified (payload libEGL, GPU renderer, no C runtime on the consumer's RUNPATH) and is not in question. What was missing was any check of what installing the stack does to the REST of a home.
1 parent f44e896 commit 346d303

2 files changed

Lines changed: 79 additions & 102 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.08.08",
83+
["compat.glx-runtime"] = "2026.06.03",
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: 78 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -2,59 +2,13 @@ package = {
22
spec = "1",
33
namespace = "compat",
44
name = "glx-runtime",
5-
description = "GLVND/GLX/OpenGL runtime for mcpp Linux window applications, from the xlings graphics stack",
5+
description = "Host GLVND/GLX/OpenGL runtime adapter for mcpp Linux window applications",
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.
2810
xpm = {
2911
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.
5812
["2026.06.03"] = {
5913
url = {
6014
GLOBAL = "https://raw.githubusercontent.com/KhronosGroup/OpenGL-Registry/a30033d3e812c9bf10094f1010374a6b15e192eb/README.adoc",
@@ -87,7 +41,6 @@ package = {
8741
}
8842

8943
import("xim.libxpkg.pkginfo")
90-
import("xim.libxpkg.system")
9144
import("xim.libxpkg.log")
9245

9346
local function sh_quote(value)
@@ -107,26 +60,6 @@ local function split_paths(value)
10760
return out
10861
end
10962

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.
13063
local function candidate_dirs()
13164
local out = {}
13265
local seen = {}
@@ -138,12 +71,13 @@ local function candidate_dirs()
13871
end
13972

14073
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)
14374
add(dir)
14475
end
145-
146-
add(path.join(system.subos_sysrootdir(), "lib"))
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")
14781
return out
14882
end
14983

@@ -156,11 +90,7 @@ local host_gl_patterns = {
15690
"libEGL.so*",
15791
"libEGL_*.so*",
15892
"libGLES*.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.
93+
"libnvidia*.so*",
16494
"libglapi.so*",
16595
"libdrm*.so*",
16696
"libexpat.so*",
@@ -174,43 +104,90 @@ local required = {
174104
["libGL.so.1"] = false,
175105
}
176106

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.
177144
local function link_runtime_libs(outdir)
178145
os.mkdir(outdir)
146+
local claimed = {}
179147
for _, dir in ipairs(candidate_dirs()) do
180148
for _, pattern in ipairs(host_gl_patterns) do
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-
)
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
187167
end
188168
end
189169

190170
for name, _ in pairs(required) do
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)
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)")
195179
return false
196180
end
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)
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.")
211186
return false
212187
end
213188
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)())
214191
return true
215192
end
216193

0 commit comments

Comments
 (0)