From 346d303a9b82fa8104758780bb7662d624ee9a1f Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sat, 8 Aug 2026 07:10:22 +0800 Subject: [PATCH] =?UTF-8?q?Revert=20"feat(glx-runtime):=20GL=20=E6=9D=A5?= =?UTF-8?q?=E8=87=AA=E7=94=9F=E6=80=81=E8=80=8C=E9=9D=9E=20/usr/lib"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit main is red at f44e8968 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 ) 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. --- pkgs/c/compat.glfw.lua | 2 +- pkgs/c/compat.glx-runtime.lua | 179 +++++++++++++++------------------- 2 files changed, 79 insertions(+), 102 deletions(-) diff --git a/pkgs/c/compat.glfw.lua b/pkgs/c/compat.glfw.lua index a4454b0c..e4ef66f1 100644 --- a/pkgs/c/compat.glfw.lua +++ b/pkgs/c/compat.glfw.lua @@ -80,7 +80,7 @@ package = { "src/posix_module.c", }, deps = { - ["compat.glx-runtime"] = "2026.08.08", + ["compat.glx-runtime"] = "2026.06.03", ["compat.x11"] = "1.8.13", ["compat.xcursor"] = "1.2.3", ["compat.xext"] = "1.3.7", diff --git a/pkgs/c/compat.glx-runtime.lua b/pkgs/c/compat.glx-runtime.lua index 9db30d4e..8cc15d1a 100644 --- a/pkgs/c/compat.glx-runtime.lua +++ b/pkgs/c/compat.glx-runtime.lua @@ -2,59 +2,13 @@ package = { spec = "1", namespace = "compat", name = "glx-runtime", - description = "GLVND/GLX/OpenGL runtime for mcpp Linux window applications, from the xlings graphics stack", + description = "Host GLVND/GLX/OpenGL runtime adapter for mcpp Linux window applications", licenses = {"MIT"}, repo = "https://github.com/KhronosGroup/OpenGL-Registry", type = "package", - -- WHERE THE GL RUNTIME COMES FROM, AND WHY IT CHANGED - -- - -- Until 2026.08.08 this package symlinked the HOST's libGL/libEGL out of - -- /usr/lib*. That is the thing mcpp#352 is: the host's Mesa needs - -- GLIBC_2.43 and mcpp's payload glibc is 2.39, so the program linked - -- cleanly and exited 255 with no output. It is also the boundary the - -- xlings hermetic policy names first -- any .so under /usr/lib* or /lib*. - -- - -- The runtime now comes from `xim:graphics`, the ecosystem's own stack: - -- 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, every - -- host shape, no conditional in this file. - -- - -- Measured on an NVIDIA host after the change: libEGL resolves to - -- xim-x-libglvnd/1.7.0/lib/libEGL.so.1 and GL_RENDERER is the GPU, not - -- llvmpipe. Both halves of that matter -- "a window appeared" is a false - -- pass, because llvmpipe renders one too. xpm = { linux = { - -- The whole hermetic graphics stack. A RUNTIME dep, not a build - -- one: nothing here compiles against it, the produced consumer - -- loads it. - -- - -- PLATFORM level, beside the version entries rather than inside - -- one. Every other recipe in both indexes places it here, and the - -- first attempt at this change put it inside the 2026.08.08 entry: - -- the descriptor parsed, the stack was never installed, and the - -- install failed on the required-library check -- an error naming - -- libGL.so.1 rather than the misplaced key. Whether a per-version - -- `deps` is rejected or merely unread was not determined; what is - -- established is that it does not take effect. - -- - -- It therefore also applies to the legacy 2026.06.03 entry below, - -- which does not use it. That costs a consumer still pinned there - -- a download it will not read, and the alternative -- deleting the - -- published version -- would break them outright. - deps = { runtime = { "xim:graphics" } }, - ["2026.08.08"] = { - url = { - GLOBAL = "https://raw.githubusercontent.com/KhronosGroup/OpenGL-Registry/a30033d3e812c9bf10094f1010374a6b15e192eb/README.adoc", - CN = "https://gitcode.com/mcpp-res/glx-runtime/releases/download/2026.08.08/glx-runtime-2026.08.08.adoc", - }, - sha256 = "ea68efce197e68413ebb62c51ab4bccfb2309a2fca776d31b49d972f59f3640e", - }, - -- Kept so already-published consumers pinned to it keep resolving. - -- It sources libGL from the HOST and is the configuration behind - -- mcpp#352; new consumers must not pin it. ["2026.06.03"] = { url = { GLOBAL = "https://raw.githubusercontent.com/KhronosGroup/OpenGL-Registry/a30033d3e812c9bf10094f1010374a6b15e192eb/README.adoc", @@ -87,7 +41,6 @@ package = { } import("xim.libxpkg.pkginfo") -import("xim.libxpkg.system") import("xim.libxpkg.log") local function sh_quote(value) @@ -107,26 +60,6 @@ local function split_paths(value) return out end --- Where to take the GL libraries from. --- --- The SUBOS VIEW (`/lib`), not a payload directory. A payload path pins --- a version, so a consumer's recorded RUNPATH would name mesa 25.0.7.1 forever --- and stop resolving the day it is upgraded; the view is the stable --- indirection -- the role /run/opengl-driver plays on NixOS. xlings repoints --- it as the active version changes and this package needs no new release. --- --- The view also carries libc.so.6, crt1.o 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 pairing one loader with another glibc's libc.so.6 faults --- inside the dynamic linker before main, with empty output. What keeps them --- out is the pattern list below -- so that list is a safety boundary, not a --- convenience, and nothing resembling `libc*` may ever be added to it. --- --- MCPP_HOST_GL_LIBRARY_PATH still works and is now the ONLY door back to the --- host. Using it leaves the hermetic guarantee: the libraries it names were --- built against the host's glibc, and loading them under mcpp's payload glibc --- is exactly the configuration mcpp#352 reports. It exists for a machine whose --- GPU vendor the ecosystem does not cover yet. local function candidate_dirs() local out = {} local seen = {} @@ -138,12 +71,13 @@ local function candidate_dirs() end for _, dir in ipairs(split_paths(os.getenv("MCPP_HOST_GL_LIBRARY_PATH"))) do - log.warn("MCPP_HOST_GL_LIBRARY_PATH names %s: GL will come from the " - .. "host, which is the configuration behind mcpp#352", dir) add(dir) end - - add(path.join(system.subos_sysrootdir(), "lib")) + add("/lib/x86_64-linux-gnu") + add("/usr/lib/x86_64-linux-gnu") + add("/lib64") + add("/usr/lib64") + add("/usr/lib") return out end @@ -156,11 +90,7 @@ local host_gl_patterns = { "libEGL.so*", "libEGL_*.so*", "libGLES*.so*", - -- No libnvidia* here. The proprietary driver reaches the subos through - -- xim:nvidia-gl-host-link, which links it under the glvnd vendor names - -- already matched above; taking it by its own name would be a second - -- route to the same libraries, and the two would disagree the day the - -- driver is upgraded under us. + "libnvidia*.so*", "libglapi.so*", "libdrm*.so*", "libexpat.so*", @@ -174,43 +104,90 @@ local required = { ["libGL.so.1"] = false, } +-- Is FILE a 64-bit ELF? e_ident[EI_CLASS] == ELFCLASS64. +-- +-- Five bytes read directly. `file`/`readelf`/`patchelf` would each answer this +-- and each may be absent when a hook runs, and a probe that answers "cannot +-- tell" by assuming "fine" is the bug below. +local function is_elf64(file) + local f = io.open(file, "rb") + if not f then return false end + local head = f:read(5) + f:close() + return head ~= nil and #head == 5 + and head:sub(1, 4) == "\127ELF" and head:byte(5) == 2 +end + +-- Link the host's GL runtime into one directory, FIRST HIT WINS, 64-bit only. +-- +-- openxlings/xlings' mcpp#352: on Fedora 44 this produced +-- libGLX.so.0 -> /usr/lib/libGLX.so.0 +-- a 32-bit library, and the application died with +-- libGLX.so.0: wrong ELF class: ELFCLASS32 +-- +-- TWO BUGS, and the obvious diagnosis ("the candidate order assumes Debian") is +-- not either of them -- `/usr/lib64` is already ahead of `/usr/lib` in the list: +-- +-- 1. `ln -sf` OVERWRITES. The loop reached /usr/lib64 first and linked the +-- correct file, then reached /usr/lib and replaced it. Last-wins, not +-- first-wins. `libOpenGL.so.0` survived as 64-bit purely because that host's +-- 32-bit glvnd does not ship it -- which is why exactly one link in the bug +-- report was right. +-- 2. NO ABI CHECK ANYWHERE, including in `required` below, which asserted that +-- libGLX.so.0 and libGL.so.1 EXIST. Both existed. Both were 32-bit. +-- +-- There is no directory layout to assume: the FHS biarch clause makes /usr/lib +-- 32-bit (Fedora/RHEL/SUSE), Debian explicitly declined that clause and uses +-- /usr/lib/ so its /usr/lib is 64-bit, and Arch is a third answer +-- again. So the fix cannot be a better ordering -- it has to be an ABI check, +-- which makes the order stop mattering. local function link_runtime_libs(outdir) os.mkdir(outdir) + local claimed = {} for _, dir in ipairs(candidate_dirs()) do for _, pattern in ipairs(host_gl_patterns) do - os.exec( - "for lib in " .. sh_quote(dir) .. "/" .. pattern .. - "; do [ -e \"$lib\" ] || continue; " .. - "ln -sf \"$lib\" " .. sh_quote(outdir) .. "/\"$(basename \"$lib\")\"; " .. - "done" - ) + -- Enumerate, then decide per file, instead of letting the shell + -- link them: the decision needs the ELF class and "have I already + -- taken this name", neither of which a `ln -sf` loop can express. + local pipe = io.popen("ls -1 " .. sh_quote(dir) .. "/" .. pattern + .. " 2>/dev/null") + if pipe then + for line in pipe:lines() do + local lib = line:gsub("[\r\n]+$", "") + local name = lib:match("[^/]+$") + if lib ~= "" and name and not claimed[name] + and is_elf64(lib) then + claimed[name] = lib + os.exec("ln -sf " .. sh_quote(lib) .. " " + .. sh_quote(path.join(outdir, name))) + end + end + pipe:close() + end end end for name, _ in pairs(required) do - if not os.isfile(path.join(outdir, name)) then - log.error("%s is not in this subos. The GL runtime comes from " - .. "`xim:graphics`; if it is declared and this still " - .. "fires, the stack did not finish installing", name) + local link = path.join(outdir, name) + -- Existence AND ABI. Existence alone passed on the Fedora host with + -- both links 32-bit, which is how a broken package reported success and + -- the failure surfaced as a silent exit code 255 from the application. + if not os.isfile(link) then + log.error("required host GL runtime library not found: %s", name) + log.error(" searched: %s", table.concat(candidate_dirs(), " ")) + log.error(" install your distro's GL runtime (mesa / libglvnd)") return false end - end - - -- Nothing resembling a C runtime may have come along. Asserted rather - -- than trusted: the pattern list is what keeps it out, and a pattern is - -- one careless edit away from matching more than it meant to. The failure - -- it prevents has no diagnostic of its own -- the consumer dies inside - -- the dynamic linker before main, printing nothing. - for _, bad in ipairs({"libc.so.6", "libc.so", "ld-linux-x86-64.so.2", - "libpthread.so.0", "libdl.so.2", "libm.so.6"}) do - if os.isfile(path.join(outdir, bad)) then - log.error("%s was linked into the GL runtime directory. It would " - .. "land on every consumer's RUNPATH and pair a second " - .. "libc with mcpp's loader, which faults before main " - .. "with no output at all", bad) + if not is_elf64(link) then + log.error("host %s is not 64-bit (%s)", name, claimed[name] or link) + log.error(" a 32-bit library here fails at dlopen with") + log.error(" `wrong ELF class: ELFCLASS32` and the application") + log.error(" exits without output. Install the 64-bit GL runtime.") return false end end + log.info("glx-runtime: linked %d host GL libraries (64-bit)", + (function() local n = 0 for _ in pairs(claimed) do n = n + 1 end return n end)()) return true end