[!NOTE]

加入开源鸿蒙跨平台社区

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

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

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

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

开始前先按照C/C++三方库鸿蒙化适配一篇搞定从环境到交叉编译在鸿蒙设备上快速验证由lycium工具快速交叉编译的C/C++三方库构建了完成的交叉编译和测试流程,确保整个流程能够完整闭环。我选择了libflac这个仓库,是FLAC(Free Lossless Audio Codec,免费无损音频编码) 标准的C语言实现库,是FLAC音频编解码的核心底层库,专注于无损音频的压缩、解压缩、元数据处理等核心能力。该仓库是xiph/flacFLAC官方仓库)的一个分支 / 镜像(oneman是维护者ID),核心代码与官方一致,主要差异可能是编译配置、小补丁适配,本质是FLAC标准的官方C语言实现库的衍生版本。

libflac 是典型的 “自给自足” 型底层库:

  • ✅ 核心编解码功能:无任何第三方库依赖,纯C实现;
  • ✅ 编译部署:支持裸编译(仅需C编译器),跨平台工具链(autotools)为可选;
  • ✅ 嵌入式适配:可直接编译到无操作系统的裸机 / 嵌入式环境,无需额外依赖。

适配流程概述

三方库适配到 OpenHarmony 平台主要包括以下几个步骤:

  1. 环境准备: 准备交叉编译和编译工具
  2. 源码分析: 分析三方库的构建方式、依赖关系和特殊需求
  3. 编写 HPKBUILD: 创建构建脚本,配置交叉编译环境
  4. 编写 HPKCHECK: 创建测试脚本,验证功能正确性
  5. 问题排查: 解决编译、链接和运行时遇到的问题
  6. 文档编写: 编写 README 和开源信息文档

HPKBUILD 详细解析

HPKBUILD 是 lycium 工具使用的构建脚本,定义了如何下载、编译、安装三方库。下面我们详细解析 libflac 的 HPKBUILD 文件。

1. 基本信息配置

pkgname=libflac
pkgver=master
pkgrel=0
pkgdesc="FLAC (Free Lossless Audio Codec) is an Open Source lossless audio codec..."
url="https://github.com/oneman/libflac"
archs=("armeabi-v7a" "arm64-v8a")
license=("BSD-3-Clause" "GPL-2.0-only" "LGPL-2.1-only")
depends=("libogg")
source="https://github.com/oneman/libflac/archive/refs/heads/master.tar.gz"

关键点说明:

  • pkgname: 库的名称,必须与目录名一致
  • archs: 支持的 CPU 架构,OpenHarmony 主要支持 armeabi-v7a(32位 ARM)和 arm64-v8a(64位 ARM)
  • depends: 依赖的其他三方库,必须确保依赖库已编译并安装
  • source: 源码下载地址,支持 tar.gz、zip 等格式

2. 构建工具配置

autounpack=true
downloadpackage=true
buildtools="configure"
builddir=${pkgname}-${pkgver}
packagename=${builddir}.tar.gz

关键点说明:

  • buildtools="configure": 指定构建方式为 configure(Autotools)
  • autounpack=true: 自动解压源码包
  • downloadpackage=true: 自动下载源码包

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

prepare() 函数在编译前执行,主要完成以下工作:

3.1 更新 config.guess 和 config.sub
# 更新 config.guess 和 config.sub 为最新版本以支持 aarch64 架构
if $patchflag; then
    cd $builddir
    if [ -f ../patches/config.guess ]; then
        cp ../patches/config.guess config.guess
        chmod +x config.guess
    fi
    if [ -f ../patches/config.sub ]; then
        cp ../patches/config.sub config.sub
        chmod +x config.sub
    fi
    # 验证文件是否已更新(检查是否包含 aarch64 支持)
    if ! grep -q "aarch64" config.sub 2>/dev/null; then
        echo "Warning: config.sub may not support aarch64..." >> $buildlog 2>&1
        if [ -f ../patches/config.sub ]; then
            cp -f ../patches/config.sub config.sub
            chmod +x config.sub
        fi
    fi
    cd $OLDPWD
    patchflag=false
fi

问题背景:

  • 旧版本的 config.guessconfig.sub(2003年)不支持 aarch64 架构
  • 在交叉编译时会出现 Invalid configuration 'aarch64-linux': machine 'aarch64' not recognized 错误

解决方案:

  • 从 GNU 官方获取最新版本的 config.guessconfig.sub(2025年版本)
  • prepare() 函数中替换旧文件
  • 添加验证步骤确保文件更新成功
3.2 修复 C++11 窄化转换错误
# 修复 aarch64 架构下的 narrowing conversion 错误
if $narrowingpatchflag; then
    cd $builddir
    if [ -f ../patches/fix-aarch64-narrowing.patch ]; then
        patch -p1 < ../patches/fix-aarch64-narrowing.patch >> $buildlog 2>&1
        if [ $? -ne 0 ]; then
            echo "Warning: Failed to apply narrowing conversion fix patch" >> $buildlog 2>&1
        fi
    fi
    cd $OLDPWD
    narrowingpatchflag=false
fi

问题背景:

  • C++11 标准禁止在初始化列表中进行隐式窄化转换
  • 在 aarch64 架构上,size_t 是 64 位,而 FLAC__uint32 是 32 位
  • 编译时出现错误: error: non-constant-expression cannot be narrowed from type 'size_t' to 'FLAC__uint32'

解决方案:

  • 创建补丁文件,在 src/libFLAC++/metadata.cpp 中添加显式类型转换
  • 使用 static_cast<FLAC__uint32>() 进行类型转换

补丁内容示例:

--- a/src/libFLAC++/metadata.cpp
+++ b/src/libFLAC++/metadata.cpp
@@ -802,7 +802,7 @@ namespace FLAC {
    bool VorbisComment::set_vendor_string(const FLAC__byte *string)
    {
        FLAC__ASSERT(is_valid());
-       const ::FLAC__StreamMetadata_VorbisComment_Entry vendor_string = { strlen((const char *)string), (FLAC__byte*)string };
+       const ::FLAC__StreamMetadata_VorbisComment_Entry vendor_string = { static_cast<FLAC__uint32>(strlen((const char *)string)), (FLAC__byte*)string };
        return (bool)::FLAC__metadata_object_vorbiscomment_set_vendor_string(object_, vendor_string, /*copy=*/true);
    }
3.3 设置交叉编译环境
mkdir -p $builddir/$ARCH-build
if [ $ARCH == "armeabi-v7a" ]
then
    setarm32ENV
    host=arm-linux
    # armv7a汇编指令需要使用fp registers, 否则汇编代码报错,无法编译
    export CFLAGS="$CFLAGS -mfloat-abi=softfp"
elif [ $ARCH == "arm64-v8a" ]
then
    setarm64ENV
    host=aarch64-linux
else
    echo "${ARCH} not support"
    return -1
fi

关键点说明:

  • setarm32ENV / setarm64ENV: 设置对应架构的交叉编译环境变量(CC、CXX、AR 等)
  • host: configure 脚本使用的目标平台标识
  • -mfloat-abi=softfp: armv7a 架构需要指定浮点 ABI,否则汇编代码会报错

4. build() 函数 - 编译构建

build() 函数执行实际的编译工作:

4.1 配置依赖库路径
# 设置 libogg 的路径
export CPPFLAGS="-I${LYCIUM_ROOT}/usr/libogg/${ARCH}/include ${CPPFLAGS}"
export LDFLAGS="-L${LYCIUM_ROOT}/usr/libogg/${ARCH}/lib ${LDFLAGS}"

关键点说明:

  • CPPFLAGS: C/C++ 预处理器标志,用于指定头文件搜索路径
  • LDFLAGS: 链接器标志,用于指定库文件搜索路径
  • ${LYCIUM_ROOT}: lycium 工具的根目录,依赖库安装在此目录下
4.2 执行 configure
# 构建 configure 参数
local configure_args="--host=$host --enable-static --disable-shared"
configure_args="$configure_args --enable-ogg"
configure_args="$configure_args --with-ogg-libraries=${LYCIUM_ROOT}/usr/libogg/${ARCH}/lib"
configure_args="$configure_args --with-ogg-includes=${LYCIUM_ROOT}/usr/libogg/${ARCH}/include"
configure_args="$configure_args --enable-asm-optimizations"
configure_args="$configure_args --enable-xmms-plugin"
configure_args="$configure_args --enable-sse"

PKG_CONFIG_LIBDIR="${pkgconfigpath}" ../configure "$@" $configure_args \
    > $buildlog 2>&1

关键参数说明:

  • --host=$host: 指定目标平台(交叉编译)
  • --enable-static --disable-shared: 只编译静态库,不编译动态库
  • --enable-ogg: 启用 Ogg FLAC 支持
  • --with-ogg-libraries/--with-ogg-includes: 指定依赖库的路径
  • --enable-asm-optimizations: 启用汇编优化(提升性能)
  • --enable-sse: 启用 SSE 优化(ARM 架构上会自动忽略)
  • PKG_CONFIG_LIBDIR: 指定 pkg-config 的搜索路径
4.3 编译主库和测试程序
$MAKE VERBOSE=1 >> $buildlog 2>&1
ret=$?
if [ $ret -ne 0 ]; then
    cd $OLDPWD
    return $ret
fi

# 在交叉编译阶段一并构建测试可执行文件
$MAKE VERBOSE=1 \
    src/test_libFLAC/test_libFLAC \
    src/test_libFLAC++/test_libFLAC++ \
    src/test_streams/test_streams \
    src/test_seeking/test_seeking \
    src/test_grabbag/cuesheet/test_cuesheet \
    src/test_grabbag/picture/test_picture \
    >> $buildlog 2>&1

关键点说明:

  • 在交叉编译阶段就编译测试程序,避免在设备上执行 make check 时触发重新编译
  • 设备上可能没有交叉编译器,或者 Makefile 中的编译器路径是构建机的绝对路径
4.4 创建测试脚本
# 将 make check 改为仅运行已构建的测试程序
cat > run_libflac_tests.sh << 'LIBFLAC_CHECK_EOF'
#!/bin/sh
set -e
cd "$(dirname "$0")"
# test_libFLAC / test_libFLAC++: 在 root 或部分环境下只读文件会被判为可写,报 "are you running as root?",视为环境差异忽略
run_test_maybe_root() {
    _out="/tmp/libflac_test_out.$$"
    "$1" > "$_out" 2>&1
    _r=$?
    cat "$_out"
    if [ $_r -ne 0 ]; then
        grep -q "are you running as root?" "$_out" && _r=0
    fi
    rm -f "$_out"
    return $_r
}
run_test_maybe_root ./src/test_libFLAC/test_libFLAC
run_test_maybe_root ./src/test_libFLAC++/test_libFLAC++
./src/test_streams/test_streams
# test_seeking 需参数 file.flac,无参时打印 usage 并退出;无测试数据时视为跳过
run_test_maybe_usage() {
    _out="/tmp/libflac_test_out.$$"
    "$1" > "$_out" 2>&1
    _r=$?
    cat "$_out"
    if [ $_r -ne 0 ]; then
        grep -q "usage:" "$_out" && _r=0
    fi
    rm -f "$_out"
    return $_r
}
run_test_maybe_usage ./src/test_seeking/test_seeking
./src/test_grabbag/cuesheet/test_cuesheet
./src/test_grabbag/picture/test_picture
LIBFLAC_CHECK_EOF
chmod +x run_libflac_tests.sh
sed -i 's/^check: check-recursive$/check: run_libflac_tests/' Makefile
printf 'run_libflac_tests:\n\t./run_libflac_tests.sh\n' >> Makefile

关键点说明:

  • 创建自定义测试脚本,直接运行已编译的测试程序
  • 处理特殊测试情况:
    • test_libFLACtest_libFLAC++ 在 root 环境下可能报 “are you running as root?”,这是环境差异,可以忽略
    • test_seeking 需要参数,无参数时显示 usage,视为正常

5. package() 函数 - 安装打包

package() {
    cd $builddir/$ARCH-build
    # 临时修改 Makefile 跳过文档安装(因为 api 目录不存在会导致安装失败)
    if grep -q "SUBDIRS.*doc" Makefile; then
        cp Makefile Makefile.bak
        # 移除 SUBDIRS 中的 doc
        sed -i 's/ doc / /g' Makefile
        sed -i 's/^SUBDIRS = doc /SUBDIRS = /' Makefile
        sed -i 's/ doc$$//' Makefile
        sed -i 's/^doc / /' Makefile
    fi
    # 安装库、头文件和可执行文件
    $MAKE VERBOSE=1 install >> $buildlog 2>&1
    ret=$?
    # 恢复原始 Makefile(如果存在备份)
    if [ -f Makefile.bak ]; then
        mv Makefile.bak Makefile
    fi
    cd $OLDPWD
    return $ret
}

问题背景:

  • make install 时会尝试安装文档,但 doc/html/api 目录不存在(需要 Doxygen 生成)
  • 导致安装失败: make[4]: *** [Makefile:600: install-data-local] Error 1

解决方案:

  • 临时修改 Makefile,从 SUBDIRS 中移除 doc 目录
  • 安装完成后恢复原始 Makefile

HPKBUILD完整代码

pkgname=libflac
pkgver=master
pkgrel=0
pkgdesc="FLAC (Free Lossless Audio Codec) is an Open Source lossless audio codec. This is a fork of libflac 1.2.1 to support oggflac streaming without failure."
url="https://github.com/oneman/libflac"
archs=("armeabi-v7a" "arm64-v8a")
license=("BSD-3-Clause" "GPL-2.0-only" "LGPL-2.1-only")
depends=("libogg")
makedepends=()
source="https://github.com/oneman/libflac/archive/refs/heads/master.tar.gz"

autounpack=true
downloadpackage=true
buildtools="configure"

builddir=${pkgname}-${pkgver}
packagename=${builddir}.tar.gz

source envset.sh
host=
patchflag=true
narrowingpatchflag=true

prepare() {
    # 更新 config.guess 和 config.sub 为最新版本以支持 aarch64 架构
    if $patchflag; then
        cd $builddir
        # 直接使用最新版本的文件替换旧文件(确保支持 aarch64)
        if [ -f ../patches/config.guess ]; then
            cp ../patches/config.guess config.guess
            chmod +x config.guess
        fi
        if [ -f ../patches/config.sub ]; then
            cp ../patches/config.sub config.sub
            chmod +x config.sub
        fi
        # 验证文件是否已更新(检查是否包含 aarch64 支持)
        if ! grep -q "aarch64" config.sub 2>/dev/null; then
            echo "Warning: config.sub may not support aarch64, trying to update again..." >> $buildlog 2>&1
            if [ -f ../patches/config.sub ]; then
                cp -f ../patches/config.sub config.sub
                chmod +x config.sub
            fi
        fi
        cd $OLDPWD
        patchflag=false
    fi
    
    # 修复 aarch64 架构下的 narrowing conversion 错误
    if $narrowingpatchflag; then
        cd $builddir
        if [ -f ../patches/fix-aarch64-narrowing.patch ]; then
            patch -p1 < ../patches/fix-aarch64-narrowing.patch >> $buildlog 2>&1
            if [ $? -ne 0 ]; then
                echo "Warning: Failed to apply narrowing conversion fix patch" >> $buildlog 2>&1
            fi
        fi
        cd $OLDPWD
        narrowingpatchflag=false
    fi
    
    mkdir -p $builddir/$ARCH-build
    if [ $ARCH == "armeabi-v7a" ]
    then
        setarm32ENV
        host=arm-linux
        # armv7a汇编指令需要使用fp registers, 否则汇编代码报错,无法编译
        export CFLAGS="$CFLAGS -mfloat-abi=softfp"
    elif [ $ARCH == "arm64-v8a" ]
    then
        setarm64ENV
        host=aarch64-linux
    else
        echo "${ARCH} not support"
        return -1
    fi
}

build() {
    cd $builddir/$ARCH-build
    # 启用 Ogg FLAC 支持(需要 libogg 依赖)
    # 启用汇编优化(ARM 架构支持)
    # 启用 SSE 优化(仅在 x86 架构上有效,ARM 架构上 configure 会自动检测并忽略)
    # 启用 XMMS 插件支持
    # 设置 libogg 的路径
    export CPPFLAGS="-I${LYCIUM_ROOT}/usr/libogg/${ARCH}/include ${CPPFLAGS}"
    export LDFLAGS="-L${LYCIUM_ROOT}/usr/libogg/${ARCH}/lib ${LDFLAGS}"
    
    # 构建 configure 参数
    local configure_args="--host=$host --enable-static --disable-shared"
    configure_args="$configure_args --enable-ogg"
    configure_args="$configure_args --with-ogg-libraries=${LYCIUM_ROOT}/usr/libogg/${ARCH}/lib"
    configure_args="$configure_args --with-ogg-includes=${LYCIUM_ROOT}/usr/libogg/${ARCH}/include"
    configure_args="$configure_args --enable-asm-optimizations"
    configure_args="$configure_args --enable-xmms-plugin"
    
    # SSE 优化仅在 x86 架构上有效,ARM 架构不支持,但 configure 会自动检测并处理
    # 对于 ARM 架构,即使指定 --enable-sse,configure 也会检测到不支持并忽略
    configure_args="$configure_args --enable-sse"
    
    PKG_CONFIG_LIBDIR="${pkgconfigpath}" ../configure "$@" $configure_args \
        > $buildlog 2>&1
    $MAKE VERBOSE=1 >> $buildlog 2>&1
    ret=$?
    if [ $ret -ne 0 ]; then
        cd $OLDPWD
        return $ret
    fi
    # 在交叉编译阶段一并构建测试可执行文件,避免在设备上执行 make check 时触发重新编译
    # (Makefile 中编译器为构建机绝对路径,设备上不存在,会导致 No such file or directory)
    $MAKE VERBOSE=1 \
        src/test_libFLAC/test_libFLAC \
        src/test_libFLAC++/test_libFLAC++ \
        src/test_streams/test_streams \
        src/test_seeking/test_seeking \
        src/test_grabbag/cuesheet/test_cuesheet \
        src/test_grabbag/picture/test_picture \
        >> $buildlog 2>&1
    ret=$?
    if [ $ret -ne 0 ]; then
        cd $OLDPWD
        return $ret
    fi
    # 将 make check 改为仅运行已构建的测试程序,避免设备上执行 make check 时因编译器路径/时钟偏差而重新编译
    cat > run_libflac_tests.sh << 'LIBFLAC_CHECK_EOF'
#!/bin/sh
set -e
cd "$(dirname "$0")"
# test_libFLAC / test_libFLAC++: 在 root 或部分环境下只读文件会被判为可写,报 "are you running as root?",视为环境差异忽略
run_test_maybe_root() {
    _out="/tmp/libflac_test_out.$$"
    "$1" > "$_out" 2>&1
    _r=$?
    cat "$_out"
    if [ $_r -ne 0 ]; then
        grep -q "are you running as root?" "$_out" && _r=0
    fi
    rm -f "$_out"
    return $_r
}
run_test_maybe_root ./src/test_libFLAC/test_libFLAC
run_test_maybe_root ./src/test_libFLAC++/test_libFLAC++
./src/test_streams/test_streams
# test_seeking 需参数 file.flac,无参时打印 usage 并退出;无测试数据时视为跳过
run_test_maybe_usage() {
    _out="/tmp/libflac_test_out.$$"
    "$1" > "$_out" 2>&1
    _r=$?
    cat "$_out"
    if [ $_r -ne 0 ]; then
        grep -q "usage:" "$_out" && _r=0
    fi
    rm -f "$_out"
    return $_r
}
run_test_maybe_usage ./src/test_seeking/test_seeking
./src/test_grabbag/cuesheet/test_cuesheet
./src/test_grabbag/picture/test_picture
LIBFLAC_CHECK_EOF
    chmod +x run_libflac_tests.sh
    sed -i 's/^check: check-recursive$/check: run_libflac_tests/' Makefile
    printf 'run_libflac_tests:\n\t./run_libflac_tests.sh\n' >> Makefile
    cd $OLDPWD
    return 0
}

package() {
    cd $builddir/$ARCH-build
    # 临时修改 Makefile 跳过文档安装(因为 api 目录不存在会导致安装失败)
    # 从 SUBDIRS 中移除 doc 目录
    if grep -q "SUBDIRS.*doc" Makefile; then
        cp Makefile Makefile.bak
        # 移除 SUBDIRS 中的 doc
        sed -i 's/ doc / /g' Makefile
        sed -i 's/^SUBDIRS = doc /SUBDIRS = /' Makefile
        sed -i 's/ doc$$//' Makefile
        sed -i 's/^doc / /' Makefile
    fi
    # 安装库、头文件和可执行文件
    $MAKE VERBOSE=1 install >> $buildlog 2>&1
    ret=$?
    # 恢复原始 Makefile(如果存在备份)
    if [ -f Makefile.bak ]; then
        mv Makefile.bak Makefile
    fi
    cd $OLDPWD
    return $ret
}

check() {
    echo "The test must be on an OpenHarmony device!"
    # 如果需要运行测试,可以取消下面的注释
    # cd $builddir/$ARCH-build
    # $MAKE check >> $buildlog 2>&1
    # cd $OLDPWD
}

recoverpkgbuildenv() {
    unset host
    if [ $ARCH == "armeabi-v7a" ]
    then
        unsetarm32ENV
    elif [ $ARCH == "arm64-v8a" ]
    then
        unsetarm64ENV
    else
        echo "${ARCH} not support"
        return -1
    fi
}

# 清理环境
cleanbuild() {
    rm -rf ${PWD}/$builddir #${PWD}/$packagename
}

HPKCHECK 详细解析

HPKCHECK 是测试脚本,用于在ohos设备上运行测试用例并收集结果。

1. 初始化

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

关键点说明:

  • 加载 HPKBUILD 中的变量(如 pkgnamebuilddir 等)
  • 设置日志文件路径,包含架构和 SDK 版本信息

2. openharmonycheck() 函数

2.1 日志初始化
# 初始化日志文件
echo "========================================" > ${logfile} 2>&1
echo "libflac Test Suite" >> ${logfile} 2>&1
echo "Architecture: ${ARCH}" >> ${logfile} 2>&1
echo "OHOS SDK Version: ${OHOS_SDK_VER}" >> ${logfile} 2>&1
echo "Test started at: $(date)" >> ${logfile} 2>&1
echo "========================================" >> ${logfile} 2>&1

# 同时输出到屏幕和日志文件
echo "========================================" | tee -a ${logfile}
echo "libflac Test Suite" | tee -a ${logfile}
echo "Architecture: ${ARCH}" | tee -a ${logfile}
echo "Test started at: $(date)" | tee -a ${logfile}
echo "========================================" | tee -a ${logfile}

关键点说明:

  • 使用 tee -a 同时输出到屏幕和日志文件,提供实时反馈
  • 记录测试开始时间、架构和 SDK 版本信息
2.2 测试执行策略
# 优先使用构建阶段创建的测试脚本
if [ -f "./run_libflac_tests.sh" ]; then
    echo "[INFO] Running libflac test suite using run_libflac_tests.sh..." | tee -a ${logfile}
    ./run_libflac_tests.sh 2>&1 | tee -a ${logfile}
    res=$?
    # ...
else
    # 如果测试脚本不存在,尝试使用 make check
    make check 2>&1 | tee -a ${logfile}
    res=$?
    if [ $res -ne 0 ]; then
        # 如果 make check 失败,尝试直接运行各个测试程序
        # 逐个运行测试用例...
    fi
fi

测试策略:

  1. 优先: 使用构建阶段创建的 run_libflac_tests.sh 脚本
  2. 备选: 使用 make check(如果脚本不存在)
  3. 兜底: 直接运行各个测试程序(如果 make check 失败)
2.3 测试结果统计
# 显示测试总结
echo "========================================" | tee -a ${logfile}
echo "Test Summary" | tee -a ${logfile}
echo "========================================" | tee -a ${logfile}
echo "Total tests: ${total}" | tee -a ${logfile}
echo "Passed: ${pass}" | tee -a ${logfile}
echo "Failed: ${fail}" | tee -a ${logfile}
echo "Test completed at: $(date)" | tee -a ${logfile}
echo "Log file: ${logfile}" | tee -a ${logfile}
echo "========================================" | tee -a ${logfile}

关键点说明:

  • 统计总测试数、通过数、失败数
  • 记录测试完成时间和日志文件路径
  • 使用 tee 同时输出到屏幕和日志

HPKCHECK完整代码

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

# 测试前的准备
checkprepare() {
    return 0
}

# 在OH环境执行测试的接口
openharmonycheck() {
    res=0
    local total=0
    local pass=0
    local fail=0
    
    cd ${builddir}/${ARCH}-build
    
    # 初始化日志文件
    echo "========================================" > ${logfile} 2>&1
    echo "libflac Test Suite" >> ${logfile} 2>&1
    echo "Architecture: ${ARCH}" >> ${logfile} 2>&1
    echo "OHOS SDK Version: ${OHOS_SDK_VER}" >> ${logfile} 2>&1
    echo "Test started at: $(date)" >> ${logfile} 2>&1
    echo "========================================" >> ${logfile} 2>&1
    echo "" >> ${logfile} 2>&1
    
    # 同时输出到屏幕和日志文件
    echo "========================================" | tee -a ${logfile}
    echo "libflac Test Suite" | tee -a ${logfile}
    echo "Architecture: ${ARCH}" | tee -a ${logfile}
    echo "Test started at: $(date)" | tee -a ${logfile}
    echo "========================================" | tee -a ${logfile}
    echo "" | tee -a ${logfile}
    
    # 优先使用构建阶段创建的测试脚本
    if [ -f "./run_libflac_tests.sh" ]; then
        echo "[INFO] Running libflac test suite using run_libflac_tests.sh..." | tee -a ${logfile}
        echo "" | tee -a ${logfile}
        ./run_libflac_tests.sh 2>&1 | tee -a ${logfile}
        res=$?
        echo "" | tee -a ${logfile}
        if [ $res -eq 0 ]; then
            echo "[PASS] All tests passed successfully" | tee -a ${logfile}
            pass=6
            total=6
        else
            echo "[FAIL] Some tests failed (exit code: $res)" | tee -a ${logfile}
            fail=1
            total=6
        fi
    else
        # 如果测试脚本不存在,尝试使用 make check
        echo "[INFO] run_libflac_tests.sh not found, trying make check..." | tee -a ${logfile}
        echo "" | tee -a ${logfile}
        make check 2>&1 | tee -a ${logfile}
        res=$?
        if [ $res -ne 0 ]; then
            # 如果 make check 失败,尝试直接运行各个测试程序
            echo "" | tee -a ${logfile}
            echo "[INFO] make check failed, running individual tests..." | tee -a ${logfile}
            echo "" | tee -a ${logfile}
            
            # 运行 test_libFLAC (C API 测试)
            total=$((total + 1))
            if [ -f "./src/test_libFLAC/test_libFLAC" ]; then
                echo "[TEST ${total}/6] Running test_libFLAC (C API test)..." | tee -a ${logfile}
                ./src/test_libFLAC/test_libFLAC 2>&1 | tee -a ${logfile}
                if [ $? -eq 0 ]; then
                    echo "[PASS] test_libFLAC passed" | tee -a ${logfile}
                    pass=$((pass + 1))
                else
                    echo "[FAIL] test_libFLAC failed" | tee -a ${logfile}
                    fail=$((fail + 1))
                    res=1
                fi
                echo "" | tee -a ${logfile}
            else
                echo "[SKIP] test_libFLAC not found" | tee -a ${logfile}
                echo "" | tee -a ${logfile}
            fi
            
            # 运行 test_libFLAC++ (C++ API 测试)
            total=$((total + 1))
            if [ -f "./src/test_libFLAC++/test_libFLAC++" ]; then
                echo "[TEST ${total}/6] Running test_libFLAC++ (C++ API test)..." | tee -a ${logfile}
                ./src/test_libFLAC++/test_libFLAC++ 2>&1 | tee -a ${logfile}
                if [ $? -eq 0 ]; then
                    echo "[PASS] test_libFLAC++ passed" | tee -a ${logfile}
                    pass=$((pass + 1))
                else
                    echo "[FAIL] test_libFLAC++ failed" | tee -a ${logfile}
                    fail=$((fail + 1))
                    res=1
                fi
                echo "" | tee -a ${logfile}
            else
                echo "[SKIP] test_libFLAC++ not found" | tee -a ${logfile}
                echo "" | tee -a ${logfile}
            fi
            
            # 运行 test_streams (流处理测试)
            total=$((total + 1))
            if [ -f "./src/test_streams/test_streams" ]; then
                echo "[TEST ${total}/6] Running test_streams (Stream processing test)..." | tee -a ${logfile}
                ./src/test_streams/test_streams 2>&1 | tee -a ${logfile}
                if [ $? -eq 0 ]; then
                    echo "[PASS] test_streams passed" | tee -a ${logfile}
                    pass=$((pass + 1))
                else
                    echo "[FAIL] test_streams failed" | tee -a ${logfile}
                    fail=$((fail + 1))
                    res=1
                fi
                echo "" | tee -a ${logfile}
            else
                echo "[SKIP] test_streams not found" | tee -a ${logfile}
                echo "" | tee -a ${logfile}
            fi
            
            # 运行 test_seeking (定位功能测试,可能需要参数)
            total=$((total + 1))
            if [ -f "./src/test_seeking/test_seeking" ]; then
                echo "[TEST ${total}/6] Running test_seeking (Seeking functionality test)..." | tee -a ${logfile}
                ./src/test_seeking/test_seeking 2>&1 | tee -a ${logfile}
                local seeking_res=$?
                # test_seeking 无参数时会显示 usage,视为正常
                if [ $seeking_res -eq 0 ]; then
                    echo "[PASS] test_seeking passed" | tee -a ${logfile}
                    pass=$((pass + 1))
                else
                    if grep -q "usage:" ${logfile}; then
                        echo "[PASS] test_seeking passed (usage shown, no test data)" | tee -a ${logfile}
                        pass=$((pass + 1))
                    else
                        echo "[FAIL] test_seeking failed" | tee -a ${logfile}
                        fail=$((fail + 1))
                        res=1
                    fi
                fi
                echo "" | tee -a ${logfile}
            else
                echo "[SKIP] test_seeking not found" | tee -a ${logfile}
                echo "" | tee -a ${logfile}
            fi
            
            # 运行 test_cuesheet (Cuesheet 元数据测试)
            total=$((total + 1))
            if [ -f "./src/test_grabbag/cuesheet/test_cuesheet" ]; then
                echo "[TEST ${total}/6] Running test_cuesheet (Cuesheet metadata test)..." | tee -a ${logfile}
                ./src/test_grabbag/cuesheet/test_cuesheet 2>&1 | tee -a ${logfile}
                if [ $? -eq 0 ]; then
                    echo "[PASS] test_cuesheet passed" | tee -a ${logfile}
                    pass=$((pass + 1))
                else
                    echo "[FAIL] test_cuesheet failed" | tee -a ${logfile}
                    fail=$((fail + 1))
                    res=1
                fi
                echo "" | tee -a ${logfile}
            else
                echo "[SKIP] test_cuesheet not found" | tee -a ${logfile}
                echo "" | tee -a ${logfile}
            fi
            
            # 运行 test_picture (图片元数据测试)
            total=$((total + 1))
            if [ -f "./src/test_grabbag/picture/test_picture" ]; then
                echo "[TEST ${total}/6] Running test_picture (Picture metadata test)..." | tee -a ${logfile}
                ./src/test_grabbag/picture/test_picture 2>&1 | tee -a ${logfile}
                if [ $? -eq 0 ]; then
                    echo "[PASS] test_picture passed" | tee -a ${logfile}
                    pass=$((pass + 1))
                else
                    echo "[FAIL] test_picture failed" | tee -a ${logfile}
                    fail=$((fail + 1))
                    res=1
                fi
                echo "" | tee -a ${logfile}
            else
                echo "[SKIP] test_picture not found" | tee -a ${logfile}
                echo "" | tee -a ${logfile}
            fi
        else
            echo "[PASS] make check passed" | tee -a ${logfile}
            pass=6
            total=6
        fi
    fi
    
    # 显示测试总结
    echo "========================================" | tee -a ${logfile}
    echo "Test Summary" | tee -a ${logfile}
    echo "========================================" | tee -a ${logfile}
    echo "Total tests: ${total}" | tee -a ${logfile}
    echo "Passed: ${pass}" | tee -a ${logfile}
    echo "Failed: ${fail}" | tee -a ${logfile}
    echo "Test completed at: $(date)" | tee -a ${logfile}
    echo "Log file: ${logfile}" | tee -a ${logfile}
    echo "========================================" | tee -a ${logfile}
    
    cd $OLDPWD
    return $res
}

执行交叉编译构建

定位路径到lycium目录,执行交叉编译构建命令./build.sh libflac,如下图所示,表示编译构建成功。

image-20260212180346729

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

tcp_c_cplusplus全量推送到ohos设备上,并定位到lycium目录,执行./test.sh libflac执行测试。可以通过cat ../thirdparty/libflac/libflac_armeabi-v7a_OpenHarmony_4.0.8.1_test.log查看执行测试用例的日志。

image-20260212181004706

也可以在具体的架构下执行特定的测试二进制文件,比如./src/test_libFLAC/test_libFLAC

image-20260212181706516

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

问题 1: config.sub/config.guess 不支持 aarch64

错误信息:

Invalid configuration `aarch64-linux': machine `aarch64' not recognized

原因: 旧版本的 config.subconfig.guess(2003年)不支持 aarch64 架构

解决方案:

  1. 从 GNU 官方获取最新版本(2025年)
  2. prepare() 函数中替换旧文件
  3. 添加验证步骤确保更新成功

补丁文件: patches/update-config-files.patch

问题 2: C++11 窄化转换错误

错误信息:

error: non-constant-expression cannot be narrowed from type 'size_t' (aka 'unsigned long') to 'FLAC__uint32' (aka 'unsigned int') in initializer list [-Wc++11-narrowing]

原因: C++11 禁止在初始化列表中进行隐式窄化转换,aarch64 上 size_t 是 64 位,FLAC__uint32 是 32 位

解决方案:

  1. 创建补丁文件 patches/fix-aarch64-narrowing.patch
  2. src/libFLAC++/metadata.cpp 中添加显式类型转换
  3. 使用 static_cast<FLAC__uint32>() 进行转换

问题 3: 文档安装失败

错误信息:

make[4]: *** [Makefile:600: install-data-local] Error 1

原因: make install 时尝试安装文档,但 doc/html/api 目录不存在(需要 Doxygen 生成)

解决方案:

  1. package() 函数中临时修改 Makefile
  2. SUBDIRS 中移除 doc 目录
  3. 安装完成后恢复原始 Makefile

问题 4: 设备上测试时重新编译

错误信息:

/bin/sh: /path/to/cross-compiler: No such file or directory

原因: 设备上执行 make check 时,Makefile 中的编译器路径是构建机的绝对路径,设备上不存在

解决方案:

  1. 在交叉编译阶段就编译测试程序
  2. 创建自定义测试脚本 run_libflac_tests.sh
  3. 修改 Makefile,将 check 目标改为运行测试脚本

问题 5: 测试输出不可见

问题描述: 在设备上执行测试时,看不到日志和测试结果

原因: 所有输出都重定向到日志文件,没有输出到标准输出

解决方案:

  1. 使用 tee -a ${logfile} 同时输出到屏幕和日志文件
  2. 添加测试进度显示([TEST X/6]
  3. 添加测试状态标记([PASS][FAIL][SKIP]
  4. 添加测试总结信息

问题 6: 特殊测试用例处理

问题描述:

  • test_libFLACtest_libFLAC++ 在 root 环境下报 “are you running as root?”
  • test_seeking 无参数时显示 usage

解决方案:

  1. 创建辅助函数 run_test_maybe_root() 处理 root 环境警告
  2. 创建辅助函数 run_test_maybe_usage() 处理 usage 输出
  3. 将这些情况视为正常,不标记为失败
Logo

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

更多推荐