[!NOTE]

加入开源鸿蒙跨平台社区

・共享三方库移植方案、跨平台应用开发

・互助解决编译 / 运行问题

・第一时间获取鸿蒙三方库适配模板

一个人走得快,一群人走得远!

说明

建议先按 C/C++ 三方库鸿蒙化适配一篇搞定:从环境到交叉编译在鸿蒙设备上快速验证由 lycium 交叉编译的 C/C++ 三方库 搭好 Lycium 交叉编译 + 设备验证 闭环;本文侧重 Abseil 特有 CMake/GTest/CTest 与补丁细节。

Abseil 与标准库互补,提供容器、字符串、时间、Status / StatusOr、日志、flags 等模块,gRPC、Protobuf 等生态常用。本适配在 OHOS NDK + CMake 下产出 armeabi-v7a / arm64-v8a 静态库,并保留 GTest 单测 以便设备上 CTest 回归。

  • 构建方式:上游原生 CMake,无 Autotools。
  • 测试依赖:GoogleTest 必须由 Lycium 先编好,配置期 不下载 gtest 源码。
  • 平台差异:musl32 位指针wchar_t 为 32 位时区 tzdata 路径与索引项长度death test / NaN 等与 Linux+glibc 不同,由补丁与脚本消化。

适配流程概述

三方库适配到 OpenHarmony 平台主要包括以下步骤(与参考博文一致):

  1. 环境准备:安装 Command Line Tools,配置 OHOS_SDK,准备 CMake、Make 等。
  2. 源码分析:确认 Abseil 为 CMake 工程、测试依赖 GTest、LTS 版本与 C++17 要求。
  3. 编写 HPKBUILD:下载、打补丁、CMake 配置、DartConfiguration.tcl 修正、编译与安装。
  4. 编写 HPKCHECK(可选增强):检查静态库、*_test 数量、CTest 配置与符号;设备实跑可接 hdc/adb
  5. 问题排查:设备 CTest 路径、macOS sed、主机误跑 ARM ELF 等。
  6. 文档编写README_zh.md、开源信息、hnp.json 等。

HPKBUILD 详细解析

HPKBUILD 是 lycium 的构建脚本,定义下载、补丁、编译、安装与打包。

1. 基本信息配置

pkgname=abseil-cpp
pkgver=20260107.1
pkgrel=0
pkgdesc="Abseil C++ standard library extensions from Google"
url="https://github.com/abseil/abseil-cpp"
archs=("armeabi-v7a" "arm64-v8a")
license=("Apache-2.0")
depends=("googletest")
makedepends=()
source="https://github.com/abseil/abseil-cpp/archive/refs/tags/${pkgver}.tar.gz"

关键点说明

  • pkgname:与 thirdparty 下目录名一致。
  • archs:OH 常用 armeabi-v7aarm64-v8a
  • depends必须先./build.sh googletest 装好 GTest;build_hpk 会把依赖安装根加入 CMAKE_FIND_ROOT_PATH,供 find_package(GTest)
  • source:官方 tag 源码包 URL。

2. 构建工具与目录配置

autounpack=true
downloadpackage=true
builddir=abseil-cpp-${pkgver}
packagename=${builddir}.tar.gz
patchflag=true

关键点说明

  • Abseil 使用 CMake(由 build() 中直接调用 ${OHOS_SDK}/.../cmake,无需 buildtools= 字段)。
  • builddir 解压后与版本号对应,构建树为 $builddir/$ARCH-build

3. prepare() 函数(编译前准备)

3.1 应用主补丁 abseil-cpp_oh_pkg.patch
prepare() {
    if $patchflag; then
        cd $builddir
        patch -p1 < ../abseil-cpp_oh_pkg.patch
        cd "$OLDPWD"
        patchflag=false
    fi

问题背景

  • 上游单测默认假设 glibc / x86 / UTF-16 wchar_t / Android 时区格式 等,在 OHOS 上会出现 death test 失败、宽字符期望不符、tzdata 索引项长度不一致(48 vs 52 字节)等。

解决方案

  • 使用汇总补丁 abseil-cpp_oh_pkg.patch(CMake 测试路径、__OHOS__ 分支、cctz、utf8、charconv、容器测试等)。升级 LTS 时务必 patch -p1 --dry-run 逐文件合并。
3.2 应用补充补丁 abseil-cpp_str_format_glibc_test.patch
    (cd "$builddir" && patch -p1 --forward < ../abseil-cpp_str_format_glibc_test.patch) || true
    rm -f "$builddir/absl/strings/internal/str_format/convert_test.cc.rej"
    mkdir -p $builddir/$ARCH-build
}

问题背景

  • str_format 单测在部分环境要求「与 glibc 完全一致%a 特征」,OHOS libc 与 glibc 不同,需与主补丁中的探测逻辑配合。

解决方案

  • 独立小补丁用 patch --forward,已应用时退出码 1 用 || true 忽略;删除 .rej 避免脏树。

4. build() 函数(CMake、CTest 路径修正)

4.1 CMake 与 GTest 选项
build() {
    cd $builddir
    ${OHOS_SDK}/native/build-tools/cmake/bin/cmake "$@" \
        -B$ARCH-build -S./ \
        -DABSL_BUILD_TESTING=ON \
        -DABSL_USE_EXTERNAL_GOOGLETEST=ON \
        -DABSL_FIND_GOOGLETEST=ON \
        -DABSL_RUN_mobile_TEST=OFF \
        -DBUILD_SHARED_LIBS=OFF \
        -DCMAKE_CXX_STANDARD=17 \
        > $buildlog 2>&1

关键点说明

  • ABSL_USE_EXTERNAL_GOOGLETEST=ON + ABSL_FIND_GOOGLETEST=ON禁止 CMake 下载 GTest。
  • ABSL_BUILD_TESTING=ON:生成 *_test,供设备 CTest
  • BUILD_SHARED_LIBS=OFF:静态库,便于北向工程链接。
  • ABSL_RUN_mobile_TEST=OFF:若 CMake 提示该变量未使用,可忽略。
  • "$@":由 build_hpk.sh 传入 toolchain、CMAKE_INSTALL_PREFIX 等。
4.2 修正 DartConfiguration.tcl(设备 CTest 必看)
    _fix_dart_ctest_paths() {
        local _dcf="$1"
        [ -f "$_dcf" ] || return 0
        if sed --version >/dev/null 2>&1; then
            sed -i 's|^BuildDirectory:.*|BuildDirectory: .|' "$_dcf"
            sed -i 's|^SourceDirectory:.*|SourceDirectory: .|' "$_dcf"
        else
            sed -i '' 's|^BuildDirectory:.*|BuildDirectory: .|' "$_dcf"
            sed -i '' 's|^SourceDirectory:.*|SourceDirectory: .|' "$_dcf"
        fi
    }
    _fix_dart_ctest_paths "$ARCH-build/DartConfiguration.tcl"
    $MAKE -C $ARCH-build >> $buildlog 2>&1
    ret=$?
    _fix_dart_ctest_paths "$ARCH-build/DartConfiguration.tcl"

问题背景

  • CMake 会把构建机绝对路径写入 DartConfiguration.tcl。设备上跑 CTest 时仍向该路径写 Testing/Temporary,出现 Cannot create directory /Users/…

解决方案

  • BuildDirectorySourceDirectory 改为 .;设备上必须在 构建目录根 执行 ctest
  • macOS 上 BSD sed -ised -i '',否则替换可能不生效;脚本用 sed --version 区分 GNU/BSD。
  • make 后再修一次:防止构建过程中 CMake 重跑覆盖该文件。

5. package()check()archive()cleanbuild()

  • package()make install 安装到 lycium/usr/abseil-cpp/<ARCH>/(头文件、libabsl_*.a、CMake 包等)。
  • check():主机上仅 ctest -N 枚举测试;不执行 ARM ELF(避免 Syntax error: word unexpected)。
  • archive():打 tar 包并 hnpcli pack,配合 hnp.json
  • cleanbuild():删除源码构建目录。

HPKBUILD 完整代码

# Copyright (c) 2026 unisources
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Contributor: allincoding <3384684593@qq.com>
# Maintainer: allincoding <3384684593@qq.com>

pkgname=abseil-cpp
pkgver=20260107.1
pkgrel=0
pkgdesc="Abseil C++ standard library extensions from Google"
url="https://github.com/abseil/abseil-cpp"
archs=("armeabi-v7a" "arm64-v8a")
license=("Apache-2.0")
# 由 build_hpk 的 cmakedependpath 注入 CMAKE_FIND_ROOT_PATH,指向已安装的
# lycium/usr/googletest/<ARCH>/,供 find_package(GTest);勿再配置期下载 gtest。
depends=("googletest")
makedepends=()

source="https://github.com/abseil/abseil-cpp/archive/refs/tags/${pkgver}.tar.gz"

autounpack=true
downloadpackage=true
builddir=abseil-cpp-${pkgver}
packagename=${builddir}.tar.gz

patchflag=true

prepare() {
    if $patchflag; then
        cd $builddir
        patch -p1 < ../abseil-cpp_oh_pkg.patch
        cd "$OLDPWD"
        patchflag=false
    fi
    # 独立补丁:每次 prepare 执行;--forward 避免已打补丁时交互/误反转;|| true 忽略已应用时的退出码 1。
    (cd "$builddir" && patch -p1 --forward < ../abseil-cpp_str_format_glibc_test.patch) || true
    rm -f "$builddir/absl/strings/internal/str_format/convert_test.cc.rej"
    mkdir -p $builddir/$ARCH-build
}

build() {
    cd $builddir
    # build_hpk.sh 传入 $@ 包含 toolchain 和 install prefix
    ${OHOS_SDK}/native/build-tools/cmake/bin/cmake "$@" \
        -B$ARCH-build -S./ \
        -DABSL_BUILD_TESTING=ON \
        -DABSL_USE_EXTERNAL_GOOGLETEST=ON \
        -DABSL_FIND_GOOGLETEST=ON \
        -DABSL_RUN_mobile_TEST=OFF \
        -DBUILD_SHARED_LIBS=OFF \
        -DCMAKE_CXX_STANDARD=17 \
        > $buildlog 2>&1
    # CTest 默认把绝对路径写入 DartConfiguration.tcl;设备上无该路径时会无法创建
    # Testing/Temporary。改为 BuildDirectory: . / SourceDirectory: . 后,须在
    # $ARCH-build 目录下执行 ctest(日志为 ./Testing/Temporary/LastTest.log)。
    # macOS 的 sed -i 必须带 '' 备份后缀参数,否则整段 s 命令会被当成后缀而不生效。
    _fix_dart_ctest_paths() {
        local _dcf="$1"
        [ -f "$_dcf" ] || return 0
        if sed --version >/dev/null 2>&1; then
            sed -i 's|^BuildDirectory:.*|BuildDirectory: .|' "$_dcf"
            sed -i 's|^SourceDirectory:.*|SourceDirectory: .|' "$_dcf"
        else
            sed -i '' 's|^BuildDirectory:.*|BuildDirectory: .|' "$_dcf"
            sed -i '' 's|^SourceDirectory:.*|SourceDirectory: .|' "$_dcf"
        fi
    }
    _fix_dart_ctest_paths "$ARCH-build/DartConfiguration.tcl"
    $MAKE -C $ARCH-build >> $buildlog 2>&1
    ret=$?
    # make 期间若触发 CMake 重新配置,会重写 DartConfiguration.tcl,需再修一次。
    _fix_dart_ctest_paths "$ARCH-build/DartConfiguration.tcl"
    cd $OLDPWD
    return $ret
}

package() {
    cd $builddir
    $MAKE -C $ARCH-build install >> $buildlog 2>&1
    ret=$?
    cd $OLDPWD
    return $ret
}

check() {
    # 交叉编译生成的是 ARM/ARM64 ELF,在 x86 构建机上执行 ctest 会被 shell 误当脚本解析
    # (报 Syntax error: word unexpected)。测试可执行文件已在 build 阶段全部链接成功。
    # 仅列出已注册的测试(不运行),设备上实跑仍由 HPKCHECK / ctest 完成。
    # 在 $ARCH-build 下调用 ctest(不用 --test-dir),与 BuildDirectory: . 一致,日志提示为相对路径。
    pushd "$builddir/$ARCH-build" >/dev/null || return 1
    echo "check: $ARCH — ctest 工作目录: $(pwd)" >> "$buildlog"
    echo "check: 失败时详细输出(相对路径): ./Testing/Temporary/LastTest.log" >> "$buildlog"
    echo "check: skip executing tests on host; ctest -N (list only)" >> "$buildlog"
    ${OHOS_SDK}/native/build-tools/cmake/bin/ctest -N >> "$buildlog" 2>&1
    ret=$?
    popd >/dev/null || true
    return $ret
}

archive() {
    mkdir -p ${LYCIUM_ROOT}/output/$ARCH
    pushd ${LYCIUM_ROOT}/usr/$pkgname/$ARCH
        tar -zvcf ${LYCIUM_ROOT}/output/$ARCH/${pkgname}_${pkgver}.tar.gz *
    popd
    cp ${PWD}/hnp.json ${LYCIUM_ROOT}/usr/$pkgname/$ARCH/
    ${OHOS_SDK}/toolchains/hnpcli pack \
        -i ${LYCIUM_ROOT}/usr/$pkgname/$ARCH \
        -o ${LYCIUM_ROOT}/output/$ARCH/ \
        -n $pkgname -v $pkgver
}

cleanbuild() {
    rm -rf ${PWD}/$builddir
}

鸿蒙化补丁说明(与「问题排查」呼应)

主补丁按模块可概括为下表(细节以 patch 为准):

方向 说明
CMake / absl_cc_test 外部 GTest 时条件包含;add_test 使用相对二进制目录路径,CTest 可找到 bin 下用例。
ABSL_OPTION_HARDENED Release 单测仍需要部分 hardening 行为。
__OHOS__ + death test fork/退出码与 GTest 不匹配处对 EXPECT_DEATH_IF_SUPPORTED 做 no-op 或放宽。
32 位 / SOO raw_hash_set_testlow_level_hash_test 等限制 OOM 风险或对齐 golden。
宽字符 / UTF-8 wchar_t 为 32 位时 surrogate 按错误标量处理;日志宽字符串期望分支。
charconv NaN musl 与 from_chars 的 quiet NaN payload 可能不同,改为均 isnan
cctz / tzdata 增加 /system/etc/zoneinfo/tzdata 等路径;兼容 48/52 字节索引项。
日志 / VLOG / flags StderrThreshold 与 flag 同步;VLOG 用独立编译单元测 ABSL_MAX_VLOG_VERBOSITY

补丁abseil-cpp_str_format_glibc_test.patchGlibcHasCorrectTraits 在非 glibc x86_64 上改为校验 缓存与即时探测一致,避免误报。


HPKCHECK 详细解析

HPKCHECK 用于在 CI 或本机记录测试结果、检查产物;设备上完整 CTest 仍需将 $ARCH-build 整树同步到设备后在构建根目录执行 ctest(见下一节)。

1. 初始化

source HPKBUILD > /dev/null 2>&1
logfile=${LYCIUM_THIRDPARTY_ROOT}/${pkgname}/${pkgname}_${ARCH}_${OHOS_SDK_VER}_test.log

关键点说明

  • source HPKBUILD 复用 pkgnamebuilddir 等变量。
  • 日志路径含架构与 SDK 版本,便于多环境归档。

2. checkprepare()openharmonycheck()

  • checkprepare():检查 libabsl_base.a 等是否存在;统计 *_test 数量是否大于 0。
  • openharmonycheck():列出测试二进制、检查 CTestTestfile.cmake、用 llvm-nm 抽查静态库符号;若存在 hdc/adb 可扩展为推送与远程执行(脚本内留有注释示例)。

HPKCHECK 完整代码

# Copyright (c) 2026 unisources
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Contributor: allincoding <3384684593@qq.com>
# Maintainer: allincoding <3384684593@qq.com>

source HPKBUILD > /dev/null 2>&1
logfile=${LYCIUM_THIRDPARTY_ROOT}/${pkgname}/${pkgname}_${ARCH}_${OHOS_SDK_VER}_test.log

checkprepare() {
    # 验证关键库文件和测试二进制存在
    for lib in libabsl_base libabsl_status libabsl_strings libabsl_flags; do
        [ -f "${LYCIUM_ROOT}/usr/${pkgname}/${ARCH}/lib/${lib}.a" ] || return 1
    done

    # 验证测试二进制存在(cmake build testing 产物)
    if [ -d "${LYCIUM_THIRDPARTY_ROOT}/${pkgname}/${builddir}/${ARCH}-build" ]; then
        # 统计测试二进制数量
        testbin_count=$(find "${LYCIUM_THIRDPARTY_ROOT}/${pkgname}/${builddir}/${ARCH}-build" \
            -name "*_test" -o -name "*_benchmark" 2>/dev/null | wc -l)
        echo "Test binaries found: ${testbin_count}" >> ${logfile}
        [ ${testbin_count} -gt 0 ] || return 1
    else
        echo "Build directory not found: ${ARCH}-build" >> ${logfile}
        return 1
    fi
    return 0
}

# 在 OpenHarmony 设备上执行测试
# 通过 ctest 运行 gtest 测试用例
openharmonycheck() {
    res=0
    build_dir="${LYCIUM_THIRDPARTY_ROOT}/${pkgname}/${builddir}/${ARCH}-build"

    if [ ! -d "$build_dir" ]; then
        echo "FAIL: Build directory $build_dir not found" >> ${logfile}
        return 1
    fi

    # 列出所有测试二进制
    echo "=== Test binaries ===" >> ${logfile}
    find "$build_dir" -name "*_test" -type f >> ${logfile} 2>&1

    # 尝试运行 ctest(需要在设备上执行,此处仅验证配置)
    echo "=== ctest configuration ===" >> ${logfile}
    if [ -f "$build_dir/CTestTestfile.cmake" ]; then
        echo "CTest configured: OK" >> ${logfile}
        # 显示已注册的测试数量
        test_count=$(grep "add_test" "$build_dir/CTestTestfile.cmake" 2>/dev/null | wc -l)
        echo "Registered tests: ${test_count}" >> ${logfile}
    else
        echo "WARN: CTestTestfile.cmake not found (tests may not be CMake-based)" >> ${logfile}
    fi

    # 验证关键库可被测试用例链接(ldd 等效检查)
    echo "=== Library linkage check ===" >> ${logfile}
    for lib in libabsl_base libabsl_status libabsl_strings; do
        libpath="${LYCIUM_ROOT}/usr/${pkgname}/${ARCH}/lib/${lib}.a"
        if [ -f "$libpath" ]; then
            # nm 检查库符号完整性
            if ${OHOS_SDK}/native/llvm/bin/llvm-nm "$libpath" 2>/dev/null | head -3 >> ${logfile}; then
                echo "OK: ${lib}.a symbols readable" >> ${logfile}
            else
                echo "WARN: Cannot read symbols from ${lib}.a" >> ${logfile}
            fi
        else
            echo "FAIL: ${lib}.a not found" >> ${logfile}
            res=1
        fi
    done

    # 设备端实际运行测试(需要 hdc 等工具,此处检查是否可用)
    echo "=== Device test execution ===" >> ${logfile}
    if command -v hdc &>/dev/null || command -v adb &>/dev/null; then
        echo "Device tool available - running actual tests on device" >> ${logfile}
        # 设备端 ctest 实际执行(需要在设备上运行ARM二进制)
        # 注意:交叉编译产物的测试必须在 OpenHarmony ARM 设备上运行
        # 此处通过远程命令执行
        if command -v hdc &>/dev/null; then
            # 将测试二进制推送到设备并执行
            echo "Using hdc for device test..." >> ${logfile}
            # 实际设备测试命令示例(需要根据设备IP/序列号调整)
            # hdc -s <serial> file send ${build_dir}/some_test /vendor/bin/
            # hdc -s <serial> shell /vendor/bin/some_test
        elif command -v adb &>/dev/null; then
            echo "Using adb for device test..." >> ${logfile}
        fi
    else
        echo "INFO: No device tool (hdc/adb) found on build machine" >> ${logfile}
        echo "INFO: For actual device testing, run manually:" >> ${logfile}
        echo "  adb push ${build_dir}/*_test /vendor/bin/" >> ${logfile}
        echo "  adb shell /vendor/bin/<test_binary>" >> ${logfile}
    fi

    return $res
}

执行交叉编译构建

lycium 目录执行(googletest 须先于 abseil-cpp):

./build.sh googletest abseil-cpp

成功后:

  • 安装树:lycium/usr/abseil-cpp/<ARCH>/
  • 构建树:thirdparty/abseil-cpp/abseil-cpp-<ver>/<ARCH>-build/

构建日志可参考 thirdparty/abseil-cpp/abseil-cpp-<ver>-<ARCH>-lycium_build.log

image-20260401151745959

全量推送到 OHOS 设备执行测试

abseil-cpp-<ver>/<ARCH>-build 整目录同步到设备(含 CTestTestfile.cmakeDartConfiguration.tclabsl/**/CTestTestfile.cmakebin/*_test),在设备上:

cd /data/<你的同步路径>/<ARCH>-build
ctest
# 失败重跑:
ctest --rerun-failed --output-on-failure

亦可按团队流程将 tpc_c_cplusplus 推到设备后,在 lycium 下执行 ./test.sh abseil-cpp(若已接入),并查看 ${pkgname}_${ARCH}_${OHOS_SDK_VER}_test.log

image-20260401151824640

业务工程 CMake 集成示例

list(APPEND CMAKE_PREFIX_PATH "/path/to/lycium/usr/abseil-cpp/arm64-v8a")
find_package(absl CONFIG REQUIRED)
target_link_libraries(your_target PRIVATE
  absl::strings
  absl::status
)

工程需 C++17+;pthread 等按 OHOS NDK 工程惯例配置。

遇到的问题和解决方案总结

问题 1:设备上 CTest 报错无法创建 /Users/.../Testing/Temporary

错误信息示例

Cannot create directory /Users/.../armeabi-v7a-build/Testing/Temporary
Cannot create log file: LastTest.log

原因DartConfiguration.tclBuildDirectory 仍为构建机绝对路径

解决方案

  1. 使用当前 HPKBUILD 中的 _fix_dart_ctest_paths(含 make 后二次修复)。
  2. 或在设备树中手改 BuildDirectory:SourceDirectory:.,并 cd 到构建根 再执行 ctest

问题 2:macOS 上构建后 DartConfiguration.tcl 仍未被改写

原因:BSD sed -i 语法与 GNU 不同,sed -i 's/.../' 可能未正确执行 in-place 替换。

解决方案:脚本中通过 sed --version 分支,macOS 使用 sed -i ''

问题 3:在 x86 构建机上直接 ctest 跑用例失败

现象:如 Syntax error: word unexpected(shell 误执行 ARM ELF)。

原因:测试二进制为 ARM/ARM64,不能在主机上直接跑。

解决方案:主机 check() 仅用 ctest -N;实跑在 OHOS 设备 构建目录下 ctest

问题 4:CMake 警告 ABSL_RUN_mobile_TEST 未使用

原因:上游未消费该变量。

解决方案:可忽略;保留该选项意在关闭不适用的移动端测试开关(若未来上游识别会生效)。

问题 5:升级 Abseil LTS 后补丁冲突

解决方案

  1. 干净 tarball 上 patch -p1 --dry-run
  2. 按本文「鸿蒙化补丁说明」分模块合并。
  3. 全量设备 CTest 回归。

参考链接

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐