Skip to content

feat(eui-neo): enable the linux SNI tray backend (EUI_TRAY_SNI) - #245

Open
FarnaHerry wants to merge 25 commits into
mcpplibs:mainfrom
FarnaHerry:feat/eui-neo-linux-tray
Open

feat(eui-neo): enable the linux SNI tray backend (EUI_TRAY_SNI)#245
FarnaHerry wants to merge 25 commits into
mcpplibs:mainfrom
FarnaHerry:feat/eui-neo-linux-tray

Conversation

@FarnaHerry

@FarnaHerry FarnaHerry commented Aug 23, 2026

Copy link
Copy Markdown
Collaborator

背景

0.5.7 的 tray_bridge.c(:207)新增了基于 GDBus 的 freedesktop StatusNotifierItem 后端:设置 EUI_TRAY_SNI 后只需 glib/gio,不再需要 GTK3 + libappindicator。此前 Linux 腿只能编 EUI_TRAY_HAS_BACKEND=0 的 stub(旧路径在这个索引里没有依赖可提供)。本 PR 把 Linux 托盘真正接上。

改动(只有 pkgs/e/compat.eui-neo.lua)

  1. xpm 层:deps = { runtime = { "xim:glib@2.80.0" } } —— 冷解析时 mcpp 会自动下载安装(已验证)。
  2. mcpp.linux:-DEUI_TRAY_SNI=1 + -Lmcpp_generated/glib/lib(payload 相对路径,同 compat.openblas 的 -Llib 惯例)+ -lglib-2.0 -lgio-2.0 -lgobject-2.0,并声明 runtime.library_dirs 使暂存目录进入消费者 RPATH。
  3. install() 钩子(新增):把 glib 头文件树和 soname 集拷贝mcpp_generated/glib/(头文件供编译,库供链接与运行时闭包)。来源分层:subos 视图 → xim store → (仅 libmount/libselinux/libblkid)宿主。

过程中发现并处理的坑(每个都有实机观测)

  • wrap 层不稳定:GitHub tarball 保留 EUI-NEO-<v>/ 顶层目录,CN 镜像的不保留。钩子里归一化为 */ glob 所依赖的单层 wrap,否则编不编得过取决于用户镜像。
  • 视图在钩子时点上不完整:包已缓存于 store 时不会重跑发布用的 config(),新项目视图就缺 pcre2/zlib;libffi 的发布清单更是漏了 .so.8(fix(libffi): publish libffi.so.8 into the subos view openxlings/xim-pkgindex#676,治本修复已提)。store 在依赖下载完成时就是全的,所以加了 store 兜底 —— 本 PR 不再依赖 #676 先合入
  • 符号链接会吊死:subos 视图是项目级的,payload 是跨项目共享的;从视图 symlink 进 payload,删掉项目 .mcpp/ 后全部吊死(gio/gio.h not found)。改为实体拷贝,payload 自包含。
  • GNU ld 不搜 rpath:runtime.library_dirs 只以 -Wl,-rpath 形式进链接行;lld(本地新 mcpp)恰好会搜 rpath 所以本地能过,CI 钉住的 2026.8.10.3 用 GNU ld,-lglib 直接落空到宿主 /lib64。显式 -L(payload 相对)补上。
  • pcre2 版本符号相干性:宿主 libselinux 引用带版本标签的 pcre2 符号(PCRE2_10.xx),xim pcre2 无版本符号 —— GNU ld 硬错误。宿主 pcre2 同时携带版本/无版本定义,两边都满足,所以 libselinux 来自宿主时 pcre2 也一并取宿主的。
  • libmount/libselinux/libblkid 无 xim 提供方:xim:glib 链接了它们但生态无对应包,按 glx-runtime 旧例从宿主暂存(带 warn)。Ubuntu runner 系统自带这三个库,CI 不需要装 glib2-devel

验证

  • CI 钉住版 mcpp 2026.8.10.3(本地下载同款):eui-neo、eui-neo-window 全冷通过
  • 最新版 mcpp 2026.8.21.3:六个成员全过(eui-neo / window / app-main / markdown / vulkan / sdl2)
  • 验证时本机 live pkgindex 里的 libffi 修复已撤回(模拟 #676 未合入的冷机器),所有手工 subos 补丁已清除

0.5.7's tray_bridge.c (:207) speaks freedesktop StatusNotifierItem over
GDBus when EUI_TRAY_SNI is set — glib/gio only, no GTK3, no
libappindicator, so the linux leg no longer has to compile the
EUI_TRAY_HAS_BACKEND=0 stub.

Wiring, in three pieces:

* xpm-level: 'deps = { runtime = { "xim:glib@2.80.0" } }' lands glib in
  the SubOS view (verified: mcpp downloads and installs it on a cold
  resolve).
* mcpp.linux: -DEUI_TRAY_SNI=1 plus -lglib-2.0/-lgio-2.0/-lgobject-2.0,
  and 'runtime.library_dirs' pointing at the staged payload dir so the
  dir reaches the consumer's RPATH.
* install() hook (new — the package previously used default extraction):
  stages the glib header tree and the soname set out of the SubOS VIEW
  into mcpp_generated/glib/, the same staging shape compat.glx-runtime
  uses for GL. Two gaps are handled explicitly:
    - the GitHub tarball keeps its EUI-NEO-<v>/ wrap layer, the CN
      mirror's does not; the hook normalizes to the wrap the '*/' globs
      are written against (observed differing on one machine, one day
      apart).
    - libmount/libselinux/libblkid have no xim provider yet (xim:glib
      links them, nothing ships them); they are staged from the HOST
      with a loud warn, the same host-plane fallback glx-runtime used
      before xim:graphics existed. libffi.so.8 comes from the view —
      needs openxlings/xim-pkgindex's libffi publish-list fix (separate
      PR) on machines whose view predates it.

Verified on a clean slate (payload, project store, and all hand-made
subos workarounds removed): all six eui-neo workspace members pass —
eui-neo, eui-neo-window, eui-neo-app-main, eui-neo-markdown,
eui-neo-vulkan, eui-neo-sdl2.
…ies not symlinks

CI on the previous commit failed cold with 'libffi.so.8 is not in this
subos'. Root cause chain: xim:glib's libgio NEEDs libffi.so.8; libffi's
publish list names only .so.7 though the tarball ships both; the runtime
closure searches the subos VIEW, so the missing publish is fatal even
though the store holds the file. openxlings/xim-pkgindex#676 fixes the
list upstream, but the descriptor must not hinge on it:

* resolve() now tiers view -> xim STORE. The view alone is unreliable at
  hook time in both directions: a publish list can lag the payload
  (libffi.so.8), and a package already cached in the store skips the
  config() run that would publish it, leaving a fresh project view
  without pcre2/zlib (observed on a wiped .mcpp/). The store is complete
  from the moment a dep is fetched, which for xpm deps is strictly
  before this hook runs. XLINGS_HOME provides the second store root.
* stage() now COPIES instead of symlinking. The subos view is
  project-local while the payload is shared across projects: a staged
  symlink dangled the moment the providing project's .mcpp/ was wiped
  (observed: 'gio/gio.h file not found' off a shared payload). Copies
  make the payload self-contained; it is version-immutable and already
  on the consumer's RPATH, so nothing is lost.
* header staging gets the same view->store treatment; os.files/os.dirs
  are not in the xpm sandbox, so globbing goes through the shell.

Verified locally against the CI shape: live pkgindex with the libffi
fix REVERTED, all hand-made subos workarounds removed, payload and
project store wiped — eui-neo passes cold, the other five members pass
against the shared self-contained payload.
CI (pinned mcpp 2026.8.10.3, GNU ld) failed every eui-neo member with
'unable to find library -lglib-2.0' — on artifacts as unrelated as
bin/libXdmcp.so, because package ldflags are unioned into every link.
Root cause: with only runtime.library_dirs, the staged dir reaches link
lines as -Wl,-rpath; ld.lld (mcpp 2026.8.21.3) happens to search rpath
for -l, so local runs passed, but GNU ld does not — its SEARCH_DIRs are
absolute host paths, so -lglib fell through to /lib64 (missing on a
clean runner, or worse: the host's glib where it exists).

Two fixes, both verified against the CI-pinned mcpp downloaded locally:

* ldflags gains '-Lmcpp_generated/glib/lib' — payload-relative, resolved
  by mcpp against the install dir, the same convention compat.openblas
  and compat.mysql-connector-cpp use ('-Llib'). Now -lglib resolves to
  the staged copies on both linkers.
* When libselinux is host-staged, libpcre2-8.so.0 is overwritten with
  the host's copy too: the host libselinux references pcre2 symbols
  WITH version tags (PCRE2_10.xx), which cannot bind to the xim pcre2's
  unversioned definitions — GNU ld hard-errors, lld only warns at
  runtime. The host pcre2 carries both versioned and unversioned defs,
  satisfying host libselinux and xim glib alike.

Verified: mcpp 2026.8.10.3 (CI pin) — eui-neo and eui-neo-window pass
cold; mcpp 2026.8.21.3 (latest) — eui-neo still passes.
@FarnaHerry

Copy link
Copy Markdown
Collaborator Author

@Sunrisepeak

Comment thread pkgs/e/compat.eui-neo.lua Outdated
"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 里相关的包)

Keep the Linux SNI change at the consumer boundary: EUI-NEO declares
xim:glib and links GIO, while xim owns the complete GLib runtime closure.
Remove host directory probing, host library copies, store globbing, pcre2
replacement, and payload staging from the consumer hook. The install hook
now only normalizes the archive wrap and is explicitly limited to 0.5.7;
older EUI-NEO versions retain their original extraction behavior.

The remaining libmount/libselinux/libblkid closure belongs in xim-pkgindex
as open-source prebuilt runtime packages, not in this descriptor.
@FarnaHerry

Copy link
Copy Markdown
Collaborator Author

已按 review 意见更新到 c155ce8:\n\n- 删除 compat.eui-neo 内所有 host 路径、host library copy、store glob 和 host pcre2 替换;消费端不再重构 GLib closure。\n- install() 只负责 archive wrap normalization,并用 pkginfo.version() == "0.5.7" 精确限制;旧版不继承 SNI hook 行为。\n- #676 已合入,libffi.so.8 的 view publication gap 已由 xim-pkgindex 修复。\n\n剩余的 libmount/libblkid/libselinux closure 将作为 xim-pkgindex 的正规开源预构建运行时依赖处理,不再从 host 引入。当前已确认原始 GLib artifact 的 DT_NEEDED 缺口,并在构建新的 xim runtime artifact。

@FarnaHerry

Copy link
Copy Markdown
Collaborator Author

补充:已将冲突解析后的最终版本推送为 \,并移除残留 merge markers;descriptor parse 已通过。当前 EUI-NEO 侧不再引入任何 host GLib closure。

Comment thread pkgs/e/compat.eui-neo.lua Outdated
-- over GDBus when EUI_TRAY_SNI is set — no GTK3, no libappindicator.
-- GLib owns and publishes its complete runtime closure in xim:
-- compat.eui-neo only consumes the declared SubOS headers/libs.
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 应该使用后者。

Sunrisepeak pushed a commit that referenced this pull request Aug 28, 2026
## CN 镜像

0.5.7 之前 gitcode 上 404,0.5.5 的描述符注释还写着「从未发布到 mcpp-res」。
本地 gtc 各发一次,三个平台腿都改成 `{ GLOBAL, CN }`:

    0.5.5  cf0da91d…  13339545 bytes
    0.5.7  2d3ec0a3…  14815553 bytes

两个都**重新下载核过**:sha256 与描述符声明的一致(一个 `sha256` 服务两个
URL,本来就要求字节相同),wrap 层也一致。

## ⚠️ 顺手推翻了一条自己写下的理由

上一版注释说「CN 镜像的 tarball 不保留 wrap 层」—— 那是照抄 #245 的说法。
去查了:gitcode 的 `eui-neo-0.5.6.tar.gz` 解出来就是 `EUI-NEO-0.5.6/`,
与 GitHub 的是同一份字节。`normalise_layout` 的扁平分支因此是**防御性的、
未被观察到的**,注释改成这么说。

## 为什么默认开启而不是做成 feature

它确实要每个 Linux 消费者付出:暂存 6.6 MB(2.9 头 + 3.7 库,45 MB 的包上
+17%)、多三条 `DT_NEEDED`、闭包多五个对象,以及 mcpp 2026.8.28.2+ 上一条
「一个库两个提供者」警告(eui-neo 经 libpng 已经在构建 compat.zlib,
而 gio 加载 `libz.so.1`)。

两条仍然定在默认开启:

1. **对称**:Windows 无条件 `EUI_TRAY_WINAPI`、macOS 无条件 `EUI_TRAY_APPKIT`,
   上游 0.5.7 也把 SNI 定为 Linux 默认。没有托盘的 Linux 才是那个异类。
2. ⚠️ **feature 表达不出来**:xpkg 的 feature 能带 `sources`/`defines`/`deps`/
   `flags`/`implies`/`requires`/`provides`,**不能带 `ldflags` 或 `include_dirs`**。
   define 可以门控,`-Lmcpp_generated/glib/lib -lgio-2.0 …` 不行 —— 关掉 feature
   的消费者照样链 glib。那比默认开启**严格更差**:代价一样,没有托盘。

真要做成 feature,得先给 mcpp 的 xpkg 解析器加 `features.<name>.ldflags`,
而那个键本身又要等索引下限抬上去;zlib 警告的根治(compat.zlib 的 `soname`)
受同一条约束。理由写进描述符,下一个读到这里的人不用再推一遍。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants