踩坑笔记)
本文记录在使用 lycium编译框架移植工作完成后自动生成完整的技术细节和踩坑经验。相关资源地址开源鸿蒙通用三方库社区地址https://gitcode.com/oh-tpctpc_c_cplusplus框架介绍地址https://gitcode.com/openharmony-sig/tpc_c_cpluspluslycium_plusplus框架地址https://gitcode.com/OpenHarmonyPCDeveloper/lycium_plusplusOpenHarmony PC Developer 社区https://gitcode.com/OpenHarmonyPCDeveloper目标平台: OpenHarmony (OHOS) — armeabi-v7a / arm64-v8a上游版本: ZenLib v0.4.41编译框架: lycium / HPKBUILD最终用途: 作为 MediaInfoLib (libmediainfo) 的依赖移植成功截图1. 背景1.1 ZenLib 是什么ZenLib 是 MediaArea 项目维护的一套跨平台 C 工具库提供字符串处理Ztring、ZtringList等文件操作File、Dir、FileName线程和同步Thread、CriticalSection配置解析InfoMap、Translation大整数支持int128s/int128u它是MediaInfo和MediaConch的核心依赖。在 OpenHarmony 上移植 MediaInfo 前必须先完成 ZenLib 的交叉编译。1.2 为什么单独做成独立包上游 MediaInfoLib 的 CMakeLists.txt 硬编码了一个相对路径../../../ZenLib/Source来引用 ZenLib 头文件这种方式无法用于交叉编译。正确的做法是将ZenLib 编译安装为一个独立的系统包提供 CMake Config 文件 (ZenLibConfig.cmake)在 MediaInfoLib 中通过find_package(ZenLib)查找依赖BUILD_ZENLIBOFF禁止内嵌构建安装路径由CMAKE_INSTALL_PREFIX控制输出到统一仓库lycium/usr/下2. 移植步骤2.1 目录结构thirdparty/libzen/ ├── HPKBUILD # 构建脚本 ├── HPKCHECK # 测试占位 ├── libzen_ohos.patch # OHOS 适配补丁 └── download.log # (自动生成) 下载日志2.2 HPKBUILD 核心说明点击展开 HPKBUILD 完整内容pkgnamelibzenpkgver0.4.41pkgrel0pkgdescZenLib - C utility library used by MediaArea projects (MediaInfo, MediaConch).urlhttps://github.com/MediaArea/ZenLibarchs(armeabi-v7aarm64-v8a)license(BSD-2-Clause)depends()makedepends()sourcehttps://github.com/MediaArea/ZenLib/archive/refs/tags/v$pkgver.tar.gzautounpacktruedownloadpackagetruebuildtoolscmake# GitHub tarball tag v0.4.41 extracts to ZenLib-0.4.41/builddirZenLib-${pkgver}packagenamev$pkgver.tar.gzpatchflagtrueprepare(){# 打patch: OHOS(musl) 不支持 pthread_cancelif$patchflagthencd$builddir# 源码文件是 CRLF 换行用 sed 直接替换规避 patch 换行符不兼容sed-is|#if !defined(__ANDROID_API__)|#if !defined(__ANDROID_API__) \\ !defined(__MUSL__)|Source/ZenLib/Thread.cpppatchflagfalsecd$OLDPWDfimkdir-p$builddir/Project/CMake/$ARCH-build}build(){cd$builddirPKG_CONFIG_LIBDIR${pkgconfigpath}${OHOS_SDK}/native/build-tools/cmake/bin/cmake$\-DOHOS_ARCH$ARCH-BProject/CMake/$ARCH-build -SProject/CMake/-L$buildlog21$MAKEVERBOSE1-CProject/CMake/$ARCH-build$buildlog21ret$?cd${OLDPWD}return$ret}package(){cd$builddir$MAKEVERBOSE1-CProject/CMake/$ARCH-buildinstall$buildlog21ret$?cd${OLDPWD}return$ret}check(){if[$ARCHarmeabi-v7a]thencp-af$OHOS_SDK/native/llvm/lib/arm-linux-ohos/libc_shared.so$LYCIUM_ROOT/usr/$pkgname/$ARCH/lib/elif[$ARCHarm64-v8a]thencp-af$OHOS_SDK/native/llvm/lib/aarch64-linux-ohos/libc_shared.so$LYCIUM_ROOT/usr/$pkgname/$ARCH/lib/elseecho$ARCHnot supportreturn-1fiechoThe test must be on an OpenHarmony device!}# 清理环境cleanbuild(){rm-rf${PWD}/$builddir}关键变量说明变量含义builddirZenLib-${pkgver}GitHub tagv0.4.41解压后目录名为ZenLib-0.4.41packagenamev$pkgver.tar.gz下载的文件名为v0.4.41.tar.gzbuildtoolscmake使用 CMake 构建而非 autotoolsdepends()ZenLib 不依赖任何第三方库纯 C仅依赖 libcHPKCHECK :sourceHPKBUILD/dev/null21logfile${LYCIUM_THIRDPARTY_ROOT}/${pkgname}/${pkgname}_${ARCH}_${OHOS_SDK_VER}_test.logopenharmonycheck(){res0cd$builddir/Project/CMake/$ARCH-build# ZenLib has no ctest-based tests to run on deviceechoThe test must be on an OpenHarmony device!${logfile}21cd$OLDPWDreturn$res}2.3 构建管道prepare() → 解压 → 打 patch → 创建构建目录 build() → cmake 配置 → make 编译 package() → make install → 复制产物到 lycium/usr/libzen/$ARCH/ check() → 安装 libc_shared.so → 提示真机测试prepare() 阶段在 CMake 配置前对Source/ZenLib/Thread.cpp做一行替换sed-is|#if !defined(__ANDROID_API__)|#if !defined(__ANDROID_API__) !defined(__MUSL__)|Source/ZenLib/Thread.cpp原因详见第 3 节坑点记录。build() 阶段cd$builddirPKG_CONFIG_LIBDIR${pkgconfigpath}${OHOS_SDK}/native/build-tools/cmake/bin/cmake\$-DOHOS_ARCH$ARCH\-BProject/CMake/$ARCH-build -SProject/CMake/-L$buildlog21$MAKEVERBOSE1-CProject/CMake/$ARCH-build$buildlog21使用 OpenHarmony SDK 内置的 cmake toolchain 进行交叉编译cmake 入口Project/CMake/非源码根目录构建目录Project/CMake/$ARCH-build选项-L列出所有 CMake cache 变量到日志方便排查package() 阶段$MAKEVERBOSE1-CProject/CMake/$ARCH-buildinstall$buildlog21CMake 的install目标会自动将库、头文件、cmake-config 文件安装到CMAKE_INSTALL_PREFIX指定的路径由build.sh传入。check() 阶段复制libc_shared.so到安装目录确保在真机上运行时能找到 C 运行时库cp-af$OHOS_SDK/native/llvm/lib/aarch64-linux-ohos/libc_shared.so\$LYCIUM_ROOT/usr/$pkgname/$ARCH/lib/不同架构的 libc_shared.so 路径不同arm-linux-ohos/对应 armeabi-v7aaarch64-linux-ohos/对应 arm64-v8a。2.4 HPKCHECK测试脚本2.4.1 HPKCHECK 是什么HPKCHECK是 lycium 框架中与HPKBUILD配套的测试描述文件位于同一个库目录下如thirdparty/libzen/HPKCHECK。它定义了在OpenHarmony 真机上对已编译库进行功能测试的流程。与HPKBUILD不同维度HPKBUILDHPKCHECK执行时机交叉编译时在构建机上运行安装到真机后在 OHOS 设备上运行执行环境Linux x86_64 OHOS toolchainOpenHarmony 设备arm产出.so/.a/ 头文件测试日志*_test.log核心函数prepare()→build()→package()openharmonycheck()必备性必须推荐2.4.2 HPKCHECK 的基本结构# 1. source HPKBUILD — 获取 pkgname、builddir 等变量sourceHPKBUILD/dev/null21# 2. 定义测试日志路径logfile${LYCIUM_THIRDPARTY_ROOT}/${pkgname}/${pkgname}_${ARCH}_${OHOS_SDK_VER}_test.log# 3. (可选) 测试前的准备工作checkprepare(){return0}# 4. 核心测试函数 — 在真机上被调用openharmonycheck(){res0cd${builddir}/Project/CMake/${ARCH}-build# ... 测试逻辑 ...cd$OLDPWDreturn$res}三个关键约定source HPKBUILD— 导入 HPKBUILD 中定义的元变量pkgname、pkgdir、builddir、ARCH等确保测试脚本与构建脚本使用同一套路径约定logfile— 测试日志输出路径框架会自动收集并归档openharmonycheck()— 唯一的入口函数返回 0 表示测试通过非 0 表示失败2.4.3 三种测试模式从现有库的 HPKCHECK 可以归纳出三种模式模式 A占位/人工测试适用于无自动化测试的库适用场景库本身不提供测试套件或测试需要在真实环境中手动执行。以libzen为例sourceHPKBUILD/dev/null21logfile${LYCIUM_THIRDPARTY_ROOT}/${pkgname}/${pkgname}_${ARCH}_${OHOS_SDK_VER}_test.logopenharmonycheck(){res0cd$builddir/Project/CMake/$ARCH-buildechoThe test must be on an OpenHarmony device!${logfile}21cd$OLDPWDreturn$res}特点不执行任何自动化测试仅在日志中提示测试需要在真机上手动执行始终返回 0不阻塞 CI 流程模式 Bctest / make test适用于 CMake 项目适用场景项目通过 CTest 或 make check 注册了单元测试。以xnnpackCMake CTest为例sourceHPKBUILD/dev/null21logfile${LYCIUM_THIRDPARTY_ROOT}/${pkgname}/${pkgname}_${ARCH}_${OHOS_SDK_VER}_test.logopenharmonycheck(){res0cd${builddir}/${ARCH}-build ctest${logfile}21res$?if[$res-ne0];thenmkdir-p${LYCIUM_FAULT_PATH}/${pkgname}cpTesting/Temporary/LastTest.log${LYCIUM_FAULT_PATH}/${pkgname}/ficd$OLDPWDreturn$res}核心模式步骤含义cd ${builddir}/...-build进入构建目录CTest 配置文件在此ctest ${logfile}执行全部 CTest 测试用例mkdir -p ${LYCIUM_FAULT_PATH}cp LastTest.log测试失败时保留现场日志return $res将测试结果传给框架如果需要过滤特定用例如排除需要 Python 的测试可以使用ctest -I# googletest 的写法只跑第 1~45 个用例ctest-I1,45${logfile}21模式 C自定义产检检查适用于无测试套件的库适用场景库没有注册 CTest 用例但仍希望在真机上验证基本功能。以libmediainfo为例本文联合编写的 HPKCHECKsourceHPKBUILD/dev/null21logfile${LYCIUM_THIRDPARTY_ROOT}/${pkgname}/${pkgname}_${ARCH}_${OHOS_SDK_VER}_test.logopenharmonycheck(){res0cd${builddir}/Project/CMake/${ARCH}-build# 1. 验证共享库已生成if[!-flibmediainfo.so];thenechoFAIL: libmediainfo.so not found${logfile}res1fi# 2. 验证核心导出符号if[$res-eq0];thenifnm-Dlibmediainfo.so|grep-qMediaInfo_New;thenechoOK: MediaInfo_New symbol found${logfile}elseechoWARN: symbol not found${logfile}fifiif[$res-ne0];thenmkdir-p${LYCIUM_FAULT_PATH}/${pkgname}cplibmediainfo.so${LYCIUM_FAULT_PATH}/${pkgname}/ficd$OLDPWDreturn$res}模式 C 的适用场景库体量大、完整的真机测试成本高需要特定媒体文件但希望至少验证产物的基本完整性文件存在、符号导出正确作为 “smoke test” 防线在 CI 中快速反馈链接是否正确2.4.4 编写 HPKCHECK 的 checklist编写一个高质量的 HPKCHECK建议按以下顺序检查是否有现成的测试套件若项目通过 CTest / make check 注册了测试 → 用模式 B若无 → 考虑模式 C至少做产检或模式 A占位构建目录路径是否正确CMake 项目${builddir}/Project/CMake/$ARCH-build如 libzen、libmediainfo也有项目用${builddir}/$ARCH-build如 xnnpack、libpng以 HPKBUILD 中build()函数的cmake -B路径为准失败时是否保留足够现场至少复制测试日志到LYCIUM_FAULT_PATH对于二进制产物检查可以复制.so文件供离线分析测试日志是否可读关键步骤使用echo OK/FAIL: ... ${logfile}输出避免裸命令静默执行而无任何日志输出3. 踩坑记录坑 1pthread_cancel在 musl 中缺失现象Source/ZenLib/Thread.cpp:522:5: error: use of undeclared identifier pthread_cancel pthread_cancel((pthread_t)ThreadPointer); ^ 1 error generated.根因pthread_cancel是 glibc / Android Bionic 提供的非 POSIX 标准函数OpenHarmony 使用 musl libcmusl 未实现pthread_cancel作者认为该设计有缺陷ZenLib 此前已对 Android 做了规避!defined(__ANDROID_API__)但未考虑 musl解决修改Source/ZenLib/Thread.cpp第 522 行在条件中增加!defined(__MUSL__)// Before:#if!defined(__ANDROID_API__)pthread_cancel((pthread_t)ThreadPointer);#endif// After:#if!defined(__ANDROID_API__)!defined(__MUSL__)pthread_cancel((pthread_t)ThreadPointer);#endif为什么用__MUSL__OHOS cmake toolchain 的编译器定义中自动包含了-D__MUSL__可从编译日志的 CFLAGS 中看到。远程影响评估ForceTerminate()不再会被调用时线程将无法被强制终止。但考虑该函数的设计意图仅是非优雅终止在 OHOS 上此功能降级为等待线程自然退出不影响正常功能。坑 2GitHub 源码文件的 CRLF 换行符现象patching file Source/ZenLib/Thread.cpp Hunk #1 FAILED at 519 (different line endings).patch工具报错不同的行结束符。根因ZenLib 的源码文件使用CRLFWindows 风格\r\n行结束符而patch工具默认处理的 patch 文件使用LFUnix 风格\n两者不兼容。解决放弃使用patch文件改用sed在prepare()中直接做文本替换sed-is|#if !defined(__ANDROID_API__)|#if !defined(__ANDROID_API__) !defined(__MUSL__)|\Source/ZenLib/Thread.cpps|旧|新|语法用|作为分隔符避免与路径中的/冲突。sed -i原地修改保留原始文件的换行符格式。注意libzen_ohos.patch文件保留在仓库中以记录改动意图但实际构建时通过sed执行。坑 3GitHub 直连下载失败现象HTTP request sent, awaiting response... No data received.wget请求 GitHub release tarball 时服务器无响应。根因该环境的网络对github.com的某些端点不稳定连接被 reset。解决改用 GitHub 的codeload端点# 原始地址可能超时https://github.com/MediaArea/ZenLib/archive/refs/tags/v0.4.41.tar.gz# 替代地址codeload通常更稳定https://codeload.github.com/MediaArea/ZenLib/tar.gz/v0.4.41如果 codeload 也不通还可以使用 jsDelivr CDN 镜像https://cdn.jsdelivr.net/gh/MediaArea/ZenLibv0.4.41但 jsDelivr 对于大文件可能需要.zip格式而非.tar.gz且不递归解压子模块。4. 编译结果验证4.1 构建产物lycium/usr/libzen/ ├── armeabi-v7a/ # 32-bit ARM 架构 │ ├── include/ZenLib/ # 头文件 (30 个 .h) │ ├── lib/ │ │ ├── libc_shared.so # C 运行时 (来自 OHOS SDK) │ │ ├── libzen.so → libzen.so.0 │ │ ├── libzen.so.0 → libzen.so.0.4.41 │ │ ├── libzen.so.0.4.41 # 共享库 (486 KB) │ │ ├── cmake/zenlib/ # CMake config (供 find_package) │ │ └── pkgconfig/libzen.pc # pkg-config 描述文件 │ └── ... (同上结构) └── arm64-v8a/ # 64-bit ARM 架构 ├── include/ZenLib/ # 头文件 (与 32-bit 相同) ├── lib/ │ ├── libc_shared.so │ ├── libzen.so → libzen.so.0 │ ├── libzen.so.0 → libzen.so.0.4.41 │ └── libzen.so.0.4.41 # 共享库 (555 KB) ├── cmake/zenlib/ └── pkgconfig/libzen.pc4.2 自动注册到构建记录编译成功后build.sh会将库注册到lycium/usr/hpk_build.csvlibzen,0.4.41,armeabi-v7a libzen,0.4.41,arm64-v8a这意味着后续编译依赖 libzen 的库如 libmediainfo时框架会自动跳过 libzen 的重复编译。4.3 文件大小对比架构libzen.so 大小libc_shared.soarmeabi-v7a486 KB1.1 MBarm64-v8a555 KB1.2 MB32-bit 库体积略小属于正常的指令集差异。4.4 CMake 集成验证ZenLibConfig.cmake正确导出了zen目标# 依赖方 CMakeLists.txt 中使用方式 find_package(ZenLib REQUIRED) target_link_libraries(myapp PRIVATE zen) # → 自动添加头文件路径和链接选项5. 与 libmediainfo 的联动libzen 之所以被移植根本目的是作为libmediainfo (MediaInfoLib)的依赖。两者在构建时的关系zlib (交叉编译自 Zlib 源码) └── libzen (本篇文章的目标库) └── libmediainfo (最终目标)编译顺序./build.sh zlib # 先编译 zlib基本依赖 ./build.sh libzen # 再编译 ZenLib ✅ 已完成 ./build.sh libmediainfo # 最后编译 MediaInfoLib依赖 libzen zliblibmediainfo 的 HPKBUILD 中声明了depends(libzenzlib)构建时通过-DBUILD_ZENLIBOFF -DBUILD_ZLIBOFF禁止内嵌编译改为find_package查找系统已安装的版本。CMake 的CMAKE_FIND_ROOT_PATH机制自动将lycium/usr/libzen/$ARCH/和lycium/usr/zlib/$ARCH/纳入搜索路径。6. 完整命令速查# 环境准备exportOHOS_SDK/root/ohos-sdk/linux# 单次编译从下载到安装cd/root/build/tpc_c_cplusplus/lycium ./build.sh libzen# 清理并重新编译rm-rf../thirdparty/libzen/ZenLib-0.4.41../thirdparty/libzen/*.log ./build.sh libzen# 手动验证产物fileusr/libzen/arm64-v8a/lib/libzen.so.0.4.41 nm-Dusr/libzen/arm64-v8a/lib/libzen.so.0.4.41|grep-izenlib# 查看编译日志排查错误时使用tail-100../thirdparty/libzen/libzen-0.4.41-arm64-v8a-lycium_build.log附文件清单libzen文件用途thirdparty/libzen/HPKBUILD构建脚本 (87 行含 pthread_cancel sed 替换)thirdparty/libzen/HPKCHECK测试占位提示真机手动测试thirdparty/libzen/libzen_ohos.patchOHOS 适配补丁pthread_cancel修复CRLF仅供参考thirdparty/libzen/download.log下载日志自动生成thirdparty/libzen/libzen-*.log编译日志自动生成每个架构一个lycium/usr/libzen/安装产物头文件 .so cmake configlibmediainfo文件用途thirdparty/libmediainfo/HPKBUILD构建脚本依赖 libzen zlibBUILD_ZENLIBOFFthirdparty/libmediainfo/HPKCHECK测试脚本产检模式验证 .so 存在 MediaInfo_New 符号thirdparty/libmediainfo/libmediainfo_ohos.patchCMakeLists.txt 移除硬编码 ZenLib 路径CRLF仅供参考