|
1 | 1 | # mcpp 图形栈:从「能跑通」到「能开发」的覆盖面设计 |
2 | 2 |
|
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 桌面栈补齐与判据修正)** |
4 | 4 |
|
5 | 5 | ## 0. 这份文档解决什么 |
6 | 6 |
|
@@ -1273,3 +1273,159 @@ index: local index af79fd2 (never refreshed) |
1273 | 1273 | **所以任何在 `--sandbox` 里做的验证,第一步必须是 `mcpp index update`** —— |
1274 | 1274 | 否则测的是镜像打包那天的生态,而不是今天的。这与 |
1275 | 1275 | 「本地旧索引快照掩盖描述符错误」那条 是同一类,只是发生在沙箱侧。 |
| 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