开源鸿蒙PC三方库实战与复现:xdg-desktop-portal 依赖树全解与方法论总结

欢迎加入开源鸿蒙 PC 社区:https://harmonypc.csdn.net/

开源仓库地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_xdg_desktop_portal_deps

本篇基于社区作者 程序山海 的《https://bigstar.blog.csdn.net/article/details/161798745》整理,结合我自己的理解重新组织。依赖关系图复用自原作者公开素材,致谢。

为什么用它

走到这里,系列里已经讲过 CMake、Meson、Makefile 三套构建系统,讲过 musl 兼容、强制签名、rpath、解压权限等一系列鸿蒙特性。xdg-desktop-portal 的依赖树恰好规整而完整,能把「多层依赖」这件事讲到极致。用它收尾、做一次系统化复盘,再合适不过——既是对上一篇的延续,也是对全系列的收束。
在这里插入图片描述

理解依赖树为什么这么重要

在动手拆树之前,先说清楚「为什么非得先理解依赖树」。在 C/C++ 项目里,多层依赖是最常见的架构,而理解它的重要性体现在四个直接后果上:

  1. 构建顺序决定成败:必须自底向上,否则上层编译时依赖库根本不存在;
  2. pkg-config 路径要逐层传递:每个库的 pkgconfig 路径必须正确传给依赖它的上层;
  3. 动态库加载问题:rpath 配置不当,运行时就 symbol not found
  4. 权限问题规避:鸿蒙的文件权限限制,需要靠特殊目录结构(libexec)来绕。

这四点,恰好串起了前面几篇的所有经验。

完整依赖树拆解

xdg-desktop-portal 的依赖分两层(不算目标本身),外加最底下的基础库:

目标库:xdg-desktop-portal(桌面门户守护进程)
├── 直接依赖(6 个):glib, fuse3, json-glib, gdk-pixbuf, gstreamer, pipewire
│
├── glib       → 依赖 libffi, pcre2
├── fuse3      → 无额外依赖(但需 musl 补丁)
├── json-glib  → 依赖 glib
├── gdk-pixbuf → 依赖 glib, libpng
├── gstreamer  → 依赖 glib
└── pipewire   → 依赖 glib
│
基础依赖:libffi, pcre2, libpng

构建顺序还是那条铁律——自底向上,逐层构建

第 1 步:libffi, pcre2, libpng(基础,互相独立,可并行)
          ↓
第 2 步:glib, fuse3, json-glib, gdk-pixbuf, gstreamer, pipewire(直接依赖)
          ↓
第 3 步:xdg-desktop-portal(目标)

第 1 层:基础依赖库

这三个库处在依赖树的最底层,自己几乎不依赖别人(或只依赖系统库),却被上层广泛使用。

libffi —— 外部函数接口库

提供便携接口,让 C/C++ 在运行时动态调用其他语言的函数(glib 的某些机制要用它)。

  • 依赖:无
  • 关键点:用 configure + make;加 --disable-multi-os-directory;host 设为 aarch64-linux-ohos

眼熟吗?第九篇 JNA 也用到了 libffi。只不过那次因为老版本 configure 不认识 ohos,借用了 android 目标。可见同一个基础库在不同上层项目里反复出现,啃过一次后面就省力。

pcre2 —— 正则表达式库

高性能正则库,提供与 Perl 5 兼容的功能。glib 的 GRegex 就依赖它。

  • 依赖:无
  • 关键点:启用 UTF-8/16/32 和 Unicode 属性支持,否则上层用到相关功能会缺。

libpng —— PNG 图像处理库

官方 PNG 参考库,gdk-pixbuf 加载 PNG 要用。

  • 依赖:zlib
  • 关键点:需要显式 export CPP 指定 C 预处理器,避免构建系统误用 C++ 编译器导致诡异错误。

基础层构建

cd lycium_plusplus/lycium
./build.sh libffi
./build.sh pcre2
./build.sh libpng    # 会自动带上 zlib

libffi 和 pcre2 完全独立,libpng 依赖 zlib,三者可以并行编。它们必须都在 glib 之前完成。

在这里插入图片描述

第 2 层:直接依赖库

glib —— 基础工具库(核心中的核心)

事件循环、数据结构、对象系统……glib 是整个 GNOME 系的地基,这棵树里好几个库都依赖它。

  • 依赖:libffi, pcre2
  • 关键点:要下子模块(源码不全编不动);Meson 选项里 -Dlibmount=disabled -Dtests=false 关掉用不上的部分。

fuse3 —— FUSE 文件系统库

xdg-document-portal(文档门户)必需。

  • 依赖:无
  • 关键点:要打 musl 补丁处理 pthread_cancel-Duseroot=false -Dutils=false

注意这个 pthread_cancel 的 musl 补丁——这正是第一篇 MediaInfo 里 ZenLib 的同款问题!musl 不支持 pthread_cancel,所以要么打补丁注释掉,要么定义个兼容宏(如 #define pthread_cancel(tid) ENOSYS)。鸿蒙适配的坑高度可复用,这是又一个铁证——啃过一次,再遇到就是肌肉记忆。

json-glib —— JSON 处理库

配置文件解析用。

  • 依赖:glib
  • 关键点:depends=("glib"),cross file 里设好 pkg_config_path 指向 glib;-Dintrospection=disabled -Dtests=false

gdk-pixbuf —— 图像加载库

截屏功能要用。

  • 依赖:glib, libpng
  • 关键点:-Dintrospection=disabled -Dbuiltin_loaders=all(把图像加载器都内建进去,避免运行时动态加载缺失)。

gstreamer —— 多媒体框架

屏幕录制要用。

  • 依赖:glib
  • 关键点:禁用所有插件和额外依赖,只编核心,否则又会拖出一大堆依赖。

pipewire —— 音视频服务

流媒体传输用。

  • 依赖:glib
  • 关键点:大量系统集成功能要 disable(-Dsystemd=disabled -Dselinux=disabled 等),因为鸿蒙没有这些。

第 3 层:目标库 xdg-desktop-portal

到这一步,要把前面所有成果整合:

  • depends=("glib" "fuse3" "json-glib" "gdk-pixbuf" "gstreamer" "pipewire")
  • 配两个 Meson 配置文件:ohos-native.ini(给构建机上跑的 glib 工具用)和 ohos-cross.ini(交叉编译用);
  • pkg_config_path 串进全部依赖库的路径;
  • -Dc_link_args="-Wl,-rpath,\$ORIGIN" 注入相对 rpath;
  • archive() 把所有依赖 .so 复制到 libexec/ 目录(规避权限 bug)。

这里出现了一个上一篇没细说的细节:native file 和 cross file 的区分。glib 在构建过程中需要一些工具(如 glib-compile-resources)在**构建机(x86)上运行,而最终库要给目标机(arm64)**用。这两者的工具版本和库版本不能混,所以要分别用 native file 和 cross file 描述。这是复杂 glib 系项目特有的讲究。

两个支撑性的硬核机制

原文专门深挖了两个机制,我认为它们是多层依赖适配的「内功」。

内功一:pkg-config 路径治理

交叉编译里 pkg-config 是依赖查找的中枢。我总结的几条铁则:

  • 每个库编完会在自己安装目录生成 .pc 文件,里面写明「头文件在哪、库在哪、还连带依赖谁」,上层全靠读它;
  • 交叉环境别信自动探测,老老实实把所有相关 .pc 目录串进 PKG_CONFIG_PATH
  • 注意不同库的 pkgconfig 目录位置可能不一致(有的 lib/pkgconfig,有的 usr/lib/pkgconfig),逐个核对;
  • 注意路径顺序——同名 .pc 出现在多处时,顺序会影响最终用哪个;
  • glib 这类要分清「构建机工具版本」和「目标机库版本」,别让 native 和 cross 串味。

内功二:rpath 与 musl 动态链接器行为

  • $ORIGIN 相对路径让产物可移植、不依赖系统路径,是鸿蒙库查找的标准解;
  • 鸿蒙文件系统权限约束(解压后目录权限异常 770)要靠 Python tarfile 解压 + 修权限 + 放 libexec 来规避;
  • musl 的动态链接器行为和 glibc 有细微差异,遇到加载问题时要意识到「这不是标准 glibc 环境」。

全系列方法论总结

写到收官,我把这十一篇沉淀的东西归成五条,作为以后适配任何鸿蒙三方库的「行动清单」。

一、构建系统三选一,套路各异

构建系统 核心套路 本系列案例
CMake cmake -S 子目录 -DOHOS_ARCH,开关控制依赖 MediaInfo、G’MIC
Meson 核心是写对 cross file(工具链 + pkg_config_path) libplacebo、mpv、xdg-desktop-portal
Makefile 警惕环境变量(LDFLAGS)覆盖默认值 JNA

二、鸿蒙平台特性,必踩的几个

  • musl 兼容:缺的函数(如 pthread_cancel),用不上就打补丁注释/定义兼容宏(MediaInfo、fuse3 都遇到);
  • 强制签名binary-sign-tool sign,可塞进构建 POST_BUILD 自动化(CodeArts 篇);
  • rpath:统一用 $ORIGIN 解决运行时库查找(CodeArts、xdg 篇);
  • 解压权限 bug:用 Python tarfile 绕过系统 tar,依赖放 libexec(xdg 篇)。

三、多层依赖,方法固定

  • 先画依赖树 → 自底向上逐层编 → 每层验证 pkgconfig → 串好 PKG_CONFIG_PATH;
  • 排查「找不到 X」永远是沿树往下定位「断在哪一层」,而不是在原地猜(mpv、xdg 篇)。

四、环境基本功,每篇都用

  • CRLF → LF:Python 二进制转换,治 $'\r': command not found
  • 清构建缓存:删 hpk_build.csv + 旧产物,否则 lycium 跳过构建;
  • 清 Windows 元数据:删 :Zone.Identifier 文件。

这三件事看着琐碎,却是几乎每个库都要做的「开机动作」。

在这里插入图片描述

五、心态与目标

  • 做减法优先:用不上的特性、后端、依赖,先关掉编通再说,别一上来全开(G’MIC 的 OpenMP、mpv 的图形后端、JNA 的 JAWT 都是例子);
  • 验证要分层:能编 ≠ 能加载 ≠ 能完整运行。复杂系统服务(如绑定 D-Bus/systemd 的 xdg-desktop-portal)要实事求是看适配目标,阶段性成果也有价值。

结语

这个系列从一个小巧的 MediaInfo 起步,一路走过 DevEco/CodeArts 的调用集成、HPKBUILD 配方编写、Meson 交叉编译,再到 mpv、xdg-desktop-portal 这样的复杂多层依赖项目,最后落到跨语言的 JNA——把鸿蒙 PC 三方库适配的主要场景和坑都趟了一遍。

回头看,这些库虽然领域各异、构建系统不同,但底层的方法论是高度一致的:理清结构、自底向上、做减法、串好路径、处理好鸿蒙的几个平台特性、分层验证。掌握了这套,再去碰新的库,就不再是「从零摸索」,而是「按图施工」。

希望这份梳理能帮到同样想给鸿蒙生态添砖加瓦的同学。最后,由衷感谢三位原创适配作者的开源分享——wei_shuoDream-Y.ocean程序山海。正是他们把一手实践公开出来,后来者才能站在肩膀上少走弯路。也欢迎大家加入开源鸿蒙 PC 社区,一起把生态做厚、做实。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐