Skip to content

Commit 217884d

Browse files
authored
docs: §17 桌面栈补齐与两条判据修正(v1.4) (#313)
文档停在 v1.3,不含 fontconfig、cairo、freetype 修复和最终沙箱结果。补齐,并把 过程中被实测**推翻**的两条判据写清楚——两条都是本文档自己先前写下的。 §17.1 决定 fork 难度的是**生成器**,不是行数。cairo 104k 行零生成器,fontconfig 26k 行七个生成器;§13.2 按行数排的优先级是反的。新判据要区分「生成代码」和 「记录探测结果」——后者永远手写,而且应该手写。 §17.2 build.mcpp 是完整 C++,由此生成器可以**比上游更正确**(上游把 U+00A0 按 码点转义成单字节;gperf 的完美哈希对只读一个字段的消费者是多余的)。 §17.3 fontconfig 七个生成物全部 build.mcpp 产出,两个大的与上游逐字节一致。 fclang.h 第一次 diff 差 2/4897 行,是 locale collation——而 std::sort over std::string 是字节序,结构性免疫。 §17.4 cairo 后端做成 feature、X11 默认关;feature 的 sources 必须逐条列(glob 不受 feature 控制);验证要看产物不看 manifest。归档 47.8MB -> 1.8MB。 §17.5 最贵的 bug 是一个 0:#ifdef 对 0 一样成立,于是 cairo 在 x86-64 上按大端 算定点数——编过、链过、报 SUCCESS、cairo_paint 正常,而每条路径拿到垃圾坐标、 cairo_fill 改动零像素。由此改了测试写法:除结果外再断言**中间量**。 §17.6 一个 compat 包的源码列表就是它的 ABI 承诺,而承诺是被消费者发现的—— freetype 的测试一直是绿的,是 fontconfig 和 cairo 分别从两个方向撞出了缺口。 同一轮里「upstream 与发布物一致」那条检查拦住了上游 14 个 .gitignore 吞掉的 5 个文件。 §17.7 「干净环境的索引是那天的」第三次出现,这次在 fork CI 里。凡从零开始的 验证第一步必须 mcpp index update。 §17.8 最终沙箱验证全文,含两条专为本轮坑设的断言。 §17.9 桌面栈状态表:只剩 pango,卡在 glib。 只改 .agents/docs/,不触发任何 CI。
1 parent afde8dd commit 217884d

1 file changed

Lines changed: 157 additions & 1 deletion

File tree

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

Lines changed: 157 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# mcpp 图形栈:从「能跑通」到「能开发」的覆盖面设计
22

3-
Date: 2026-08-30 · 前置:[`2026-08-30-gbm-cross-repo-closed-loop-plan.md`](2026-08-30-gbm-cross-repo-closed-loop-plan.md) §19/§20 · **状态:已实现并闭环验证(v1.3,§11 总账 / §12 客户端侧 / §13 仍缺 / §14 fork 规范 / §15 fontconfig / §16 沙箱盲点)**
3+
Date: 2026-08-30 · 前置:[`2026-08-30-gbm-cross-repo-closed-loop-plan.md`](2026-08-30-gbm-cross-repo-closed-loop-plan.md) §19/§20 · **状态:已实现并闭环验证(v1.4,§11 总账 / §12 客户端侧 / §14 fork 规范 / §17 桌面栈补齐与判据修正)**
44

55
## 0. 这份文档解决什么
66

@@ -1273,3 +1273,159 @@ index: local index af79fd2 (never refreshed)
12731273
**所以任何在 `--sandbox` 里做的验证,第一步必须是 `mcpp index update`** ——
12741274
否则测的是镜像打包那天的生态,而不是今天的。这与
12751275
「本地旧索引快照掩盖描述符错误」那条 是同一类,只是发生在沙箱侧。
1276+
1277+
---
1278+
1279+
## 17. 桌面栈补齐:fontconfig、cairo,以及被推翻的两条判据
1280+
1281+
§15 停在「fontconfig 还差 fc-case 和 fc-lang」。两个都做完了,cairo 也做完了,而
1282+
过程中有两条判据被实测推翻——都是本文档自己先前写下的。
1283+
1284+
### 17.1 判据修正一:决定 fork 难度的是**生成器**,不是行数
1285+
1286+
| | 源码 | 生成器 | 实际难度 |
1287+
|---|---|---|---|
1288+
| fontconfig | 26k 行 | **7 个**(python + gperf) ||
1289+
| cairo | **104k 行** | **0 个** ||
1290+
1291+
§13.2 按行数把 cairo 标成「中」、把 fontconfig 标成「中」,并据此排了优先级——排反了。
1292+
cairo 的 meson 只出两个产物,`config.h``cairo-features.h`,两个都是
1293+
`configure_file`:那是探测**答案**,属于手写的适配文件,不是生成物。
1294+
1295+
**新判据**:看 `grep -c 'custom_target\|configure_file'`,并区分「生成代码」与
1296+
「记录探测结果」。后者永远是手写,而且应该手写——决定属于可读可争的文件,不属于
1297+
生成器。
1298+
1299+
### 17.2 判据修正二:`build.mcpp` 是 C++ 程序,不是配置
1300+
1301+
§14.1 已记。这里补一条后果:因为它是完整 C++,**生成器可以比上游更正确**
1302+
1303+
- libdisplay-info:上游 python 把 U+00A0 按码点转义成 `\240`(单字节,不是
1304+
UTF-8),`build.mcpp` 按字节转义成 `\302\240`
1305+
- fontconfig:上游用 gperf 对 72 个条目做完美哈希,而唯一消费者只读一个字段;
1306+
排序表 + 二分查找语义相同,少一个工具和一遍 C 预处理
1307+
1308+
### 17.3 fontconfig(mcpp-index#310)
1309+
1310+
七个生成物全部由 `build.mcpp` 产出,零 python / 零 sh / 零 gperf。两个大的**
1311+
上游逐字节一致**,fork CI 每次重跑上游脚本再 diff:
1312+
1313+
```
1314+
fclang.h 4897 行 281 个 .orth -> charset 位图
1315+
fccase.h 368 行 Unicode 大小写折叠
1316+
```
1317+
1318+
**`fclang.h` 第一次 diff 有 2/4897 行不同,原因是 locale collation** ——参考输出
1319+
`ls *.orth` 生成,UTF-8 locale 下 `ayc.orth` 排在 `ay.orth` 前(比较忽略点号)。
1320+
`LC_ALL=C` 后一致。这是 libglvnd `genglmod.sh` 那个坑的第二次撞见,而
1321+
`build.mcpp``std::sort` over `std::string`**字节序**,结构性免疫。
1322+
1323+
### 17.4 cairo(mcpp-index#312)
1324+
1325+
后端做成 **feature**,`default = ["ft", "fc", "png"]`,**X11 默认关**。上游把每个
1326+
后端做成 `get_option()`,等于让发行版替所有人决定一次;索引不能这样,因为合成器
1327+
和 X11 应用要的是同一个包的不同构建。
1328+
1329+
两条实现细节值得记:
1330+
1331+
- **feature 的 `sources` 必须逐个文件列**。选通按字面条目匹配,glob 不受 feature
1332+
控制——`"*/src/cairo-xlib-*.c"` 会把 X11 后端编进**每一个**消费者。上游自己的
1333+
`cairo_feature_sources` 字典正好是逐条的,照抄即可。
1334+
- **验证要看产物,不是 manifest**。fork CI 查 `.o` 里有没有 `XOpenDisplay` /
1335+
`xcb_connect`,有没有 `cairo-xlib-*.o` 被编出来。manifest 写的是意图。
1336+
1337+
**归档从 47.8 MB 降到 1.8 MB**:cairo 发布物带 61 MB 参考图(`test/`),这个包一个
1338+
都不编。裁掉 `test/``perf/`,同时把「upstream 与发布物一致」检查改为比对**剩下
1339+
的树**而不是关掉——那条检查此前已经拦住过一次真实缺口(见 §17.6)。
1340+
1341+
### 17.5 最贵的一个 bug 是一个 `0`
1342+
1343+
```c
1344+
#define WORDS_BIGENDIAN 0 // 我写的
1345+
#ifdef FLOAT_WORDS_BIGENDIAN // cairo 怎么测(cairoint.h:196)
1346+
```
1347+
1348+
`#ifdef` 对 `0` 一样成立,所以这是在说**大端**。x86-64 上的后果是最坏的那种静默:
1349+
1350+
- 编过、链过、`cairo_status` 全程 SUCCESS、`cairo_paint` **正常工作**
1351+
- 每一条**路径**拿到垃圾定点坐标:`cairo_rectangle(4,4,16,16)` 的
1352+
`path_extents` = `-8.03e+06 … 4.37e+06`,`cairo_in_fill(40,40)` 对界外点返回 1
1353+
- `cairo_fill` / `cairo_stroke` 改动 **0 个像素**,一声不吭
1354+
1355+
查了一小时「是不是缺了扫描转换器 / spans 合成器」。**判据**:上游用 `#ifdef` 测的
1356+
宏,只能「定义」或「不定义」,不能定义成 0。
1357+
1358+
**由此改了测试写法**:除了断言结果,再断言**中间量**。像素断言只会说「描边没画」,
1359+
把人引向缺文件;`cairo_path_extents` 直接指出算术错在哪。同类中间量:
1360+
`di_edid_get_version` 之于 EDID、`FcLangGetCharSet` 之于 fclang。
1361+
1362+
### 17.6 一个 compat 包的源码列表就是它的 ABI 承诺
1363+
1364+
两次撞见,方向相反:
1365+
1366+
- fontconfig 按上游默认声明 `HAVE_FT_GET_BDF_PROPERTY` → 链接期**八个**未定义引用,
1367+
因为 `compat.freetype` 不编 BDF/Type1 模块 → 关掉这两个探测项
1368+
- cairo **无条件**调 `FT_GlyphSlot_Embolden`(无 `HAVE_` 守卫)→ **两个**未定义
1369+
引用,因为 `compat.freetype` 漏了 `ftsynth.c` → 补进 compat.freetype(#311)
1370+
1371+
**承诺是被消费者发现的,不是被自己的测试发现的**——freetype 的测试成员一直是绿的。
1372+
1373+
同一轮里,cairo 的 fork CI 抓到另一个完整性缺口:cairo 自带 **14 个 `.gitignore`**,
1374+
fork 提交时生效,吞掉了 5 个**在发布 tarball 里存在**的文件。抓到它的正是那条
1375+
「upstream 与发布物逐字节一致」——我写它时当成防手改的形式检查。
1376+
1377+
### 17.7 「干净环境的索引是那天的,不是今天的」——第三次
1378+
1379+
| 场景 | 症状 | 真因 |
1380+
|---|---|---|
1381+
| 沙箱验证 | 「包找不到」 | 合成 home 的索引快照早于被测包 |
1382+
| 索引测试成员 | 「glob 没匹配到文件」 | store 缓存了旧 tarball |
1383+
| **cairo fork CI** | **两个 `FT_GlyphSlot_*` 未定义引用** | **runner 的索引快照早于同日的 freetype 修复** |
1384+
1385+
三次报错都指向别处,最后一次读起来像 cairo 有 bug。**凡是从零开始的验证——沙箱、
1386+
CI、新 subos——第一步必须 `mcpp index update`。** 已加进 cairo 的 fork CI。
1387+
1388+
这也解释了为什么本地一直绿:开发环境的索引是新的。**本地绿 ≠ 干净环境绿**,差别
1389+
恰好是「索引有多旧」。
1390+
1391+
### 17.8 最终沙箱验证(五个包,已发布索引)
1392+
1393+
```
1394+
===== 5. did anything come from the host? =====
1395+
PASS: the host's copies were present and reachable, and none of them won
1396+
===== 5b. libdisplay-info is kind=lib, so it must NOT be a DT_NEEDED =====
1397+
PASS: libdisplay-info's objects were merged in
1398+
1399+
===== 6. run it =====
1400+
wl_egl_window_create 0x1e3b28c0 客户端 GPU 入口
1401+
wl_cursor_frame 0 客户端指针
1402+
di_info_get_make Acer Technologies EDID + 生成的 PNP
1403+
fclang zh-cn 6765 codepoints 语言表完整
1404+
cairo inside=255,255,255 outside=191,128,64 矩形填充内外像素精确
1405+
cairo xlib off feature 默认集生效
1406+
0 failure(s)
1407+
RESULT: PASS
1408+
```
1409+
1410+
后两条断言是**专为本轮踩过的坑设的**:`zh-cn` 的码点数会让截断的语言表当场露馅,
1411+
而不是很久之后表现为「匹配不到字体」;cairo 的内外两个像素会让字节序 bug 露馅,
1412+
而 `cairo_status` 全程 SUCCESS。
1413+
1414+
### 17.9 桌面栈的当前状态
1415+
1416+
| 层 | 状态 |
1417+
|---|---|
1418+
| 渲染链 DRM→GBM→EGL→GLES | ✅ 沙箱像素级 |
1419+
| 输入链 udev→evdev/mtdev→libinput→xkb | ✅ 沙箱 |
1420+
| 客户端 GPU 路径 / 指针 | ✅ |
1421+
| 显示器识别(EDID) | ✅ |
1422+
| 字体发现(fontconfig) | ✅ |
1423+
| 2D 矢量绘制(cairo) | ✅ |
1424+
| **段落排版(pango)** | ⬜ **卡在 glib** |
1425+
1426+
**wlroots 的闸门已开**:它需要的东西索引里现在全有,做与不做是「要不要」而非
1427+
「能不能」。
1428+
1429+
**pango 是唯一量准后仍需决策的**:57k 行本身不大,但硬依赖 glib,而 glib 是
1430+
`glib/` + `gobject/` + `gio/` 三大块 300k+ 行,自己还缺 `pcre2` 和 `libmount`。
1431+
按 §17.1 的新判据,它的难点也不在行数——需要先数它的生成器再定。

0 commit comments

Comments
 (0)