Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkgs/c/compat.glfw.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
179 changes: 78 additions & 101 deletions pkgs/c/compat.glx-runtime.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -87,7 +41,6 @@ package = {
}

import("xim.libxpkg.pkginfo")
import("xim.libxpkg.system")
import("xim.libxpkg.log")

local function sh_quote(value)
Expand All @@ -107,26 +60,6 @@ local function split_paths(value)
return out
end

-- Where to take the GL libraries from.
--
-- The SUBOS VIEW (`<subos>/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 = {}
Expand All @@ -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

Expand All @@ -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*",
Expand All @@ -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/<triplet> 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

Expand Down
Loading