鸿蒙PC移植libadwaita1.8.8:从编译到合入实录
欢迎加入开源鸿蒙PC社区: https://harmonypc.csdn.net/
欢迎在PC社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper
鸿蒙PC移植libadwaita1.8.8:无显示测试实录
| 元信息 | 值 |
|---|---|
| 对象 | libadwaita 1.8.8(GNOME Adwaita UI 组件库,C 原生) |
| 环境 | HUAWEI MateBook Pro(HAD-W32)/ HarmonyOS 6.1.0 / aarch64(uname -m 实测)/ clang 15 / 社区适配版 Conan 2.29.1 |
| 仓库 | build_in_harmonyos(AtomGit:OpenHarmonyPCDeveloper/build_in_harmonyos) |
| PR | #13574 |
目录
- 摘要
- 一、背景:为什么值得做
- 二、环境与差异前置(9 个差异点先列全)
- 三、适配过程(10 个坑四段式全记录)
- 四、验证(分层,禁止相加)
- 五、知识沉淀(4 条草稿)
- 六、FAQ
- 七、总结
- 参考链接
摘要
把 GNOME 的 Adwaita UI 组件库 libadwaita 1.8.8 移植到鸿蒙 PC(aarch64):1 个 portability 补丁、10 个适配问题、9 项依赖解析。上游 63/63 测试实跑全部因无显示服务阻塞(SIGTRAP,全量证据链入仓可复现);其中 2 项纯计算测试的 105/105 公共 API 断言 1:1 移植执行全部通过;消费者探针 124/124 全过。PR #13574 已合入 main,沉淀 4 条知识草稿。
libadwaita 是纯 UI 组件库,它的核心 API(widget、样式、动画)全部预设了显示服务器的存在;而鸿蒙 PC 目前没有显示服务。让它编译通过只花了约 7 分钟——真正的难点在于:在一个"没有显示器"的环境里建立分层测试证据链——上游 63 个测试哪些能执行、哪些断言可以移植、如何证明"能验证的全验证了、不能验证的写清楚了"。本文是这条证据链的完整实录,包括 2 处我自己走错路的误判。
诚实声明:以下内容在本环境中未验证,本文不写成"验证通过":
① 61 个创建 widget 的上游测试(无显示服务,SIGTRAP 阻塞);
② 137 条nearest_from_rgba断言(私有 API,库未导出该符号);
③ 一切视觉渲染效果(无显示设备)。
本文结论限定于"无显示 API 子集"与"可移植断言子集"两个边界之内。
一、背景:为什么值得做
libadwaita 是 GNOME 42+ 的默认 UI 组件库,整个 GTK4 应用生态的视觉底座——GNOME 45+ 桌面应用的控件、主题、动画系统直接构建在它之上。它在本仓库生态链中的位置是承上启下的关键一环:
- 生态价值:基础库,下游受益面大。libadwaita 就位后,GTK4 应用层(含 C++ 绑定 gtkmm)才有完整的视觉组件依赖;
- 补链价值:本仓库此前已适配其 C++ 绑定 gtkmm 4.0.0.1,"无显示 API 探针"方法论(知识条目 E471)正是在那次任务中诞生的;C 原生核心库缺位,等于 GNOME 桌面链断在中间;
- 难度价值:无显示服务这一平台缺口,迫使测试策略整体重设计——不是"跑上游测试集"这么简单,而是要回答"上游测试集里到底哪部分属于消费者可验证的范围"。
二、环境与差异前置
2.1 环境
| 项 | 值 |
|---|---|
| 设备 | HUAWEI MateBook Pro(HAD-W32) |
| 系统 | HarmonyOS 6.1.0 |
| 架构 | aarch64(uname -m 实测;conan profile arch=armv8 交叉印证) |
| 工具链 | clang 15(conan MesonToolchain,libc++) |
| 构建工具 | 社区适配版 Conan 2.29.1(OHOS 枚举/三元组已适配)+ build_in_harmonyos |
| 上游源 | https://download.gnome.org/sources/libadwaita/1.8/libadwaita-1.8.8.tar.xz |
| SHA-256 | 9e64939959a071cb660090c0e1b380c51ca29e3cfb57f3f23ea42a9922eb6f41 |
| 构建规模 | meson 535 目标,clean-room 全量约 7 分钟 |
2.2 差异点(先列全,逐个在第三节展开)
| # | 差异点 | 上游假设 | 鸿蒙 PC 实际情况 |
|---|---|---|---|
| 1 | 显示服务 | Wayland/X11 显示服务器存在 | 无显示服务;GTK4 仅 Broadway 后端可用,broadwayd 受签名约束不可运行 |
| 2 | GLib 版本 | 测试代码用 2.86+ API(g_log_get_always_fatal) | 制品仓 glib/2.84.0.1.1,只有 2.80+ 的 setter |
| 3 | glib .pc 完整性 | glib-2.0.pc 自带 -lintl | PkgConfigDeps 生成只有 -lglib-2.0;1.8.x 新代码直调 g_libintl_*,--no-undefined 下必炸 |
| 4 | glib 二进制工具 | 系统工具可直接执行 | 裸 2.84.0.1 包的工具未签名(OHOS 上 “Operation not permitted”),须用 .1.1(已签名) |
| 5 | 依赖版本图 | appstream 钉 pango/1.50.14 + zstd/1.5.7.1 | 与 gtk4 链(pango/1.56.4 + zstd/1.5.7)不可共存的版本冲突 |
| 6 | 组件键引用 | 各配方组件键写法一致 | libxmlb 以无 ::gobject 后缀键引用 glib 组件 |
| 7 | appstream 获取 | subproject 源码 clone 可兜底 | 1.8.x 起为 required 依赖,离线 clone 必败 |
| 8 | 符号可见性 | 测试可用库的全部符号 | 测试链接内部静态库可用私有 API;消费者只能链接 .so 导出面 |
| 9 | 构建缓存 | 单用户干净缓存 | 同机多任务共享 conan 缓存,存在陈旧产物污染风险 |
三、适配过程
3.1 侦察(动手前需要检查的三项)
- 依赖发布检查(W0):对依赖链顶层逐个
conan download --only-recipe探测制品仓——gtk4/4.22.4、glib/2.84.0.1.1、appstream/1.2.0 均已发布,依赖链不断裂,可开工; - 构建系统识别:meson(GNOME 标准族),交叉注意点是它的
dependency()→ pkg-config 解析链和 subproject fallback 行为; - 测试策略:清点
tests/meson.build的test_names= 63 项,其中绝大多数创建 widget。策略定为"全量构建 + 全量实跑 + 证据留档 + 纯计算子集断言移植"——先把 63 个测试二进制全部编出来、全部跑一遍留证,再谈移植。
3.2 依赖树
libadwaita/1.8.8(meson,535 目标)
├── gtk4/4.22.4 主依赖(transitive_headers/libs;Broadway-only 后端)
│ └── 图内固定 pango/1.56.4、zstd/1.5.7、zlib/1.3.1.1、fontconfig/2.18.1.1
├── glib/2.84.0.1.1 shared + override(bin 工具已签名,OHOS 可执行)
├── appstream/1.2.0 1.8.x 新增 required(必须产出 .pc,阻断 subproject clone)
│ └── libxmlb/0.3.19 以无后缀键引用 glib 组件 → 键别名注入
├── pango/1.56.4 override(SONAME libpango-1.0.so.0 兼容 1.50.14 链接)
├── zstd/1.5.7 override(SONAME libzstd.so.1 兼容 1.5.7.1 链接)
├── fribidi/1.0.16 文本双向算法
└── sassc/3.6.2 build-time(样式编译)
3.3 坑 1:pango/zstd 双版本冲突(对应差异 #5)
- 现象:
conan create直接报版本冲突——appstream/1.2.0 配方钉 pango/1.50.14 + zstd/1.5.7.1(compose/vips 链),gtk4/4.22.4 链钉 pango/1.56.4 + zstd/1.5.7(meson 要求 pango ≥ 1.56),两图不可共存。 - 定位:对比两份配方的 requires 钉住版本,确认冲突是"两个已发布配方各钉各的",不是本包写错。
- 方案:主配方
self.requires("pango/1.56.4", override=True)+zstd/1.5.7override,统一图到 gtk4 链(不降版本——libadwaita 1.8 的 meson 硬性要求 pango ≥ 1.56)。 - 经验:override 前必须先确认预编译二进制的运行期符号。appstream 预编译包链接的是 1.50.14/1.5.7.1,之所以能跑在 1.56.4/1.5.7 上,是因为 pango 1.x 保持
libpango-1.0.so.0、zstd 1.x 保持libzstd.so.1的 SONAME。先查 SONAME 再 override,顺序反了就是埋雷。
3.4 坑 2:glib 组件键冲突(对应差异 #6)
- 现象:meson 依赖解析失败——appstream 的传递依赖 libxmlb/0.3.19 以无
::gobject后缀的键引用 glib 组件,而 conan 组件模型里只有带后缀的键。 - 定位:顺着报错找到 libxmlb 配方里
glib无后缀键的引用点;这是"传递依赖的键写法和当前 conan 组件模型不一致",不是 libadwaita 的问题。 - 方案:主配方
generate()注入 4 组键别名(glib::gobject→glib、glib::gmodule、glib::gthread、glib::gio),test_package 同款注入(消费者侧同样要解析这个图)。 - 经验:传递依赖引用"另一种写法"的组件键时,修复点在当前主配方的 generate()(图的所有权在这里),不要去改上游配方。
3.5 坑 3:g_libintl_* 链接失败(对应差异 #3)
- 现象:链接阶段
undefined reference to g_libintl_dngettext(meson 对库链接带--no-undefined,一个都藏不住)。 - 定位:
nm -D查 glib 包——libintl.so.8 里有 12 个 T(文本定义)的g_libintl_*符号;再查源码——1.8.x 新增的 adw-main.c / adw-tab-overview.c 直调g_libintl_dngettext(1.2.0 没有这些直调,所以旧版本幸免)。根因是 PkgConfigDeps 生成的 glib-2.0.pc 的 Libs 只有-lglib-2.0,缺-lintl。 - 方案:
_normalize_glib_pc()——在 PkgConfigDeps 之后强制回写 6 个 glib 系 .pc:prefix 统一指向 glib/2.84.0.1.1 包目录,glib-2.0.pc 的 Libs 补-lintl。glib 包自带 libintl.so.8,自洽闭环,不引入外部 libintl。 - 经验:PkgConfigDeps 产出的 .pc 是"按 conan 组件模型生成的",不一定等于上游 .pc 的完整 Libs。先 nm 目标包的真实符号面,再决定 .pc 要补什么——这是本包所有 .pc 修改的统一方法论。
3.6 坑 4:g_log_get_always_fatal() 链接错误(对应差异 #2)
- 现象:两个测试二进制(test-flap / test-preferences-window)链接失败:
g_log_get_always_fatal未定义。 - 定位:该 getter 是 GLib 2.86+ 才有的符号;ohpcd 的 glib/2.84.0.1.1 只有
g_log_set_always_fatal(2.80+,稳定 ABI:setter 内部强制|= G_LOG_LEVEL_ERROR并返回旧 mask)。 - 方案:唯一补丁
0001-tests-ignore-deprecations.h.patch(portability,+5/-1):测试辅助头里改用 setter 并捕获返回值作为"旧基线",与上游"抑制 ERROR-fatal 再恢复"语义等价。0 处平台宏(不是#ifdef __OHOS__分支,是版本兼容)。 - 经验:API 版本差只影响测试代码时,用 portability 补丁修测试文件,不动主库——补丁面最小、语义可对照、评审可核。
3.7 坑 5:GdkRGBA 成员名(20 个编译错误)
- 现象:消费者测试一次报 20 个编译错误——
GdkRGBA没有.r/.g/.b/.a成员。 - 定位:读 gtk4 头文件的结构体定义:成员名是
.red/.green/.blue/.alpha。 - 方案:改成员名(纯笔误级,20 个错误同源)。
- 经验:GTK4 相对 GTK3 的类型命名变化点(成员名、API 更名)要在写消费者测试前翻一次头文件确认,别按 GTK3 的肌肉记忆写。
3.8 坑 6:easing 端点断言失败(容差对齐,不是放宽)
- 现象:探针 B 段(easing 端点检查)2 条断言失败:
ease_in_elastic(0)与 0 偏差 -4.88e-4,ease_in_out_elastic(0)偏差 8.48e-5。 - 定位:把上游 adw-easing.c 的 elastic 族公式代 t=0 手算——该族公式没有 t==0 特判,端点值数学上就不是精确 0。再翻上游自己的 tests/test-easing.c:断言写法是
g_assert_cmpfloat_with_epsilon(..., 0, 0.005)——上游自己的容差就是 0.005。 - 方案:探针端点容差对齐上游契约 0.005。强调一下:这不是"把测试改松",是"和上游用同一把尺子"——如果库的端点偏差超过 0.005,上游自己的测试同样会挂。
- 经验:测试失败先查上游自己的断言容差/契约,"与上游同源"比"自己放宽"在评审上站得住得多。
3.9 坑 7:探针 SIGSEGV——误判自曝①
- 现象:探针 D 段(spring 物理参数)崩溃:SIGSEGV,fault @0x1,崩溃点在
g_object_run_dispose内部。 - 误判(自曝):第一反应是怀疑 gobject 链或平台内存子系统有问题——毕竟"释放一个对象就崩"很像环境损坏。这个方向查了半天没有新证据。
- 纠正:停手,做对照组实验——D0/D1 用真正的 GObject(
g_object_new创建、g_object_unref释放)走完全相同的释放路径:全部正常。对照成立,说明 glib/gobject 链本身健康,问题在测试代码自己:AdwSpringParams是 GBoxed(G_DEFINE_BOXED_TYPE+ gatomicrefcount),不是 GObject;对它调用g_object_unref()是未定义行为——gobject 把这段内存当 GObject 解读,内部字段被当弱引用 datalist 指针解引用,fault @0x1 这种极低地址正是"数据位模式被误读成指针"的典型签名。 - 方案:改用
adw_spring_params_unref(sp)。D 段 5 条断言(damping_ratio/mass/stiffness/派生量 damping == 2ζ√(mk) == 20.0)全过。 - 经验:① 写释放代码前先确认类型是 GObject / GBoxed / 裸 struct,三者 unref 路径完全不同;② “库坏了"还是"我写错了”,对照组实验是最快的裁决方式——比继续读库源码便宜一个数量级。
- 入档:本案已入档知识草稿 E429(错误签名
SIGSEGV|g_object_run_dispose|fault @0x|GBoxed,含对照组结论),条目见第五节。
3.10 坑 8:.so 行为偏离源码——误判自曝②
- 现象:探针实测的 easing 值与源码公式手算对不上;但源码 md5 一致、.so 反汇编与公式一致——“代码对、结果错”。
- 误判(自曝):先怀疑源码被改过(查 md5)、怀疑编译产物异常(反汇编逐条对公式)——都排除了,一度陷进"平台浮点行为差异"的假设里打转。
- 纠正:换个问题问自己——“运行的到底是不是我刚编出来的 .so”。一查 conan 缓存:这台机器同机多任务共享缓存,09-14 的旧会话残留了 libadwaita/1.8.8 的旧缓存条目(旧 recipe revision / 旧源码),conan 目录名哈希碰撞导致陈旧 build/package 目录被复用,探针加载的是陈旧 .so。
- 方案:
conan remove "libadwaita/*" -c全清 + clean-room 重建,实测值与公式完全一致。 - 经验:① 共享缓存机器上,正式构建前先清本包旧条目;② “反汇编一致但运行不一致"这个签名,第一嫌疑永远是"实际加载的产物来源”,而不是浮点/平台行为。
- 入档:本案已入档知识草稿 E430;其中一条工具层细节特别值得注意——conan 不会重新校验已存在 source 目录的 sha256(只校验下载时刻)——这正是陈旧目录能被长期复用的工具层根因。
3.11 坑 9:appstream subproject 全源码 clone(对应差异 #7)
- 现象:meson setup 阶段开始 clone appstream 的全部源码(离线环境必败,且拖死构建)。
- 定位:1.8.x 起 appstream 从可选变成 required 依赖(meson 里无
required:false,且带 subproject fallback)——PkgConfigDeps 没产出 appstream.pc,meson 的dependency("appstream")就落到 fallback 分支去 clone。 - 方案:appstream/1.2.0 制品仓已发布,PkgConfigDeps 产出 appstream.pc 后
dependency()直接命中,fallback 被阻断。 - 经验:meson 的 subproject fallback 是个"静默陷阱"——它不报错,只是开始下载。GNOME 系配方里凡看到构建阶段出现 git clone,先查哪个
dependency()没被 .pc 满足。
3.12 坑 10:gtk_doc 选项 deprecated
- 现象:meson 配置报
gtk_doc选项错误。 - 定位:1.8 起该选项 deprecated,查 meson_options.txt 确认替代项。
- 方案:
-Ddocumentation=false(本任务不产出文档)。 - 经验:GNOME 系构建选项随大版本轮换,选项报错先翻
meson_options.txt,不要凭旧版本记忆传参。
3.13 构建结果
10 个坑清完后,conan create 全流程:
conan create archives/l/libadwaita/1.8.8/conanfile.py --build=missing
→ meson 535 目标(含全部 63 个上游测试二进制)0 FAILED,约 7 分钟(clean-room)
→ test_package: libadwaita-1.8.8 no-display probe: checks=124 PASS
(105/105 upstream-ported assertions included)
→ 产物:libadwaita-1.so、libadwaita-1-internal.a、85 个头文件(OHOS 签名)

图1:真机 conan create 完整构建 + 消费者测试全过(证明:配方在真机环境可构建、124/124 全过;不能证明:下游可消费性、视觉渲染)
四、验证(分层,禁止相加)
口径声明:下面 5 层分别统计、分别陈述,不做任何相加或合并。“63 实跑”、“105 移植”、"124 消费者"是三个不同口径的数字,混在一起说就是造假。
4.1 上游测试实跑(R51 三步法台账)
- 清点:
tests/meson.build的test_names列表 = 63 项,全部构建成功(535 目标 0 FAILED); - 实跑:63/63 执行(env:
GSETTINGS_BACKEND=memory GTK_A11Y=none,每测试 10s 超时); - 通过:0;
- 失败模式(63/63 同因,非断言失败):
gtk_test_init → gtk_init →
Gtk-WARNING: Failed to open display →
g_log fatal → __builtin_trap() → SIGTRAP(rc=-5,注意不是 exit 1)
- 分类:63 项中 61 项创建 widget(display 依赖,无显示服务下无法执行);2 项纯计算(test-easing / test-accent-color,见 4.2/4.3);
- 次要环境问题(非致命,如实记录):
Fontconfig error: Cannot load default config file: File not found——fontconfig/2.18.1.1 包缺默认 fonts.conf,显示链路可用前需处理; - 证据(已入仓,可复现):
archives/l/libadwaita/1.8.8/upstream-tests/——summary.txt(63 行,一行一测试:rc + 首行关键输出)+ 63 份 .out 全量原始输出 + run-upstream-tests.py 复现 runner + README.md。runner 对任意含build-release/tests/的构建目录一条命令重跑;找不到测试二进制时报错退出(exit 1),不会静默空跑。
4.2 断言 1:1 移植执行(可移植子集 = 公共 API 断言)
63 项里只有 2 项是纯计算测试(无 widget、无显示依赖),它们的公共 API 断言已逐条移植到消费者测试(test_package/test.c)中无显示执行:
| 上游测试 | 断言构成 | 移植执行结果 |
|---|---|---|
| test-easing | 35 个 AdwEasing 值 × f(0)≈0 / f(1)≈1,共 70 条 | 70/70 PASS |
| test-accent-color | to_rgba 精确 hex 9 + to_standalone_rgba 18 + rgba_to_standalone 8,共 35 条 | 35/35 PASS |
| 合计 | 105 条公共 API 断言 | 105/105(100%)PASS |
移植是断言级 1:1(数值、容差、判定方向逐条对照上游源文件,test.c 内每条断言注释了上游行号),例如 easing 端点:
/* 上游 test-easing.c:g_assert_cmpfloat_with_epsilon (adw_easing_ease (e, 0), 0, 0.005) */
ok &= check(fabs(adw_easing_ease(e, 0.0)) <= 0.005, "easing f(0)~=0 (eps 0.005)");
口径边界(这里比较重要):这是"上游测试的公共 API 断言"在无显示环境被执行,不是"上游测试二进制"被执行(那些二进制因 gtk_test_init 前置 display 初始化而无法启动)。两个口径在本文和 PR 里始终分开陈述。
4.3 私有 API 137 条:分类留证,不冒充移植
上游 test-accent-color.c 另有 137 条断言(nearest_from_rgba roundtrip 10 + 各桌面调色板 127)调用 adw_accent_color_nearest_from_rgba()。这个函数:
- 仅声明于私有头
src/adw-accent-color-private.h,公共头 adwaita/ 目录无此声明; - 未从 libadwaita-1.so 导出——
nm -D --defined-only libadwaita-1.so.0 | grep nearest输出为空;同一编译单元里的兄弟函数(to_rgba / to_standalone_rgba / rgba_to_standalone)均正常导出; - 上游测试二进制之所以能用它:meson 把 tests 链接到内部静态库 libadwaita-1-internal.a(全部符号、无可见性过滤)。
因此这 137 条消费者上下文不可执行(消费者只能链接已安装的 .so)。处理:不入移植数字、不做"通过"声明,按"私有 API 子集"分类留证(函数名 + 私有头路径 + nm -D 证据 + 上游可用的原因),随 63/63 台账一并入仓。为什么不让测试去链接内部 .a:那不是真实消费场景,且内部 .a 的符号面不代表公共接口——那样做会让"测试通过"的声明失真。
4.4 消费者测试(124 checks)
test_package/test.c 共 124 项 checks,ok 累积决定进程退出码(任一项失败即非零退出,CI 可拦截):
| 段 | 内容 | 数量 | 级别 |
|---|---|---|---|
| A | 版本/初始化态:adw_get_major/minor/micro_version、版本宏、ADW_CHECK_VERSION 正/反、adw_is_initialized()==FALSE | 7 | L1 |
| B | easing:上游 1:1 移植 70 + 解析值/单调性扩展 7 | 77 | L2 |
| C | accent-color:上游 1:1 移植(公共 API)35 | 35 | L2 |
| D | spring 物理参数:new + damping_ratio/mass/stiffness + 派生量 damping == 2ζ√(mk) == 20.0(GBoxed,adw_spring_params_unref 释放) | 5 | L2 |
实测输出:
libadwaita-1.8.8 no-display probe: checks=124 PASS (105/105 upstream-ported assertions included)
L1/L2 声明:A 段(版本断言)属 L1 级别,单独不能宣称功能完成;实质功能验证由 B/C/D 段承担(真实 API + 数值结果断言,L2)。D 段的
2ζ√(mk)是 spring 模型的物理定义式,属于"结果断言"而非"能跑就算"。

图2:消费者测试二进制直接运行(证明:交付物可独立执行且 124/124 全过;不能证明:功能面完整、视觉渲染)
4.5 产物核验
| 项 | 结果 |
|---|---|
| 构建目标 | meson 535 目标 0 FAILED(含 63 个上游测试二进制) |
| 库产物 | libadwaita-1.so(动态)、libadwaita-1-internal.a(内部静态,供上游测试链接) |
| 头文件 | 85 个(公共 API 面) |
| 符号面 | nm -D 核验:公共 API 符号齐全;私有 API(如 nearest_from_rgba)不导出(4.3 的实证基础) |
| 签名 | OHOS 共享库签名(配方 _sign_shared_libs) |
| 构建耗时 | clean-room 全量约 7 分钟;warm 缓存(包命中、仅重编测试)约 2 分钟 |
4.6 CI 与评审(完整轮次记录,以及失败轮)
| 轮次 | 构建号 | head | 结果 | 说明 |
|---|---|---|---|---|
| 1 | #63822 | 3bdd8cc9 | ✅ 通过 | conan-build-test / L0 / L1 全绿 |
| 2 | #63893 | 4b021501c | ⚠️ 作废 | worker CI-021 OOM:zsh:1: fork failed: out of memory,连 git --version 都 fork 不出——基础设施故障,与代码无关,重触发 |
| 3 | #63909 | aed5e885 | ✅ 通过 | 重触发后全绿(换 worker) |
| 4 | #66403 | 85273d54 | ✅ 通过 | 测试强化提交(124 checks + 证据链入仓)后全绿 |
检视记录:平台 AI 检视曾多次基础设施故障(“审查未能完成”);另做了一轮本地 deep review(correctness/security/performance/tests_contracts 四维度),9 条发现(P2×3 + P3×6)全部处置——runner 参数化 + 空跑保护(exit 1)、README 复现命令修正、test.c include 显式化;检视报告已发 PR 讨论区。
合并:PR #13574 经评审(Clancy_Xie)批准合入 main;关联 Issue #3562(【高难度挑战】候选申请)自动关闭。

图3:PR #13574 已合并页
4.7 复现速查
以下 3 条命令可在同环境复现本文全部核心数字(命令 1、2 于本文发布前一日复核可运行;实跑 env 由 runner 内部固定为实测值):
cd <仓库根目录 build_in_harmonyos>
# 1) 全量构建 + 消费者测试:期望 checks=124 PASS,Ok: 1 / Fail: 0
conan create archives/l/libadwaita/1.8.8 --build=missing
# 2) 复跑上游 63/63 全量证据链:BUILD_DIR 为含 build-release/tests/ 的 conan 构建根目录;runner 找不到测试二进制时以非零码退出,不会静默空跑
python3 archives/l/libadwaita/1.8.8/upstream-tests/run-upstream-tests.py <BUILD_DIR>
# 3) 符号面核验:期望输出为空(私有符号未导出,4.3 的 137 条分类依据)
nm -D --defined-only <已安装 libadwaita 包目录>/lib/libadwaita-1.so.0 | grep nearest
口径对应:命令 1 → 3.13/4.4(124),命令 2 → 4.1(63/63),命令 3 → 4.3(137 分类)。读者可尝试复现(emm。。
五、知识沉淀(4 条草稿,随 PR 入仓)
| 编号 | 知识 | 来源坑位 | 复用价值 |
|---|---|---|---|
| E429 | 对 GBoxed 调用 g_object_unref 致 SIGSEGV——类型混淆易被误判为 gobject 链损坏;对照组实验是裁决手段 | 坑 7 | 所有 GLib 系 C 库的探针编写(GObject/GBoxed 判别) |
| E430 | 共享 conan 缓存陈旧 .so 污染——"反汇编一致但运行不一致"先查实际加载产物来源;clean-room 重建定案 | 坑 8 | 多任务并行共享缓存的所有构建环境 |
| E431 | PkgConfigDeps 生成的 glib-2.0.pc 缺 -lintl 致 g_libintl_* 链接失败(–no-undefined);先 nm 目标包真实符号面再补 .pc | 坑 3 | 所有依赖 glib 且代码直调 intl 函数的包 |
| E432 | 上游测试链接内部静态库可用私有符号——消费者移植测试断言前须先 nm -D 过滤已导出符号(公私二分如实申报) | 4.3 节 | 所有"上游测试可用、公共 API 不可用"符号的移植场景(GNOME/GTK 系普遍存在 private 头) |
草稿状态说明:4 条均为 .kv + .md 配套草稿,随 PR 入仓、待主编 promote 为正式条目(走"草稿→审核→正式"知识生命周期);编号 E429-E432 与正式库无冲突(graph validate 通过)。
六、FAQ
Q1:上游 63 个测试 0 通过,合理吗?
合理,且已证明是平台阻塞而非断言失败:63/63 同因 SIGTRAP(gtk_init “Failed to open display”),rc=-5(trap 信号),不是 exit 1(断言失败退出码)。全量证据链已入仓(summary.txt + 63 份 .out + 复现 runner),任何人可在同类环境一条命令复跑。"0/0 没跑"和"63/63 全跑了但被平台卡死"是两回事——本文是后者。
Q2:105/105 移植断言,算"上游测试通过"吗?
不算,本文从不这样声称。准确口径:上游 2 项纯计算测试的公共 API 断言子集在消费者上下文 1:1 执行通过。上游测试二进制本身因 display 前置初始化无法启动(见 4.1)。两个口径全文分开陈述,PR 与 Issue 同口径。
Q3:137 条私有 API 断言为什么不"想办法"跑一下?
技术上可以(链接内部 .a),但那是伪消费场景:内部 .a 的符号面不代表公共接口,消费者拿不到它。跑了也只能证明"内部实现如此",不能证明"库对消费者可用"。如实分类留证(4.3)。
Q4:124 项消费者 checks 是不是空壳测试(print 一下就算过)?
不是。判定标准:B/C/D 段是真实 API 调用 + 数值结果断言(easing 数学值、颜色 hex 精确值、spring 物理定义式 2ζ√(mk)=20.0),任何一条数值不对进程即非零退出(ok 累积,CI 可拦截)——这是 L2 级验证。A 段版本断言是 L1 级,已单独声明"不能宣称功能完成"。test.c 全文在 PR 中可逐行复核。
Q5:libadwaita 是 UI 库,核心不是渲染吗?没验证渲染算适配完成吗?
不算渲染完成,本文也这么写(诚实声明)。当前可用面 = 无显示 API 子集(版本/easing 数学/颜色换算/spring 物理参数,已验证 124/124);widget 渲染属显示链路可用后的下一阶段(届时上游 63 测试全集 + 视觉走查覆盖)。配方层面(依赖解析/构建/产物/签名/消费者链接)已完整,这是"库已就位、渲染待显示服务"的明确分界,而非"验证通过"的模糊声明。
七、总结
7.1 量化盘点
| 维度 | 数字 |
|---|---|
| 补丁 | 1 个(portability,+5/-1,0 处平台宏) |
| 适配问题 | 10 个(含 2 处误判纠正:对照组排除法 / 缓存污染 clean-room) |
| 依赖解析 | 9 项(3 组 override + 组件键别名 + .pc 回写 + subproject 阻断) |
| 构建 | meson 535 目标 0 FAILED,约 7 分钟(clean-room) |
| 上游测试 | 63 清点 / 63 实跑 / 0 通过(63/63 display 阻塞 SIGTRAP,证据链入仓) |
| 断言移植 | 105/105 公共 API 断言 1:1 移植执行全过(70 + 35) |
| 私有 API | 137 条分类留证(nm -D 实证,不冒充移植) |
| 消费者测试 | 124/124 checks(L1×7 + L2×117) |
| 知识沉淀 | 4 条草稿(E429-E432) |
| CI | 4 轮(3 绿 + 1 基础设施故障作废);PR 已合入 main |
7.2 可复用方法(4 条)
- display 依赖测试的三级分类 + nm -D 第一闸:上游测试先分 display 依赖 / 纯计算-公共 API / 纯计算-私有 API 三级;移植前
nm -D过滤导出符号,公私二分如实申报(本文 63 = 61 + 70/105 口径 + 137 留证); - "与上游契约对齐"的容差原则:测试失败先翻上游自己的断言容差(本文 eps 0.005 同源),"同源"比"放宽"可辩护;
- 对照组裁决法:崩溃/异常先建最小对照组(真 GObject vs 疑点类型、新构建 vs 缓存产物),再决定查库还是查自己——两处误判都是靠它止损的;
- R51 证据链入仓:测试台账 = 清点 + 实跑 + 逐项原始输出 + 可复现 runner,全部进仓库——评审看到的不是"我说 63/63",是"63 份 .out 在仓里"。
7.3 流程 checklist(无显示 UI 库适配通用)
- 依赖发布检查(conan download 逐个探测)再动手
- 清点上游测试清单,预判 display 依赖占比,定"实跑 + 证据 + 移植"策略
- .pc 修改前先 nm 目标包真实符号面
- 消费者测试分 L1/L2 级,L1 段带"不能宣称功能完成"声明
- 上游证据链(.out 全量 + runner)入仓
参考链接
- PR(已合并):https://atomgit.com/OpenHarmonyPCDeveloper/build_in_harmonyos/merge_requests/13574
- Issue(已关闭):https://atomgit.com/OpenHarmonyPCDeveloper/build_in_harmonyos/issues/3562
- 仓库:https://atomgit.com/OpenHarmonyPCDeveloper/build_in_harmonyos
- 上游源码:https://mirror.nju.edu.cn/gnome/sources/libadwaita/1.8/libadwaita-1.8.8.tar.xz
更多推荐



所有评论(0)