@@ -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
4389import (" xim.libxpkg.pkginfo" )
90+ import (" xim.libxpkg.system" )
4491import (" xim.libxpkg.log" )
4592
4693local function sh_quote (value )
@@ -60,6 +107,26 @@ local function split_paths(value)
60107 return out
61108end
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.
63130local 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
82148end
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 ) == " \127 ELF" 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.
144177local 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
192215end
193216
0 commit comments