开源鸿蒙PC三方库实战与复现:迁移 Linux 桌面门户服务 xdg-desktop-portal

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

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

xdg-desktop-portal。它依赖链长、还涉及鸿蒙特有的文件权限和动态库加载问题,是检验适配功力的好题目。

本篇基于社区作者 程序山海 的《https://bigstar.blog.csdn.net/article/details/161798885》整理,结合我自己的复现重新讲述。验证截图复用自原作者公开素材,致谢。

它是干什么的,为什么难

xdg-desktop-portal 是 Flatpak 社区开发的桌面门户守护进程。它的作用是:给沙箱化应用(Flatpak、Snap 这类被隔离起来、不能直接乱碰系统的应用)提供一套标准化的 D-Bus 接口,让它们能安全可控地访问系统资源——文件选择器、屏幕录制、通知、打印、位置、摄像头/麦克风访问等等。简单说,它是沙箱应用和真实系统之间的「受控中介」。

它的难度有两层,而且这两层都很硬:

第一层:依赖极其复杂。 它直接依赖 glib、fuse3、json-glib、gdk-pixbuf、gstreamer、pipewire 六个库,而 glib 自己又依赖 pcre2、libffi……这又是一棵比 mpv 还规整的多层依赖树(下一篇会完整拆解)。

第二层:平台差异巨大。 它深度绑定 D-Bus 系统服务和 systemd 用户会话,而鸿蒙的系统架构跟标准 Linux 桌面完全不同。这意味着——即便编译通过,它也未必能在鸿蒙上原样运行起来提供完整门户功能

所以这次我把适配目标定得很务实:先验证能在鸿蒙上完整交叉编译出 arm64 产物,跑通整条复杂依赖链和打包流程。至于运行时的完整门户能力,那需要鸿蒙原生层另做对接实现,不在本次范围。适配工作要实事求是地分层看待目标,这点很重要。

项目 信息
xdg-desktop-portal
版本 1.21.2
协议 LGPL-2.0-or-later
构建系统 Meson + Ninja
直接依赖 glib, fuse3, json-glib, gdk-pixbuf, gstreamer, pipewire
架构 arm64-v8a

在这里插入图片描述

这一篇最有价值的两个新知识点

依赖树的处理上一篇 mpv 已经讲透了(下一篇还会就这棵树做完整拆解),这里我重点说两个 mpv 没碰到的、极具鸿蒙运行时特色的问题。这两点属于「能编出来」之后、「能加载起来」要补的课。

知识点一:rpath 用 $ORIGIN 解决「找不到库」

问题背景

在标准 Linux 上,程序运行时要找它依赖的 .so,常见做法是设 LD_LIBRARY_PATH 环境变量指向库目录。但在鸿蒙 PC 上,由于文件系统权限的限制,这个环境变量经常不生效——你设了也白设,程序还是报找不到库。

解法:把库查找路径「焊」进二进制

我的解法是给二进制注入相对 rpath,用 $ORIGIN 这个特殊变量:

# Meson 链接参数
-Dc_link_args="-Wl,-rpath,\$ORIGIN"

$ORIGIN 是 ELF 动态链接器认识的特殊占位符,代表「可执行文件自己所在的那个目录」。注入之后,程序运行时会自动去自己的同级目录找依赖库,完全不依赖任何环境变量。

这么做的好处是可移植:部署时只要把可执行文件和它的依赖 .so 放在同一个目录里,整包搬到哪都能跑,不会断链。

这其实和第三篇 CodeArts 里用的 BUILD_RPATH "$ORIGIN" 是同一个思路——可见 $ORIGIN 是鸿蒙这边解决运行时库查找问题的「标准答案」,几乎每个有依赖库的产物都会用到它。

在这里插入图片描述

知识点二:鸿蒙解压 tar 会改坏权限

问题现象(极其隐蔽)

这个坑非常坑。我在构建机上打好的 tar 包,库文件权限都正常。但拷到鸿蒙系统、用系统自带的 tar 命令解压后,.so 文件的权限被强制改成了 770。结果动态链接器去加载这些库时,因为权限不对而失败——而你完全想不到是「解压」这一步搞的鬼。

绕过办法

两招配合:

  1. 别用鸿蒙系统的 tar 解压,改用 Python 的 tarfile 模块来解,解完再手动把权限修正回正常值(如 755):
import tarfile, os
with tarfile.open("xdg-desktop-portal_1.21.2-with-deps.tar.gz") as t:
    t.extractall(".")
# 解压后修复 .so 权限
for root, dirs, files in os.walk("usr"):
    for f in files:
        if f.endswith(".so") or ".so." in f:
            os.chmod(os.path.join(root, f), 0o755)
  1. 把依赖库放进 libexec/ 目录。这是原作者的一个巧思——libexec/ 这个目录的权限在鸿蒙解压后能保持 755,天然规避了那个 770 的 bug。所以 archive 阶段我会把所有依赖 .so 收拢到 libexec/ 下。

产物的组织:带不带依赖的取舍

这次打了三个产物,体现了「自包含 vs 精简」的不同取向:

产物 内容 适用场景
xdg-desktop-portal_1.21.2-with-deps.tar.gz 主程序 + 全部依赖 .so 开箱即用,部署省心
xdg-desktop-portal_1.21.2.tar.gz 仅主程序 系统已有依赖时用,省空间
xdg-desktop-portal.hnp 鸿蒙标准安装包 走 HNP 生态分发

archive() 阶段会把所有依赖 .so 收拢到 libexec/ 一起打进 with-deps 包,配合前面的 $ORIGIN rpath,部署时就不会缺库——这两个机制是配套使用的。

hnp.json

{
    "type": "hnp-config",
    "name": "xdg-desktop-portal",
    "version": "1.21.2",
    "description": "Desktop integration portal for sandboxed applications",
    "license": "LGPL-2.0-or-later",
    "arch": "arm64-v8a",
    "install": {
        "bin": ["usr/libexec/xdg-desktop-portal", "usr/libexec/xdg-document-portal"],
        "lib": ["usr/lib/*.so*"],
        "share": ["usr/share/dbus-1/services", "usr/share/xdg-desktop-portal"]
    }
}

注意 bin 入口指向的是 usr/libexec/ 下的可执行文件——正是为了配合上面说的权限规避策略。

验证

# 用上面的 Python 方式解压(避免权限 bug)
# 进入 libexec
cd usr/libexec/

# 鸿蒙强制签名
binary-sign-tool sign -inFile xdg-desktop-portal -outFile xdg-desktop-portal -selfSign "1"
chmod +x xdg-desktop-portal

# 验证架构
file xdg-desktop-portal          # 期望 ARM aarch64

# 验证 rpath 和依赖(关键)
readelf -d xdg-desktop-portal
# 重点看两行:
#   RUNPATH: $ORIGIN        ← 库查找路径配对了
#   NEEDED: 各依赖 .so       ← 依赖列表是否完整

readelf -d 里能看到 RUNPATH: $ORIGIN 和完整的依赖列表,就说明库查找路径和依赖打包都对了。

在这里插入图片描述
在这里插入图片描述

常见问题速查(FAQ)

现象 根因 解决
运行时 symbol not found / cannot open shared object rpath 没配对 $ORIGIN 注入 rpath
解压后 .so 权限变 770 鸿蒙系统 tar 的 bug Python tarfile 解压 + 修权限
依赖库为什么要放 libexec 规避 770 权限问题 该目录解压后保持 755
Meson dependency not found pkg-config 路径不全 串全所有依赖的 pkgconfig 路径
复制依赖库 No such file or directory 依赖实际安装路径不符 确认真实安装路径再复制
lycium 提示 dependency not completed 依赖没标记完成 在 hpk_build.csv 标记
wrap-file download failed Meson 想联网下子项目 --wrap-mode=nodownload 或手动下载
编译找不到头文件 头文件路径/pkgconfig 没配 检查并补 -I

小结

xdg-desktop-portal 这一篇,把两个鸿蒙运行时特有的硬骨头讲明白了:

  1. $ORIGIN rpath —— 解决运行时库查找,绕开失效的 LD_LIBRARY_PATH;
  2. Python tarfile 解压 + libexec 目录 —— 绕过鸿蒙解压改坏权限的 bug。

这两点是前面纯交叉编译篇章里没有的,属于「编出来之后还要让它能加载、能跑」要补的课。

它也再次提醒我一个朴素的道理:有些库(尤其深度绑定 systemd/D-Bus 的系统服务),即便编译通过,也未必能在鸿蒙上原样运行。适配的目标要实事求是地分层看待——能完整编译、能打包、能加载,本身就是有价值的阶段性成果,不必强求一步到位。

我会把 xdg-desktop-portal 的整棵依赖树从最底层逐个拆开,做一次完整的「多层依赖适配方法论」总结,把全系列的经验拧成一份行动清单。感谢原创适配作者 程序山海 的开源分享。

Logo

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

更多推荐