《鸿蒙PC三方库适配实战:aegisub 3.4.2从编译报错到348测试通过的完整复盘》
摘要:本文记录我参加开源鸿蒙 PC 专项积分赛、完成 aegisub 字幕编辑器核心库适配的全过程。从源码获取、meson 构建配置、依赖链处理到 gtest 全量测试,覆盖高难度库适配完整链路。适配过程中遇到 wxWidgets GUI 模块缺失、LuaJIT 平台检测失败、Boost 模块裁剪、.pc 文件 prefix 错误、iconv 边界差异 5 个真实踩坑问题,逐一给出定位思路与最终解法。适配完成后 libaegisub 核心库 349 个 gtest 用例 348 个通过(99.7%),积分赛单库得分 111 分,并贡献 E217 知识草稿(OHOS meson 适配三步法)。
适用读者:参与开源鸿蒙 PC 专项积分赛的开发者、需要移植 C/C++ 三方库到鸿蒙 PC 的工程师、对 meson 构建系统交叉编译感兴趣的开发者
前置知识:了解 meson 构建系统、熟悉 C/C++ 基础、对 Conan 包管理器有初步认识
环境说明:DevEco Studio 5.0.5、HarmonyOS PC 5.0.1(x86_64 架构)、OpenHarmony SDK API 18、aegisub 3.4.2、wxWidgets 3.2.6、Boost 1.90.0
一、背景与痛点
开源鸿蒙 PC 专项积分赛的核心任务是:把社区常用 C/C++ 三方库移植到鸿蒙 PC 平台,通过 Conan 配方归档到 build_in_harmonyos 仓库,每完成一个库的适配并根据难度和完成度获得相应积分。普通难度的库需要完成多个才能有机会获得较高的分数,而完成一个高难度库就可以获得较多的积分,所以我们尝试去适配并完成高难度库。
我们选的库是 aegisub 3.4.2。原因有两个:一是 aegisub 作为开源字幕编辑器的标杆项目,在视频创作社区使用率很高,鸿蒙PC上目前还没有对应的原生工具,适配价值大;二是这个库依赖链复杂——涉及 wxWidgets、boost、icu、fontconfig、freetype、harfbuzz、fftw3、luajit、libass 等十几个依赖,属于典型的"高难度挑战"级别,适配它具有生态价值,并且可以将这类核心库的踩坑经验总结完善成方法,便于后续其他库的适配,积分含金量很高。
但动手之前我低估了难度。aegisub 不是那种几个源文件就能编完的轻量库,它用 Meson 构建系统,有复杂的子项目依赖(LuaJIT、libass 都是子项目),GUI 前端和核心库耦合紧密。我前后用了 9 天才跑通第一个能运行 gtest 的静态库,中间编译报错 60 多次,光是 Boost 模块补全就花了两天时间。
这篇文章把整个适配过程完整记录下来,包括我在适配途中遇到的一些困难和最终解法。如果你也在参加积分赛或者要做鸿蒙 PC 的三方库移植,希望可以帮助到你。
二、鸿蒙PC三方库适配机制
在进入实战前,先要充分了解鸿蒙 PC 平台三方库适配的整体机制和规则。这部分我也耗费了一些时间才真正理解,如果一开始就讲规则和机制熟记于心,那会节省大量的时间,提高适配的效率。
2.1 适配整体架构
鸿蒙 PC 的三方库适配采用 Conan + build_in_harmonyos 的模式。每个适配的库以 Conan 配方(conanfile.py)的形式提交到 build_in_harmonyos 仓库,先触发 AI 校验,在 AI 校验通过后,CI 会自动构建并验证,如果存在报错或校验失败就需要对代码进行修改、更正。
其适配的本质便是:让原本运行在 Linux/macOS/Windows 上的 C/C++ 库,能用鸿蒙 PC 的交叉编译工具链编译通过,并在真机上跑通测试。
适配工作集中在以下三个层面:
- 构建系统适配 — 处理 meson/CMake/autotools 等构建系统在鸿蒙平台的检测逻辑
- 依赖链适配 — 确保所有依赖库都有鸿蒙 PC 版本,且版本兼容
- 运行时验证 — 在真机上编译运行 gtest 等测试套件,确认功能可用
2.2 Conan 配方核心要点
Conan 配方是适配工作的交付物,有几个要点一开始必须搞清楚:
conanfile.py:每个库的核心配置文件,定义源码来源、构建命令、打包规则。鸿蒙 PC 适配的 Conan 配方通常用 MesonToolchain 或 CMakeToolchain 注入交叉编译参数。
conandata.yml:声明源码下载地址和 sha256 校验值。注意 R39 规则——必须用官方原始 URL,不能用本地缓存或 gh-proxy 包装的 file:// 路径,否则 CI 构建直接失败。
test_package:消费者测试,验证打包后的库能被外部项目 find_package 并链接使用。R34 规则要求必须调用真实 API,不能只检查文件存在(空壳测试会被打回)。
2.3 积分赛评分维度
积分赛对每个适配库的评分有多个维度,了解评分规则有助于分配精力:

最终单库得分 111 分(100+10+1)。其中基础适配拿了满分,知识贡献因为提交了可复用的 meson 适配三步法加 10 分,高难度挑战因为依赖链庞大且全量测试覆盖加 1 分。
三、实战方案:Aegisub 完整适配流程
3.1 适配前的环境准备
环境准备这一步看似简单,但我因为工具链版本不对返工过一次。鸿蒙 PC 的交叉编译工具链藏在 DevEco Studio 的 SDK 目录里,必须确认版本和架构。
检查 SDK 路径和版本:
# 确认鸿蒙 PC SDK 已安装
# macOS: ~/Library/Huawei/Sdk
# Windows: C:\Program Files\Huawei\Sdk
ls ~/Library/Huawei/Sdk/openharmony/18/native/
# 应该能看到 llvm、sysroot、build-tools 等目录
# 确认交叉编译工具链存在
ls ~/Library/Huawei/Sdk/openharmony/18/native/llvm/bin/
# 应该有 clang、clang++、llvm-ar 等可执行文件
关键点:鸿蒙 PC 从 API 18 开始支持 x86_64 架构,之前的版本只有 ARM。如果 SDK 版本低于 18,交叉编译出来的静态库在 PC 上跑不起来。我一开始用的 API 17 的 SDK,编译通过但运行时报 dlopen failed: wrong ELF class,就是这个原因。
3.2 源码获取与依赖分析
Aegisub 的上游仓库已经从 Aegisub/Aegisub 迁移到了 TypesettingTools/Aegisub,最新版本是 3.4.2(2025 年 1 月发布)。
源码获取:
# 从官方 GitHub 下载源码
wget https://github.com/TypesettingTools/Aegisub/archive/refs/tags/v3.4.2.tar.gz
# 校验 sha256(填入 conandata.yml)
sha256sum v3.4.2.tar.gz
依赖链梳理是适配前最重要的一步。Aegisub 的依赖比我预想的多,我画了一张依赖图:
aegisub 3.4.2
├── wxWidgets 3.2.6(GUI 框架,核心库只用 wxBase)
├── boost 1.90.0(interprocess/locale/asio/charconv/range 等模块)
├── icu4c 77(Unicode 支持,boost.locale 依赖)
├── fontconfig(字体配置)
├── freetype(字体渲染)
├── harfbuzz(文字塑形)
├── fftw3(FFT 计算,音频波形用)
├── zlib(压缩)
├── libpng(图片)
├── luajit 2.1.0(自动化脚本,meson 子项目)
├── libass(字幕渲染,meson 子项目)
└── gtest(单元测试)
其中 LuaJIT 和 libass 是 Aegisub 的 Meson 子项目(在 subprojects/ 目录下),其他依赖需要从 Conan 制品库获取。
3.3 只编译核心库 libaegisub
为什么只编译 libaegisub 不编 GUI:鸿蒙 PC 上的 wxWidgets 是 GUI=OFF 版本(只有 wxBase 核心库,没有 std/stc/gl 等 GUI 模块)。Aegisub 的 GUI 前端(src/ 目录)大量使用 wxWidgets GUI 控件,短期内适配成本极高。但 libaegisub 核心库只用到了 wxBase 的 wxString 和少量工具类,适配可行。这是我花了一天时间读源码后才确定的适配策略——先让核心库跑起来,GUI 留待后续。
3.4 编译执行与产物验证
配置完成后执行编译:
# 创建构建目录并配置
meson setup build --buildtype=release \
-Dwx_version=3.2.6 \
-Dalsa=disabled \
-Dopenal=disabled \
-Dlibpulse=disabled \
-Dportaudio=disabled \
-Dffms2=disabled \
-Davisynth=disabled \
-Dsystem_luajit=false \
-Ddefault_library=static \
-Duchardet=disabled \
-Dcsri=disabled \
-Denable_update_checker=false
# 编译 gtest 测试程序
meson compile -C build gtest-main
# 运行测试
LD_LIBRARY_PATH=<conan 制品 lib 目录> ./build/tests/gtest-main
验证产物架构:
# 用鸿蒙 SDK 自带的 llvm-readelf 检查
$OHOS_SDK_NATIVE_DIR/llvm/bin/llvm-readelf -h build/libaegisub/libaegisub.a | grep Machine
# 应该输出:Machine: Advanced Micro Devices X86-64
# 如果输出 ARM 或 AArch64,说明交叉编译配置有误
3.4 编译执行与产物验证
配置完成后执行编译:
# 创建构建目录并配置
meson setup build --buildtype=release \
-Dwx_version=3.2.6 \
-Dalsa=disabled \
-Dopenal=disabled \
-Dlibpulse=disabled \
-Dportaudio=disabled \
-Dffms2=disabled \
-Davisynth=disabled \
-Dsystem_luajit=false \
-Ddefault_library=static \
-Duchardet=disabled \
-Dcsri=disabled \
-Denable_update_checker=false
# 编译 gtest 测试程序
meson compile -C build gtest-main
# 运行测试
LD_LIBRARY_PATH=<conan 制品 lib 目录> ./build/tests/gtest-main
创建构建目录并配置
meson setup build --buildtype=release
-Dwx_version=3.2.6
-Dalsa=disabled
-Dopenal=disabled
-Dlibpulse=disabled
-Dportaudio=disabled
-Dffms2=disabled
-Davisynth=disabled
-Dsystem_luajit=false
-Ddefault_library=static
-Duchardet=disabled
-Dcsri=disabled
-Denable_update_checker=false
编译 gtest 测试程序
meson compile -C build gtest-main
运行测试
LD_LIBRARY_PATH=<conan 制品 lib 目录> ./build/tests/gtest-main
验证产物架构:
# 用鸿蒙 SDK 自带的 llvm-readelf 检查
$OHOS_SDK_NATIVE_DIR/llvm/bin/llvm-readelf -h build/libaegisub/libaegisub.a | grep Machine
# 应该输出: Machine: Advanced Micro Devices X86-64
# 如果输出 ARM 或 AArch64,说明交叉编译配置有误
用鸿蒙 SDK 自带的 llvm-readelf 检查
$OHOS_SDK_NATIVE_DIR/llvm/bin/llvm-readelf -h build/libaegisub/libaegisub.a | grep Machine
应该输出: Machine: Advanced Micro Devices X86-64
如果输出 ARM 或 AArch64,说明交叉编译配置有误
为什么禁用一堆功能:Aegisub 默认启用了 ALSA、OpenAL、PulseAudio 等多种音频后端,还有 FFMS2、AviSynth 等视频源插件。但鸿蒙 PC 上这些系统库都不存在,禁用后可以大幅减少依赖,让编译更快通过。反正我们的目标是 libaegisub 核心库,音频视频播放这些 GUI 功能暂时不需要。
四、编译报错排查实录
适配过程中遇到的编译报错远不止 5 个,这里挑 5 个最有代表性的、定位时间超过 2 小时的报错详细记录。
4.1 报错一:wxWidgets GUI 模块缺失
现象:meson configure 时报 Dependency “wx” not found,但 wxWidgets 的 Conan 包明明已经装好了。
定位过程:
# 检查 wxWidgets 制品里有什么
ls <conan_wxwidgets_lib_dir>/pkgconfig/
# 输出: wx-baseu-3.2.pc ← 只有 wxBase,没有 wx std/stc/gl
# 看看 aegisub 的 meson.build 里要什么
grep -n "wx_dep" meson.build
# wx_dep = dependency('wx', modules : ['std', 'stc', 'gl'], ...)
# 它要的是 wx std + stc + gl 三个 GUI 模块
4.1 报错一:wxWidgets GUI 模块缺失
现象:meson configure 时报 Dependency “wx” not found,但 wxWidgets 的 Conan 包明明已经装好了。
定位过程:
在这里插入# 检查 wxWidgets 制品里有什么
ls <conan_wxwidgets_lib_dir>/pkgconfig/
# 输出: wx-baseu-3.2.pc ← 只有 wxBase,没有 wx std/stc/gl
# 看看 aegisub 的 meson.build 里要什么
grep -n "wx_dep" meson.build
# wx_dep = dependency('wx', modules : ['std', 'stc', 'gl'], ...)
# 它要的是 wx std + stc + gl 三个 GUI 模块片
检查 wxWidgets 制品里有什么
ls <conan_wxwidgets_lib_dir>/pkgconfig/
输出: wx-baseu-3.2.pc ← 只有 wxBase,没有 wx std/stc/gl
看看 aegisub 的 meson.build 里要什么
grep -n “wx_dep” meson.build
wx_dep = dependency(‘wx’, modules : [‘std’, ‘stc’, ‘gl’], …)
它要的是 wxStd + STC + GL 三个 GUI 模块
翻了一下 build_in_harmonyos 仓库里已合入的 wxWidgets 3.2.6 配方,发现它是 wxUSE_GUI=OFF 编译的——也就是说鸿蒙 PC 上的 wxWidgets 只有命令行工具库 wxBase,没有 GUI 模块。这是合理的,因为鸿蒙 PC 有自己的 ArkUI 框架,不需要 wxWidgets 的 GUI。
但 Aegisub 的核心库 libaegisub 到底用不用 GUI 模块?我 grep 了一下源码:
# libaegisub 目录下引用了哪些 wx 头文件
grep -rh "#include <wx/" libaegisub/ | sort -u
# wx/string.h ← wxBase 有
# wx/format.h ← wxBase 有
# wx/arrstr.h ← wxBase 有
# ... 其他 wxBase 头文件,总共 2 处
# 对比 src/ 目录(GUI前端):144 处 wx GUI 头文件引用
libaegisub 目录下引用了哪些 wx 头文件
grep -rh “#include <wx/” libaegisub/ | sort -u
wx/string.h ← wxBase 有
wx/format.h ← wxBase 有
wx/arrstr.h ← wxBase 有
… 其他 wxBase 头文件,总共 2 处
对比 src/ 目录(GUI前端):144 处 wx GUI 头文件引用
结论:libaegisub 核心库只用到了 wxBase,完全不需要 GUI 模块。aegisub 的 meson.build 把 wx_dep 全局定义成带 GUI 模块,只是因为 GUI 前端需要。
解决方案:打补丁修改 meson.build,把 wx_dep 从 wx std/stc/gl 改成 wx-baseu-3.2(wxBase 的 pkg-config 名),同时用 if false 禁用 aegisub 可执行文件的构建(GUI 前端)。
# patches/0001-ohos-meson.patch 核心改动
- wx_dep = dependency('wx', modules : ['std', 'stc', 'gl'], ...)
+ wx_dep = dependency('wx-baseu-3.2', ...)
# GUI 可执行文件在鸿蒙上跳过
- aegisub = executable('aegisub', ...)
+ if false
+ aegisub = executable('aegisub', ...)
+ endif
patches/0001-ohos-meson.patch 核心改动
- wx_dep = dependency(‘wx’, modules : [‘std’, ‘stc’, ‘gl’], …)
- wx_dep = dependency(‘wx-baseu-3.2’, …)
GUI 可执行文件在鸿蒙上跳过
- aegisub = executable(‘aegisub’, …)
- if false
- aegisub = executable(‘aegisub’, …)
- endif
同时,GL(OpenGL)依赖只用于 GUI 前端的视频渲染,libaegisub 和 tests 都不引用,也在鸿蒙上跳过:
- dep_gl = dependency('gl')
+ dep_gl = dependency('', required : false) # harmonyos 上不需要
- dep_gl = dependency(‘gl’)
- dep_gl = dependency(‘’, required : false) # harmonyos 上不需要
经验:遇到依赖缺失时,先别急着“想办法补上”,先搞清楚自己的目标产物到底用不用得上这个依赖。很多时候可以通过裁剪功能范围来绕过去,这比补一个新依赖快得多。
4.2 报错二:LuaJIT 子项目 Unsupported platform
现象:Meson configure 到 LuaJIT 子项目时报错:Unsupported platform,编译直接中断。
定位过程:
# 找到 luajit 的 meson.build
cat subprojects/luajit/meson.build | grep -A 10 "host_machine.system"
# if host_machine.system() == 'linux'
# ...
# elif host_machine.system() == 'darwin'
# ...
# elif host_machine.system() == 'windows'
# ...
# else
# error('Unsupported platform') ← 就是这里报错了
# 找到 luajit 的 meson.build
cat subprojects/luajit/meson.build | grep -A 10 "host_machine.system"
# if host_machine.system() == 'linux'
# ...
# elif host_machine.system() == 'darwin'
# ...
# elif host_machine.system() == 'windows'
# ...
# else
# error('Unsupported platform') ← 就是这里报错了
找到 luajit 的 meson.build
cat subprojects/luajit/meson.build | grep -A 10 “host_machine.system”
if host_machine.system() == ‘linux’
…
elif host_machine.system() == ‘darwin’
…
elif host_machine.system() == ‘windows’
…
else
error(‘Unsupported platform’) ← 就是这里报错了
问题出在 meson 的平台检测。在鸿蒙PC上,host_machine.system() 返回的是 harmonyos,而不是 linux。luajit 的 meson.build 只认识 linux/darwin/windows 三个平台,鸿蒙直接被归入 else 分支报错。
解决方案:打补丁,把 harmonyos 和 ohos 映射到 linux 分支。鸿蒙的内核是 Linux,系统调用基本兼容,luajit 的 linux 分支代码在鸿蒙上完全能用。
# patches/0002-luajit-platform.patch 核心改动
- if host_machine.system() == 'linux'
+ if host_machine.system() in ['linux', 'harmonyos', 'ohos']
# LUAJIT_OS_LINUX + elfasm + lj_vm.s
# 这些在鸿蒙上都能正常工作
patches/0002-luajit-platform.patch 核心改动
- if host_machine.system() == ‘linux’
- if host_machine.system() in [‘linux’, ‘harmonyos’, ‘ohos’]
LUAJIT_OS_LINUX + elfasm + lj_vm.s
这些在鸿蒙上都能正常工作
打完补丁后重新 configure,luajit 子项目顺利通过:
Subproject luajit finished: YES
经验:meson 子项目的平台检测是鸿蒙适配的高频踩坑点。凡是用 meson 构建且带子项目的库,几乎都会遇到这个问题。后来我把这个方法总结进了 E217 知识草稿,叫"harmonyos 平台映射法"——遇到 Unsupported platform 就把 harmonyos/ohos 加到 linux 分支里,90% 的情况都能解决。
4.3 报错三:boost 模块缺失,手工编译 libboost_locale
现象:编译时报 fatal error: boost/interprocess/…hpp: No such file or directory,但 boost 的 Conan 包已经装了。
定位过程:
# 看看 boost 制品里到底有哪些头文件
ls <conan_boost_include_dir>/boost/ | head -20
# 有很多模块,但 interprocess、locale、asio、charconv 都不在
# 查一下 conan 配方里是不是裁剪了
grep -n "interprocess\|locale\|asio\|charconv" boost/conanfile.py
# 发现鸿蒙版本的 boost 做了裁剪,只保留了最常用的模块
# 裁剪原因:减小包体积,加快 CI 构建速度
看看 boost 制品里到底有哪些头文件
ls <conan_boost_include_dir>/boost/ | head -20
有很多模块,但 interprocess、locale、asio、charconv 都不在
查一下 conan 配方里是不是裁剪了
grep -n “interprocess|locale|asio|charconv” boost/conanfile.py
发现鸿蒙版本的 boost 做了裁剪,只保留了最常用的模块
裁剪原因:减小包体积,加快 CI 构建速度
boost 裁剪了 5 个 aegisub 需要的模块:interprocess、locale、asio、charconv、range。
头文件模块的补全相对简单——这些是 header-only 的模块,直接从 GitHub 下载 boost 源码,把对应的头文件目录复制到制品的 include 路径下就行。
但 boost.locale 不一样,它不是纯头文件模块,有编译库 libboost_locale.a。这个库 aegisub 的 agi::charset模块(字符集转换)要链接,缺了就会报 undefined reference。
解决方案:手工编译 libboost_locale.a。
# 1. 从 GitHub 下载 boost.locale 源码
# 2. 提取需要的源文件(15个共享源 + ICU/STD 后端)
# 3. 用鸿蒙交叉编译器编译
clang++ -target x86_64-unknown-linux-ohos \
--sysroot=$OHOS_SDK_NATIVE_DIR/sysroot \
-I<boost_include_dir> \
-DBOOST_LOCALE_NO_POSIX_BACKEND \
-DBOOST_LOCALE_NO_WINAPI_BACKEND \
-c boost/libs/locale/src/icu/*.cpp \
boost/libs/locale/src/std/*.cpp \
boost/libs/locale/src/shared/*.cpp
# 4. 打包成静态库
llvm-ar rcs libboost_locale.a *.o
1. 从 GitHub 下载 boost.locale 源码
2. 提取需要的源文件(15个共享源 + ICU/STD 后端)
3. 用鸿蒙交叉编译器编译
clang++ -target x86_64-unknown-linux-ohos
–sysroot=$OHOS_SDK_NATIVE_DIR/sysroot
-I<boost_include_dir>
-DBOOST_LOCALE_NO_POSIX_BACKEND
-DBOOST_LOCALE_NO_WINAPI_BACKEND
-c boost/libs/locale/src/icu/.cpp
boost/libs/locale/src/std/.cpp
boost/libs/locale/src/shared/*.cpp
4. 打包成静态库
llvm-ar rcs libboost_locale.a *.o
这里有两个关键编译宏:
• BOOST_LOCALE_NO_POSIX_BACKEND — 禁用 POSIX 后端,因为它用到了 glibc 专有的 strfmon_l 函数,鸿蒙的 musl libc 没有
• BOOST_LOCALE_NO_WINAPI_BACKEND — 禁用 Windows 后端,这个在非 Windows 平台本来就不用,但加上更保险
编译好的 libboost_locale.a 放到 boost 制品的 lib 目录下,就能正常链接了。
经验:boost 的模块裁剪是鸿蒙 Conan 制品的常态。遇到 boost 头文件缺失先别急着报 bug,先确认是 header-only 还是有编译库。header-only 的直接补头文件,有编译库的就手工编一下,比等官方更新快多了。
4.4 报错四:.pc 文件 prefix=/ 导致空前缀
现象:编译时报大量 warning: include path ‘/include’ has nonexistent suffix,部分头文件找不到。
定位过程:
# 看看 freetype 的 .pc 文件
cat <conan_freetype_lib_dir>/pkgconfig/freetype2.pc
# prefix=/ ← 这里不对!应该指向制品实际路径
# exec_prefix=${prefix}
# includedir=${prefix}/include
# libdir=${prefix}/lib
# 结果就是 -I//include 变成了空前缀
看看 freetype 的 .pc 文件
cat <conan_freetype_lib_dir>/pkgconfig/freetype2.pc
prefix=/ ← 这里不对!应该指向制品实际路径
exec_prefix=${prefix}
includedir=${prefix}/include
libdir=${prefix}/lib
结果就是 -I//include 变成了空前缀
查了一圈,发现不止 freetype,harfbuzz、fontconfig、icu、boost、wx-baseu、fftw3 这 7 个库的 .pc 文件都有这个问题——prefix 都写成了 /,而不是制品的实际安装路径。
原因是 Conan 制品的 .pc 文件在打包时做了"可重定位"处理,prefix 设成了 /,然后靠 Conan 的 environment 变量在消费时修正。但 meson 的 pkg-config 检测不认这个机制,直接读原始值。
解决方案:逐个创建修正版 .pc 文件,把 prefix 指向制品的实际路径。
# 以 freetype 为例,创建修正版 .pc
cat > <my_pc_dir>/freetype2.pc << EOF
prefix=<conan_freetype_dir>
exec_prefix=${prefix}
includedir=${prefix}/include
libdir=${prefix}/lib
Name: FreeType 2
Description: A free, high-quality, and portable font engine.
Version: 2.13.2
Libs: -L${libdir} -lfreetype
Cflags: -I${includedir}/freetype2
EOF
以 freetype 为例,创建修正版 .pc
cat > <my_pc_dir>/freetype2.pc << EOF
prefix=<conan_freetype_dir>
exec_prefix=prefixincludedir={prefix}
includedir=prefixincludedir={prefix}/include
libdir=prefix/libName:FreeType2Description:Afree,high−quality,andportablefontengine.Version:2.13.2Libs:−L{prefix}/lib
Name: FreeType 2
Description: A free, high-quality, and portable font engine.
Version: 2.13.2
Libs: -Lprefix/libName:FreeType2Description:Afree,high−quality,andportablefontengine.Version:2.13.2Libs:−L{libdir} -lfreetype
Cflags: -I${includedir}/freetype2
EOF
7 个库都改完后,设置 PKG_CONFIG_PATH 指向修正版 .pc 目录,再跑 meson configure,空前缀警告全部消失。
经验:这也是我总结进 E217 知识草稿的一个通用方法——“.pc prefix 修正法”。只要用了 Conan 制品 + meson 构建,大概率会遇到这个问题。提前把常用库的修正版 .pc 准备好,能省很多时间。
4.5 报错五:gtest 运行时 iconv 边界用例失败
现象:gtest-main 编译成功了,但运行时 349 个测试里有 1 个失败:lagi_iconv.Roundtrip。
定位过程:
# 运行失败用例的详细输出
./build/tests/gtest-main --gtest_filter=lagi_iconv.Roundtrip
# 失败信息:
# Expected equality of these values:
# actual: "你好,世界"
# expected: "你好,世界"
# (看起来一样,但编码转换的边界字节有差异)
运行失败用例的详细输出
./build/tests/gtest-main --gtest_filter=lagi_iconv.Roundtrip
失败信息:
Expected equality of these values:
actual: “你好,世界”
expected: “你好,世界”
4.5 报错五:gtest 运行时 iconv 边界用例失败
现象:gtest-main 编译成功了,但运行时 349 个测试里有 1 个失败:lagi_iconv.Roundtrip。
定位过程:
# 运行失败用例的详细输出
./build/tests/gtest-main --gtest_filter=lagi_iconv.Roundtrip
# 失败信息:
# Expected equality of these values:
# actual: "你好,世界"
# expected: "你好,世界"
# (看起来一样,但编码转换的边界字节有差异)
```这个测试是测 iconv 的字符集转换往返(UTF-8 → GBK → UTF-8)。在 Linux 上 glibc 的 iconv 和鸿蒙上 musl libc 的 iconv 实现不完全一致,某些边界字符(比如全角标点、特殊符号)的转换结果有细微差异。
确认不是 bug:我单独写了个测试程序,在鸿蒙上用 iconv_open("GBK", "UTF-8") 做了同样的转换,结果和 gtest 失败的输出一致——这不是 aegisub 的代码有问题,而是系统 iconv 实现的差异。
解决方案:如实披露这个失败用例,不强行"修复"。在 PR 里明确写了:
1 FAIL(lagi_iconv.Roundtrip)为 iconv 转换边界差异,是鸿蒙 musl libc 与 glibc 的实现差异导致,非 aegisub 适配 bug。348/349 = 99.7% 通过率。
为什么不强行改测试用例?因为积分赛有个原则——不修改上游测试源码来"凑通过率"。如实披露 1 个失败用例,反而比"冒充全通过"更可信。
经验:适配第三方库时,测试用例 100% 通过当然最好,但遇到系统级差异(libc、系统调用、iconv 等)导致的失败,如实披露比强行修改更专业。审核者更看重你的诚实和对问题的理解深度。
五、验证结果与数据
5.1 测试结果汇总

5.2 消费测试说明
除了上游自带的 gtest,我还加了 test_package 消费测试,从 Conan 消费者的角度验证打包后的 libaegisub 能被真实使用:
```cpp
// test_package/test_aegisub.cpp
#include <libaegisub/util.h>
#include <cassert>
#include <string>
int main() {
// 真实调用 aegisub API:try_parse 字符串转数字
auto result = agi::util::try_parse<int>("42");
assert(result.has_value());
assert(result.value() == 42);
auto fail_result = agi::util::try_parse<int>("not_a_number");
assert(!fail_result.has_value());
return 0;
}
// test_package/test_aegisub.cpp
#include <libaegisub/util.h>
#include
#include
int main() {
// 真实调用 aegisub API:try_parse 字符串转数字
auto result = agi::util::try_parse(“42”);
assert(result.has_value());
assert(result.value() == 42);
auto fail_result = agi::util::try_parse(“not_a_number”);
assert(!fail_result.has_value());
return 0;
}
加上 elf_check 产物验证(校验 libaegisub.a 是合法的 ar 归档),确保打包产物不是空壳。
5.3 知识贡献
这次适配总结出了一套可复用的方法,我提交为 E217 知识草稿:OHOS meson 适配三步法:

这套方法在 aegisub 上验证通过后,后来我适配其他 meson 构建的库时直接套用,效率提高了至少 50%。
六、FAQ
Q:为什么不适配 aegisub 的 GUI 前端?
A:不是不想,是成本太高。aegisub 的 GUI 前端基于 wxWidgets 的 std/stc/gl 模块,但鸿蒙PC上的 wxWidgets 是 GUI=OFF 的,没有这些模块。要适配 GUI 前端,要么先把 wxWidgets GUI 模块整个移植到鸿蒙PC上(工作量巨大),要么把 aegisub 的 GUI 改成 ArkUI(几乎等于重写)。当前阶段先让 libaegisub 核心库跑通,更务实也更有价值——字幕处理的核心逻辑都在核心库里,GUI 只是外壳。
Q:iconv 那个失败用例,会不会影响实际使用?
A:基本不会。失败的是边界用例,测试的是某些特殊字符的编码转换往返一致性。日常使用的中文、英文、常用标点都没问题。aegisub 的主要功能是字幕时间轴编辑和样式调整,这些功能都不依赖 iconv 的边界转换。
Q:boost 手工编译的部分,稳定性有保障吗?
A:有保障。我只编译了 boost.locale 的 ICU + STD 后端,源码是官方 1.90.0 版本,没有做任何功能修改。禁用的只是 POSIX 和 WinAPI 后端(这两个在鸿蒙上本来就不可用)。编译出来的 libboost_locale.a 通过了 aegisub 所有用到 boost.locale 的测试用例。
Q:给后来者的建议?
A:三个建议。第一,适配前先读源码搞清楚目标产物的依赖范围,能裁剪的就裁剪,别一上来就想全量适配。第二,遇到 meson 平台检测报错,先试试把 harmonyos 加到 linux 分支,大部分情况都能解决。第三,测试用例不用追求 100% 通过,系统级差异导致的失败如实披露就行,审核者看得懂。
七、总结
aegisub 3.4.2 的鸿蒙PC适配是我做过的最有挑战性的三方库移植项目。12 个依赖库、2 个 meson 补丁、5 个核心踩坑、348 个测试通过,最终拿到 111 分——这个过程比我预想的要难,但收获也更大。
最重要的收获不是积分,而是总结出了一套可复用的 meson 适配方法论。从 luajit 的平台映射到 .pc prefix 修正,再到 boost 模块补全,这些方法不是 aegisub 特有的,而是所有用 meson 构建的 C/C++ 库适配鸿蒙PC都会遇到的共性问题。把这些沉淀成知识库条目(E217),能帮后来的开发者少踩很多坑。
如果你也在做鸿蒙PC的三方库适配,欢迎在评论区交流。也欢迎加入开源鸿蒙PC社区,一起完善鸿蒙PC的生态。
项目源码:已上传至 AtomGit 仓库,详见 PR #5301
欢迎加入开源鸿蒙PC社区:https://harmonypc.csdn.net/
欢迎在PC社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper
更多推荐



所有评论(0)