Skip to content

Commit 65a1603

Browse files
authored
feat(gnome.gio): gio 到位,pango 的闸门开了 —— 以及一次被推翻的判断 (#322)
* feat(gnome.gio): gio 到位,pango 的闸门开了 —— 以及一次被推翻的判断 §19.7 说 gio 缺席,理由是 gdbus-codegen 有 8,351 行 Python,在 build.mcpp 里 复刻它「与消费者的需要不成比例」。前面两条测量是对的,结论推不出来 —— 它把 两件不同的事混成了一件: 复刻这个生成器 ≠ 拿到它的输出 构建从来不需要那个生成器,只需要 15,392 行 C,而那是五个 XML 与 codegen 版本 的纯函数。mcpp/tools/gengdbus.sh 用上游的原参数生成一次,产物提交进 mcpp/generated/,CI 在另一台机器、另一个 Python 上重新生成并 diff。 第三步是全部论证所在:提交进仓库的生成物有一个构建永远看不见的失败模式 —— 它可以停止匹配自己的输入,而且照样能编译。索引里本来就有先例 (freedesktop.wayland-protocols-* 的 195 个生成文件),当初却因为「生成器太大」 否掉了整个包,判据用错了对象。 ⭐ 而且这次改动挖出了一个已经发货的缺陷。把 glib-mkenums 从 gobject 的 4 个 enum 扩到 gio 的 82 个,逼着去读真正的算法,才发现它有两个前缀:enum_prefix 取自枚举成员(决定 nick),@ENUMPREFIX@ 取自类型名(决定宏名)。原实现用第一个 当了两个用,于是 gnome.gobject 2.82.5 发出去的是 G_UNICODE_TYPE_TYPE,上游是 G_TYPE_UNICODE_TYPE,四个宏全错。 每一个函数名都是对的,所以它编译通过、链接通过,并且通过了那个只检查函数和 nick 的测试。推广出来的一条:一个生成器有多个互相独立的输出,测了其中一个不 构成对其余的证据。CI 现在分开检查宏名、nick、enum/flags 归类;gobject 示例加 了一行 G_TYPE_UNICODE_TYPE == g_unicode_type_get_type() —— 宏拼错就是编译 错误,这是唯一能识破旧 tarball 的探针。 包版本仍是上游的 2.82.5(不加 fork 后缀):tag 原地重切,sha256 换成 628b8f98…,CN 容器 tag 走 2.82.5-3(gitcode 不允许替换同名 asset)。代价写进 了描述符:store 按 (name, version) 索引,已解开过旧 2.82.5 的机器要清一下。 gio 的四类输入里有三类缺了会静默降级,所以示例按名字断言每一类:inotify 读回 GFileMonitor 的类型名是不是 GInotifyFileMonitor(和轮询实现唯一能区分的地方)、 gvdb 用一段非 gvdb 字节要求它干净地拒绝、xdgmime 只断言「答得出来」而不断言 具体 MIME(那取决于 runner 有没有 /usr/share/mime)。 pkgs/g/gnome.gio.lua 新增 pkgs/g/gnome.{glib,gobject,gmodule}.lua 新 sha256;❌ 标注被推翻的 gio 段落 tests/examples/gio/ 30 项断言 tests/examples/gobject/ 加识破旧 tarball 的宏探针 .agents/docs/…design.md §19.7 就地标 ❌ 指向 §20;新增 §20 八小节 本地:四个示例在 gcc 16.1 与 llvm 22.1 上全绿;fork CI 四个 job 全绿。 * fix: tests/examples/gio joins [workspace].members `mcpp test -p gio` is how CI runs it, and -p resolves against the root workspace manifest — a new example that is not listed there fails with error: workspace member 'gio' not found in [workspace].members before anything is downloaded, which reads like a package problem and is not. * feat(gnome.*): 四个成员补上 import —— namespace 是契约 索引里 namespace 决定消费形态:compat.xxx 按头文件消费,归属 namespace 表示包 提供 import。freedesktop.cairo 导出 544 个名字、wlroots.wlroots 986、 freedesktop.libdisplay-info 206;gnome.* 四个是当时唯一的违例 —— 用了归属 namespace 却没有模块。 gnome.glib 2,732 gnome.gobject 495 转出 gnome.glib gnome.gmodule 20 转出 gnome.glib gnome.gio 2,850 转出全部三个 转出不是便利:glib 是 workspace path 依赖,消费者再点名它就会得到 `requested as both a version dep and a path dep`,所以 import gnome.gio; 必须 和 #include <gio/gio.h> 一样完整。先以失败出现的:'GQuark' was not declared。 ⚠️ 两条路不能混用。同一个 TU 两条都走会经由两条路径到达 <time.h>,同一个 struct tm 变成两个实体(conflicting declaration 'struct tm')。选哪条由宏决定: 模块带不了宏,glib 的宏占 API 的一半。G_DEFINE_TYPE 那类代码走头文件;函数 API 走模块。每个成员因此各带两个测试,两扇门都被检验。 我原来在描述符里写「没有模块,因为 API 宏化」—— 观察对,结论错。已就地标 ❌。 ⭐ 生成器四次静默漏名,每次产出的包装体都能编译,全部由消费者或另一条工具链 发现,而不是由构建: = '{' 字符字面量里的花括号(G_VARIANT_CLASS_DICT_ENTRY)让 typedef 读取器失衡,吞掉 gvariant.h 剩余全部内容;每个 g_variant_* 消失,而另外 1,853 个名字让它看起来很完整 G_DECLARE_INTERFACE 展开成 typedef 和 _get_type,文本扫描器两个都看不见 —— GListModel、GListStore、g_list_model_get_type,正是 pango 等的那几个 <glibconfig.h> 拼写不带 glib/ 前缀,按 umbrella 推导头文件集合看不到, 而 gsize/gssize 正是在那里 typedef 的 void (g_free) (…) glib 把名字加括号防宏展开,声明符位于括号深度 1 还有一次结构性的:注释剥离与 typedef 读取器是两个读者,后者从没见过前者,于是 enum 里的注释变成了导出名。一个流上两个读者就是 bug。 ⭐⭐ 只有两条工具链能看见的一条:导出 enum 的 typedef 会让枚举量在 GCC 上可见, clang 拒绝同一个文件(use of undeclared identifier 'G_MODULE_BIND_LAZY')。 现在每个枚举量按名导出。这是本 fork 里「CI 跑两条工具链」最有力的论据。 tarball 重切:sha256 → 38a175bc…,CN 容器 tag → 2.82.5-4,包版本仍是上游的 2.82.5。 本地:四个示例 × 两条路线 × 两条工具链全绿;fork CI 四个 job 全绿。
1 parent 2b9145e commit 65a1603

13 files changed

Lines changed: 1511 additions & 123 deletions

File tree

‎.agents/docs/2026-08-30-graphics-stack-coverage-design.md‎

Lines changed: 277 additions & 12 deletions
Large diffs are not rendered by default.

‎mcpp.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ members = [
107107
"tests/examples/glib",
108108
"tests/examples/gobject",
109109
"tests/examples/gmodule",
110+
"tests/examples/gio",
110111
"tests/examples/libgbm",
111112
"tests/examples/libpng",
112113
"tests/examples/libwebp",

‎pkgs/g/gnome.gio.lua‎

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
-- gnome.gio — part of GLib 2.82.5.
2+
--
3+
-- Files, streams, sockets, D-Bus, GApplication, GSettings, GResource — and
4+
-- `GListModel`, which is what pango's `PangoFontMap` implements.
5+
--
6+
-- Upstream ships glib as one source tree producing FOUR separate shared
7+
-- libraries, and this index follows that split: `gnome.glib`, `gnome.gobject`,
8+
-- `gnome.gmodule` and `gnome.gio` are four packages from one fork, because
9+
-- that is what a consumer links.
10+
--
11+
-- ─────────────────────────────────────────────────────────────────────────
12+
-- ⭐ THIS PACKAGE EXISTS BECAUSE AN EARLIER ARGUMENT WAS WRONG
13+
--
14+
-- gio was left out of this index once, with the reason: two of its six
15+
-- generators are `gdbus-codegen`, an 8,351-line Python program that turns
16+
-- D-Bus interface XML into GObject skeletons, and reimplementing it in
17+
-- `build.mcpp` is not proportionate. That premise was measured and correct.
18+
-- The conclusion did not follow, because it conflated two different things:
19+
--
20+
-- reproducing the generator ≠ obtaining its output
21+
--
22+
-- The build never needed the generator. It needed 15,392 lines of C that are a
23+
-- PURE FUNCTION of five XML files and the codegen version — no target, no
24+
-- host, no locale enters it. So `mcpp/tools/gengdbus.sh` produces them once,
25+
-- they live in `mcpp/generated/`, and a CI job REGENERATES AND DIFFS them on a
26+
-- runner with a different Python than the one that produced them.
27+
--
28+
-- That last part is the whole argument. Committed output has exactly one
29+
-- failure mode a build never sees: it can stop matching its input, and it
30+
-- still compiles. A diff is what makes "checked in" different from "stale".
31+
--
32+
-- The same arrangement `freedesktop.wayland-protocols-*` already used for its
33+
-- 195 generated files — where it was FORCED, since a package whose product is
34+
-- headers cannot generate at build time. Here it is not forced; it is simply
35+
-- the cheap way to get code a rewrite would otherwise have to produce.
36+
--
37+
-- ─────────────────────────────────────────────────────────────────────────
38+
-- THE OTHER FIVE GENERATORS ARE C++, AS EVERYWHERE ELSE IN THIS FORK
39+
--
40+
-- gio-visibility.h gen_visibility("GIO", …)
41+
-- gnetworking.h one substitution
42+
-- gioenumtypes.{h,c} glib-mkenums over 82 enums
43+
--
44+
-- ⚠️ 82 ENUMS AGAINST gobject's FOUR, and what changes at that scale is not
45+
-- the loop but the ANNOTATIONS — with a trap in each direction:
46+
--
47+
-- gio writes `/*< private >*/`, `/*< public >*/` and `/*< protected >*/`
48+
-- 144 times. Those are GTK-DOC annotations for STRUCT MEMBERS and mean
49+
-- nothing to mkenums. A scanner that read every `/*< … >*/` would silently
50+
-- drop enumerators.
51+
--
52+
-- `GConverterFlags` has NO `/*< flags >*/` despite the name, so upstream
53+
-- registers it with `g_enum_register_static`. A scanner that guessed from
54+
-- the type name would disagree with upstream's ABI.
55+
--
56+
-- The nicks are public API — `g_flags_get_value_by_nick` reads them — and gio
57+
-- overrides seventeen of them. Derived, `G_CONVERTER_NO_FLAGS` would be
58+
-- "no-flags"; upstream says "none".
59+
--
60+
-- `gconstructor_as_data.h` is upstream's sixth generator and is NOT here: it
61+
-- embeds `glib/gconstructor.h` as a C string for `glib-compile-resources`, a
62+
-- TOOL. See below.
63+
--
64+
-- ─────────────────────────────────────────────────────────────────────────
65+
-- ⚠️ WHAT gio NEEDS BESIDES gio/*.c — AND WHY EACH IS CHECKED BY NAME
66+
--
67+
-- gio is assembled from four kinds of input. A missing `gio/*.c` is an
68+
-- undefined reference, which the linker catches. The other three DEGRADE
69+
-- SILENTLY, so the example test asserts each one by name rather than trusting
70+
-- that it built:
71+
--
72+
-- gio/xdgmime/ absent → g_content_type_guess answers
73+
-- "application/octet-stream" for everything
74+
-- gio/inotify/ absent → the file monitor quietly falls back to
75+
-- polling; the test reads the backend's TYPE NAME,
76+
-- which is the only place the two differ
77+
-- subprojects/gvdb/ absent → GResource and GSettings' schema reader are
78+
-- never reachable, and nothing says so
79+
-- mcpp/generated/ stale → it still compiles; the portal calls fail
80+
-- only inside a Flatpak sandbox
81+
--
82+
-- `-DXDG_PREFIX=_gio_xdg` is load-bearing rather than hygiene: xdgmime's
83+
-- header macro-renames every `xdg_mime_*` symbol, and `gcontenttype.c` calls
84+
-- the unprefixed spelling. Both sides must see the same value or the calls and
85+
-- the definitions are different symbols.
86+
--
87+
-- ─────────────────────────────────────────────────────────────────────────
88+
-- NO TOOLS, AND NO EXTERNAL DEPENDENCY BUT zlib
89+
--
90+
-- The ten tools — `gio-tool`, `gdbus-tool`, `glib-compile-schemas`,
91+
-- `glib-compile-resources`, `gsettings`, … — are NOT built. Each provides
92+
-- `main`, and a dependency that provides one collides with the consumer's;
93+
-- that is the rule every package in this index follows.
94+
--
95+
-- Upstream's meson names exactly one external dependency for the library
96+
-- beyond glib itself: `libelf`, used by `gresource-tool.c` — an
97+
-- `executable()`, not the library. Measured: no library source mentions elf at
98+
-- all. There is therefore NO libelf feature, because there is nothing for it
99+
-- to switch.
100+
--
101+
-- `libmount`, `selinux` and `sysprof` are `auto` upstream and absent here.
102+
-- Each is `#ifdef`-tested, so each is ABSENT rather than 0, and gio degrades
103+
-- the way upstream intends: GUnixMountMonitor falls back to reading
104+
-- /proc/self/mountinfo, and the SELinux and tracing attributes are not
105+
-- offered. Defining any of them as 0 would say "yes" — see `gnome.glib` for
106+
-- the nine macros that lesson came from.
107+
--
108+
-- ─────────────────────────────────────────────────────────────────────────
109+
-- ⚠️ NAME WHAT YOU USE; DO NOT NAME glib
110+
--
111+
-- [dependencies]
112+
-- gnome.gio = "2.82.5"
113+
-- gnome.gobject = "2.82.5"
114+
-- # gnome.glib and gnome.gmodule arrive transitively
115+
--
116+
-- gio declares glib, gobject and gmodule as workspace PATH dependencies — they
117+
-- are built from one tree — and mcpp rejects a package requested both ways:
118+
--
119+
-- error: dependency 'gnome.glib' is requested as both a version dep
120+
-- (by 'your-package') and a path dep (by 'gnome.gio')
121+
--
122+
-- ─────────────────────────────────────────────────────────────────────────
123+
-- ⚠️ NO extern "C" WRAPPER
124+
--
125+
-- gio decorates every header with G_BEGIN_DECLS, so a wrapper is redundant —
126+
-- and harmful: gio.h reaches <stdlib.h>, which libc++ routes through <cstdlib>,
127+
-- which defines TEMPLATES. Inside an extern "C" block that is
128+
-- `templates must have C++ linkage`, dozens of times, against a header the
129+
-- consumer never named. libstdc++ does not route them that way, so gcc is
130+
-- green and clang is a wall.
131+
--
132+
-- ❌ "THERE IS NO MODULE" USED TO BE HERE, AND IT WAS THE WRONG CONCLUSION.
133+
-- The API being macro-heavy is true and does not make a module pointless: this
134+
-- one hands a consumer 2,850 declarations, and gio's function API — GFile,
135+
-- GSocket, GVariant, GListModel — needs no macros at all. See above.
136+
--
137+
-- ─────────────────────────────────────────────────────────────────────────
138+
-- LINUX ONLY
139+
--
140+
-- This is the Unix build: the Windows and Cocoa backends are excluded because
141+
-- they are ALTERNATIVES to the Unix ones, not companions — both halves define
142+
-- the volume monitor, the app-info backend and the settings backend. The
143+
-- generated `glibconfig.h` fixes `G_OS_UNIX` besides, and the upstream tree
144+
-- carries two `COPYING` symlinks a Windows extraction would not survive.
145+
-- ─────────────────────────────────────────────────────────────────────────
146+
-- ⭐ TWO WAYS TO CONSUME IT, AND YOU PICK ONE
147+
--
148+
-- import gnome.gio; -- the module route
149+
-- #include <gio/gio.h> -- the header route
150+
--
151+
-- The namespace is the contract in this index: `compat.xxx` means headers, an
152+
-- owner namespace means the package exposes `import`. This module exports
153+
-- 2,850 names. It re-exports all three siblings, because
154+
-- `gio.h` reaches all three and because a consumer CANNOT name them itself —
155+
-- they are workspace path dependencies.
156+
--
157+
-- ⚠️ THE TWO ROUTES DO NOT COMPOSE. A TU that imports the module AND textually
158+
-- includes a glib header reaches <time.h> twice — once through the module's
159+
-- global fragment, once directly — and the same `struct tm` from the same file
160+
-- becomes two entities:
161+
--
162+
-- error: conflicting declaration 'struct tm'
163+
-- note: previous declaration as 'struct tm' (of module gnome.gio)
164+
--
165+
-- WHICH ROUTE IS DECIDED BY MACROS. A module cannot carry them, and glib's are
166+
-- half its API — 1,337 `#define` against 1,312 declarations for glib, 1,679
167+
-- against 1,753 for gio. `G_DEFINE_TYPE`, `G_OBJECT`, `g_signal_connect` and
168+
-- every `G_TYPE_*` are macros, so code that defines a GObject subclass takes
169+
-- the HEADER route. Code that uses the function API — most of gio — takes the
170+
-- MODULE route and includes nothing at all.
171+
--
172+
-- The wrapper is GENERATED from upstream's public headers, so a name upstream
173+
-- adds or removes cannot be silently missed. That is not a preference at this
174+
-- size: four separate silent misses were found by consumers rather than by the
175+
-- build — a brace inside a char literal that swallowed the rest of a file,
176+
-- `G_DECLARE_INTERFACE` (which expands to the names rather than spelling
177+
-- them), `<glibconfig.h>` being spelled without a `glib/` prefix, and glib's
178+
-- habit of parenthesising a name to defend it from macro expansion.
179+
--
180+
package = {
181+
spec = "1",
182+
namespace = "gnome",
183+
name = "gio",
184+
description = "GIO 2.82.5 — files, streams, sockets, D-Bus, GSettings and GListModel",
185+
licenses = {"LGPL-2.1-or-later"},
186+
repo = "https://github.com/mcpplibs/glib",
187+
type = "package",
188+
189+
xpm = {
190+
linux = {
191+
["2.82.5"] = {
192+
url = {
193+
GLOBAL = "https://github.com/mcpplibs/glib/archive/refs/tags/2.82.5.tar.gz",
194+
-- ⚠️ The container tag is `2.82.5-4`, not `2.82.5`. gitcode
195+
-- refuses to REPLACE an asset of the same name in an
196+
-- existing release, so each corrected tarball needs a new
197+
-- container tag while the PACKAGE version stays upstream's.
198+
-- Verified byte-identical to the GLOBAL tag archive.
199+
CN = "https://gitcode.com/mcpp-res/glib/releases/download/2.82.5-4/glib-2.82.5.tar.gz",
200+
},
201+
sha256 = "38a175bcd8899f376b6540e2e3ef7450169205fc56bdb909cfb086a0438553e5",
202+
},
203+
},
204+
},
205+
206+
mcpp = "*/mcpp/gio/mcpp.toml",
207+
}

0 commit comments

Comments
 (0)