Skip to content
Closed
Changes from 3 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
937df82
feat(eui-neo): enable the linux SNI tray backend (EUI_TRAY_SNI)
FarnaHerry Aug 23, 2026
598562b
fix(eui-neo): make glib staging self-sufficient — store fallback, cop…
FarnaHerry Aug 23, 2026
3851dd5
fix(eui-neo): explicit -L for the staged glib + pcre2 version coherence
FarnaHerry Aug 23, 2026
c155ce8
fix(eui-neo): remove host GLib closure reconstruction
FarnaHerry Aug 24, 2026
da0de4e
fix(eui-neo): resolve review merge cleanup
FarnaHerry Aug 24, 2026
99f939b
fix(eui-neo): remove stale merge conflict markers
FarnaHerry Aug 24, 2026
1bdd986
Merge remote-tracking branch 'upstream/main' into feat/eui-neo-linux-…
FarnaHerry Aug 26, 2026
cdacb42
fix(eui-neo): resolve generated platform source path
FarnaHerry Aug 26, 2026
a846ae8
fix(eui-neo): bridge platform header for generated TU
FarnaHerry Aug 26, 2026
44bd72c
fix(eui-neo): expose archive root for platform includes
FarnaHerry Aug 26, 2026
ab9e382
fix(eui-neo): forward platform header from include root
FarnaHerry Aug 26, 2026
cb9f4c5
fix(eui-neo): expose GLib as Linux build dependency
FarnaHerry Aug 26, 2026
c0c1625
ci: retry validation after runner startup failure
FarnaHerry Aug 27, 2026
f167f78
fix(eui-neo): forward window backend header
FarnaHerry Aug 27, 2026
41ed7b6
fix(eui-neo): restore native platform include layout
FarnaHerry Aug 27, 2026
63f9bef
fix(eui-neo): expose archive root for platform includes
FarnaHerry Aug 27, 2026
28b6afa
fix(eui-neo): restore native platform source include
FarnaHerry Aug 27, 2026
1302c6c
fix(eui-neo): bridge nested platform includes
FarnaHerry Aug 27, 2026
ec30a51
fix(eui-neo): bridge tray platform include
FarnaHerry Aug 27, 2026
d646f54
fix(eui-neo): expose archive root includes
FarnaHerry Aug 27, 2026
7ad565c
fix(eui-neo): revert to main source layout, drop generated wrappers
FarnaHerry Aug 27, 2026
45797ce
fix(eui-neo): restore main include dirs, keep native platform source
FarnaHerry Aug 27, 2026
87dc592
fix(eui-neo): expose archive core dir to consumer includes
FarnaHerry Aug 27, 2026
b81d461
fix(eui-neo): revert to main descriptor, keep pure unpack hook
FarnaHerry Aug 27, 2026
1021627
fix(eui-neo): restore upstream package layout
FarnaHerry Aug 27, 2026
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
337 changes: 321 additions & 16 deletions pkgs/e/compat.eui-neo.lua
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@
-- descriptor names is byte-identical in upstream's CORE_SOURCES.
--
-- 0.5.7: CORE_SOURCES is byte-identical to 0.5.6, so the lib's shape does not
-- change; the version's real moves are the linux tray default (SNI over GDBus
-- change; the version's real moves are the linux tray backend (SNI over GDBus
-- via glib/gio, replacing the dead GTK3+libappindicator path — tray_bridge.c
-- now speaks freedesktop SNI when EUI_TRAY_SNI is set, which `compat.tray`
-- never feeds) and SDL2-only input fixes (SDL_GetMouseFocus, pointer/button
-- mapping) plus X11 resource handling in the SDL2 window backend. None of that
-- touches the sources this descriptor compiles: `compat.tray` does not provide
-- GDBus, so the linux leg still builds the EUI_TRAY_HAS_BACKEND=0 stub exactly
-- as before, and the x11_*.cpp / tray-gio pieces are not in CORE_SOURCES.
-- The new `EUI_ENABLE_TRAY` option does not affect this package either.
-- speaks freedesktop StatusNotifierItem when EUI_TRAY_SNI is set, which this
-- descriptor now does on linux, wiring glib through `xim:glib` and the
-- staging install() hook below) and SDL2-only input fixes (SDL_GetMouseFocus,
-- pointer/button mapping) plus X11 resource handling in the SDL2 window
-- backend. The new `EUI_ENABLE_TRAY` option does not affect this package:
-- tray_bridge.c is in CORE_SOURCES unconditionally and the define selects
-- which backend its body compiles to.
--
-- All `mcpp` paths are GLOBS relative to the verdir; the leading `*/` absorbs
-- the GitHub tarball's `EUI-NEO-0.5.7/` wrap layer.
Expand All @@ -50,6 +50,13 @@ package = {

xpm = {
linux = {
-- 0.5.7: tray_bridge.c (:207) speaks freedesktop StatusNotifierItem
-- over GDBus when EUI_TRAY_SNI is set — no GTK3, no libappindicator.
-- xim:glib publishes glib-2.0 / gio-2.0 / gobject-2.0 into the SubOS
-- view; the install() hook at the bottom of this file stages them
-- into the payload (headers for the compile, sonames for the link
-- and the runtime closure — the same mcpp#352 boundary as GL).
deps = { runtime = { "xim:glib@2.80.0" } },

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

为什么要走二进制依赖呢?而不是走 compat.glib

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

因为 GLib 在这个项目中属于运行时系统库,不是普通的“把源码编进当前项目”的兼容库,所以选择 xim:glib 二进制依赖是有意的。

两种包的职责不同

compat.glib

如果走 compat.glib,它通常意味着:

  • 每个 mcpp consumer 都从 GLib 源码重新编译;
  • GLib、GObject、GIO 的源码对象被编入当前项目的链接产物;
  • 每个 consumer 可能得到一份自己的 GLib;
  • 还要处理 Meson 生成文件、平台检测、线程、动 ;
  • 无法自然复用系统上的 GLib ABI 和运行时插件布局。

这比较适合:

  • header-only 库;
  • 小型 C/C++ 库;
  • 源码直接编译后能独立工作的第三方库;
  • 不依赖复杂安装布局和运行时模块的库。

但 GLib 并不是这种形态。它包含多个相互关联的动

libglib-2.0.so
libgobject-2.0.so
libgmodule-2.0.so
libgio-2.0.so
libgthread-2.0.so

并且 GIO 还涉及运行时模块、GDBus、动态加载和系

xim:glib

xim:glib 是预构建运行时包,负责:

  • 发布 GLib 的共享库;
  • 发布头文件;
  • 发布 pkg-config 文件;
  • 处理 libffi、pcre2、zlib 等依赖;
  • 将库正确暴露给 SubOS 的 /lib;
  • 给 ELF 写入正确的运行时依赖路径;
  • 让多个 consumer 共享同一套 ABI 兼容的 GLib。

EUI-NEO 本身只是使用 GLib:

#include <gio/gio.h>

它不应该拥有或重新构建 GLib。它只需要:

deps = { runtime = { "xim:glib@2.80.0" } }

然后链接:

-lglib-2.0
-lgio-2.0
-lgobject-2.0

为什么不能简单把 GLib 源码做成 compat 包

主要有四个问题。

  1. 重复编译和重复链接

EUI 的六个测试成员都会各自触发 compat.glib 的 他包也可能各自带一份 GLib,浪费大量时间和空间。

  1. GLib 不是单一库

GIO 依赖 GObject、GModule、GLib,并且还依赖外 码库会把“源码编译”问题变成:

GLib 构建配置

  • 生成头文件
  • 多个共享库
  • pkg-config
  • GIO modules
  • ELF closure
  • platform ABI

这已经是系统运行时打包问题,而不是 compat 源码

  1. ABI 和运行时闭包必须统一

当前问题正是 GLib 的运行时 closure:

libgio-2.0.so
├── libglib-2.0.so
├── libgobject-2.0.so
├── libffi.so.8
├── libz.so.1
├── libmount.so.1
└── libselinux.so.1

这些库必须来自同一个可控的预构建依赖图。使用 决这些运行时库的版本一致性。

  1. xim 的职责就是分发这种预构建系统栈

维护者给出的规则也是:

  • 能在构建期直接解决的,走 mcpp-index;
  • 不能在构建期合理解决、需要预构建交付的,走
  • 只有必要的 host 专有库才做最小 host-link 包

GLib 属于第二类,不属于 compat。

这次 CI 暴露的真正问题

不是“应该改成 compat.glib”,而是原来的 xim:glib 发布不完整:

  1. 初始 recipe 没有正确把 GLib .so 注册到 SubOS 的 /lib;
  2. 因此 GNU ld 找不到:

-lglib-2.0
-lgio-2.0
-lgobject-2.0

  1. #680 已修复这个发布层问题,并且已经合并;
  2. 但 EUI CI 使用的 mcpp/registry 仍然没有把 前链接环境,或者 CI 拉取的 index artifact尚未包含最新 package metadata。

所以正确路线仍是:

EUI-NEO
→ xim:glib
→ xim:glibc
→ xim:libffi
→ xim:zlib
→ xim:pcre2

而不是:

EUI-NEO
→ compat.glib

一句话总结:

▎ compat.glib 是“每个项目自己编译一份源码库” 可复用、可验证、带完整 ABI/runtime closure 的
▎ GLib”。EUI-NEO 应该使用后者。

["0.5.3"] = {
url = { GLOBAL = "https://github.com/sudoevolve/EUI-NEO/archive/refs/tags/v0.5.3.tar.gz",
CN = "https://gitcode.com/mcpp-res/eui-neo/releases/download/0.5.3/eui-neo-0.5.3.tar.gz" },
Expand Down Expand Up @@ -132,7 +139,9 @@ package = {
-- `*/include` carries the umbrella `eui_neo.h` and `eui/*.h`; `*` is the
-- verdir root, which is what makes the `"components/…"`, `"core/…"` and
-- `"3rd/stb_image.h"` quoted includes resolve. Upstream marks both PUBLIC.
include_dirs = { "*/include", "*", "mcpp_generated" },
-- `mcpp_generated/glib/include/glib-2.0` is the staged glib header tree
-- (linux SNI tray; see install()); off linux it is an empty directory.
include_dirs = { "*/include", "*", "mcpp_generated", "mcpp_generated/glib/include/glib-2.0" },

-- mcpp#233/#240: every package in a link emits its objects into ONE
-- flat obj/ dir keyed by source basename. Upstream's
Expand Down Expand Up @@ -458,13 +467,309 @@ package = {
},

linux = {
-- No tray define on purpose. Upstream only sets
-- EUI_TRAY_APPINDICATOR when pkg-config finds GTK3 AND
-- libappindicator; this index has neither, so tray_bridge.c
-- compiles its EUI_TRAY_HAS_BACKEND=0 stub — which is exactly
-- what upstream does on a machine without those dev packages.
-- `-ldl` is glad's CMAKE_DL_LIBS.
ldflags = { "-lpthread", "-ldl" },
-- 0.5.7: tray_bridge.c (:207) speaks freedesktop StatusNotifierItem
-- over GDBus when EUI_TRAY_SNI is set — no GTK3, no libappindicator.
-- xim:glib (declared at the xpm→linux level) lands the libraries in
-- the SubOS view; the install() hook below stages them plus the
-- headers into mcpp_generated/glib/ so the compile and the runtime
-- closure both resolve inside the hermetic boundary (the same
-- mcpp#352 problem as GL on the host, the same staging shape as
-- compat.glx-runtime).
-- The -L is RELATIVE to the payload: mcpp resolves it against
-- the install dir (same convention as compat.openblas's
-- "-Llib"). It is load-bearing, not decorative: with only
-- runtime.library_dirs the staged dir reaches links as
-- -Wl,-rpath, which lld happens to search for -l but GNU ld
-- does not — on the GNU-ld toolchain -lglib then falls through
-- to the host's /lib64 (wrong glib, or none at all).
cflags = { "-DEUI_TRAY_SNI=1", "-pthread" },
ldflags = { "-Lmcpp_generated/glib/lib",
"-lpthread", "-ldl",
"-lglib-2.0", "-lgio-2.0", "-lgobject-2.0" },
runtime = {
library_dirs = { "mcpp_generated/glib/lib" },
},
},
},
}

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

-- Libraries the SNI tray backend needs at runtime, COPIED into the payload
-- so the link and the runtime closure both resolve inside one directory on
-- the consumer's RPATH. Copies, not symlinks: the subos view is
-- project-local while this payload is shared across projects, and a staged
-- symlink dangles the moment the project that provided the view is not the
-- one building (observed: `gio/gio.h file not found` off a shared payload
-- pointing into a wiped project .mcpp/). The payload dir is
-- version-immutable and already on the RPATH, so copies lose nothing.
-- Two origins, in preference order:
--
-- * the glib sonames and the xim-provided transitives (libffi / pcre2 /
-- zlib — xim:glib's own deps) come from the subos view when published,
-- else straight from the xim store. The view alone is NOT enough at
-- hook time: a package already cached in the store skips the config()
-- run that publishes it, so on a fresh project view the transitives
-- can be missing even though the store holds them (observed).
-- * libmount / libselinux / libblkid have NO xim provider yet: the
-- xim:glib build links them, no xim package ships them. They are
-- staged from the HOST with a warning, the same host-plane fallback
-- compat.glx-runtime used before xim:graphics existed. The day xim
-- gains util-linux/libselinux packages this branch is dead code;
-- the day a host ships a glibc newer than the payload's, this is
-- the mcpp#352 configuration again and the warn below says so.
local subos_sonames = {
"libglib-2.0.so", "libglib-2.0.so.0",
"libgio-2.0.so", "libgio-2.0.so.0",
"libgobject-2.0.so", "libgobject-2.0.so.0",
"libgmodule-2.0.so", "libgmodule-2.0.so.0",
"libgthread-2.0.so", "libgthread-2.0.so.0",
"libffi.so.8",
"libpcre2-8.so", "libpcre2-8.so.0",
"libz.so", "libz.so.1",
}
local host_fallback_sonames = {
"libmount.so.1",
"libselinux.so.1",
"libblkid.so.1",
}
local host_lib_dirs = {

@Sunrisepeak Sunrisepeak Aug 24, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • 1.不要直接用 host 的库
  • 2.使用hook 需要做版本判断
  • 3.能构建期解决走mcpp-index, 不能构建期解决的走 xim-pkgindex (预构建),必须要用host的非开源库时要把范围收敛到最小 变成一个 link ( 具体参考 xim-pkgindex 里相关的包)

"/usr/lib/x86_64-linux-gnu", "/lib/x86_64-linux-gnu",
"/usr/lib64", "/lib64", "/usr/lib", "/lib",
}

function install()
-- Default extraction, which this package previously got by having no
-- hook at all: move the unpacked tarball into place (same idiom as
-- compat.glfw).
local srcdir = pkginfo.install_file():replace(".tar.gz", "")
if not os.isdir(srcdir) then
srcdir = "EUI-NEO-" .. pkginfo.version()
end
local idir = pkginfo.install_dir()
os.tryrm(idir)
os.mv(srcdir, idir)

-- Normalize to the single wrap layer the descriptor's `*/` globs are
-- written against. Whether the tree arrives wrapped depends on the
-- fetch path: the GitHub tarball keeps its `EUI-NEO-<v>/` top level,
-- the CN mirror's does not — observed on one machine, one version,
-- one day apart. Without this, which layout a consumer compiles is a
-- function of their mirror, and the losing side fails with
-- `"core/…" file not found` on the quoted includes.
if os.isdir(path.join(idir, "core")) then
local tmp = idir .. ".wrap-tmp"
os.tryrm(tmp)
os.mv(idir, tmp)
os.mkdir(idir)
os.mv(tmp, path.join(idir, "EUI-NEO-" .. pkginfo.version()))
end

-- The staging dir exists on EVERY platform so the include_dirs entry
-- `mcpp_generated/glib/include/glib-2.0` never names a missing path;
-- only linux populates it. Off linux it stays an empty directory,
-- which as an -I is inert.
local gendir = path.join(pkginfo.install_dir(), "mcpp_generated", "glib")
local geninc = path.join(gendir, "include")
os.mkdir(path.join(geninc, "glib-2.0"))

if os.host() ~= "linux" then
return true
end

-- Resolver, in tier order. The subos VIEW (project-local) is consulted
-- first but is NOT reliable at hook time: entries appear there only
-- when the providing package's config() runs, and a package already
-- cached in the store skips that run — observed on a wiped .mcpp/
-- where the view had the glib sonames but not pcre2/zlib/libffi yet.
-- The STORE (the xpkgs dir the payloads live in) is the stable source:
-- a package's files exist there from the moment it is fetched, which
-- for every xpm-level dep is strictly before this hook runs.
local subos = system.subos_sysrootdir()
local subos_lib = path.join(subos, "lib")

local store_roots = {}
local function add_root(root)
if root and root ~= "" and os.isdir(root) then
for _, r in ipairs(store_roots) do
if r == root then return end
end
table.insert(store_roots, root)
end
end
add_root(path.directory(path.directory(idir)))
local xlh = os.getenv("XLINGS_HOME")
if xlh and xlh ~= "" then
add_root(path.join(xlh, "data", "xpkgs"))
end

local from_store = {}
-- os.files/os.dirs are NOT in the xpm sandbox (install hook died on
-- exactly that); glob through the shell instead. One match per line,
-- no-match expands to an ls error that stderr's redirect swallows.
local function glob(pattern)
local out = {}
for line in os.iorun("sh -c 'ls -d " .. pattern .. " 2>/dev/null'"):gmatch("[^\n]+") do
table.insert(out, line)
end
return out
end
local function resolve(soname)
local in_view = path.join(subos_lib, soname)
if os.isfile(in_view) then
return in_view, false
end
for _, root in ipairs(store_roots) do
for _, hit in ipairs(glob(root .. "/*/*/lib/" .. soname)) do
if os.isfile(hit) then
return hit, true
end
end
end
return nil, false
end

-- Headers, view first then the store (same race as the sonames).
local subos_glib_inc = path.join(subos, "usr", "include", "glib-2.0")
local glib_inc = nil
if os.isdir(subos_glib_inc) then
glib_inc = subos_glib_inc
else
for _, root in ipairs(store_roots) do
local hits = glob(root .. "/xim-x-glib/*/include/glib-2.0")
if #hits > 0 then
glib_inc = hits[1]
break
end
end
end
if not glib_inc then
log.error("glib-2.0 headers found neither in this subos nor in the "
.. "xim store. They come from `xim:glib` (declared as a "
.. "runtime dep at the xpm level); if it is declared and "
.. "this still fires, the stack did not finish installing")
return false
end

-- COPY, not symlink. The subos view is project-local while this payload
-- is shared across projects: a symlink staged from the view dangles the
-- moment another project (or a wiped .mcpp/) is the one that builds —
-- observed as `gio/gio.h file not found` on a shared payload whose
-- staged link pointed into a deleted project view. The payload dir is
-- version-immutable and already on the consumer's RPATH, so copies are
-- also the stable indirection the glx-runtime note wants; a glib bump
-- simply rides the next eui-neo version.
os.tryrm(path.join(geninc, "glib-2.0"))
os.exec("cp -rL '" .. glib_inc .. "' '" .. geninc .. "'")

local genlib = path.join(gendir, "lib")
os.mkdir(genlib)
local function stage(soname, src)
os.exec("cp -L '" .. src .. "' '" .. path.join(genlib, soname) .. "'")
end

local missing = {}
for _, soname in ipairs(subos_sonames) do
local src, store_side = resolve(soname)
if src then
stage(soname, src)
if store_side then
table.insert(from_store, soname)
end
else
table.insert(missing, soname)
end
end
if #from_store > 0 then
log.warn("%s came from the xim store, bypassing a subos view that "
.. "had not published them yet (publish lists lag payloads — "
.. "libffi.so.8: openxlings/xim-pkgindex#676)",
table.concat(from_store, ", "))
end
if #missing > 0 then
log.error("%s is in neither this subos nor the xim store — see the "
.. "header note about xim:glib", table.concat(missing, ", "))
return false
end

-- The host-plane fallback. Prefer the view/store for these too (a future
-- xim provider lands there without a descriptor change); only when both
-- have nothing do the host dirs get searched, loudly.
local from_host = {}
for _, soname in ipairs(host_fallback_sonames) do
local src = resolve(soname)
if not src then
for _, dir in ipairs(host_lib_dirs) do
local cand = path.join(dir, soname)
if os.isfile(cand) then
src = cand
break
end
end
end
if src then
stage(soname, src)
local host_origin = false
for _, dir in ipairs(host_lib_dirs) do
if src:sub(1, #dir) == dir then
host_origin = true
break
end
end
if host_origin then
table.insert(from_host, soname)
end
else
table.insert(missing, soname)
end
end
if #from_host > 0 then
log.warn("%s staged from the HOST: no xim package provides them yet "
.. "(xim:glib links libmount/libselinux; util-linux and "
.. "libselinux are not packaged). Built against the host's "
.. "glibc, they are the mcpp#352 configuration the day that "
.. "glibc overtakes the payload's", table.concat(from_host, ", "))
end

-- Versioned-symbol coherence: a HOST libselinux references pcre2
-- symbols WITH version tags (PCRE2_10.xx), and a versioned reference
-- cannot bind to the xim pcre2's unversioned definitions — GNU ld
-- hard-errors at link time, lld lets it through to a runtime warning.
-- The reverse direction is fine (unversioned refs bind to versioned
-- defs), so when libselinux came from the host, pcre2 must too:
-- overwrite the xim-staged copy with the host's, which satisfies
-- both sides. Ubuntu and Fedora both ship libpcre2-8-0 by default.
local selinux_from_host = false
for _, soname in ipairs(from_host) do
if soname == "libselinux.so.1" then
selinux_from_host = true
break
end
end
if selinux_from_host then
local host_pcre = nil
for _, dir in ipairs(host_lib_dirs) do
local cand = path.join(dir, "libpcre2-8.so.0")
if os.isfile(cand) then
host_pcre = cand
break
end
end
if host_pcre then
stage("libpcre2-8.so.0", host_pcre)
else
log.warn("libselinux came from the host but libpcre2-8.so.0 did "
.. "not: the xim pcre2's unversioned symbols cannot "
.. "satisfy the host libselinux, and GNU ld will fail "
.. "the final link")
end
end
if #missing > 0 then
log.error("%s found neither in this subos, the xim store, nor on "
.. "the host", table.concat(missing, ", "))
return false
end
return true
end
Loading