系列导读:本文是 Lycium 适配系列的第三篇,也是最核心的一篇。适配工作的 80% 都在编写 HPKBUILD,本文将深入讲解每一个字段和函数,以及三种构建系统的典型写法。


欢迎加入【开源鸿蒙PC社区】,一起共建鸿蒙化C/C++三方库生态。

前言

项目 说明
macOS环境配置 https://bxming.blog.csdn.net/article/details/159284830
Ubuntu环境配置(或者windows + wsl) https://bxming.blog.csdn.net /article/details/159284760
lycium框架 https://atomgit.com/OpenHarmonyPCDeveloper/lycium_plusplus.git
应用平台 HarmonyOS PC

系列索引

篇章 标题 内容
第一篇 概述与环境配置 Lycium 概念、构建机要求、OHOS SDK 配置
第二篇 项目结构与适配目录创建 目录结构、community vs thirdparty、创建适配目录
第三篇 HPKBUILD 编写详解 元数据字段、过程函数、三种构建系统写法
第四篇 构建执行与产物获取 构建流程、日志分析、多库递归、HAP 集成
第五篇 流程图与角色职责 完整流程图、各角色职责、协作时序
第六篇 关键注意事项与最佳实践 依赖管理、架构超集、日志调试、外部适配仓
第七篇 快速参考与模板 入门步骤、模板、完整案例、检查清单

1. HPKBUILD 概览

HPKBUILD 是 Lycium 适配的灵魂文件。它本质上是一个 Bash 脚本,由两部分组成:

HPKBUILD = 声明式元数据 + 过程式 shell 函数
  • 声明式元数据:用变量告诉框架"这是谁、从哪下载、依赖什么"
  • 过程式函数:用 shell 函数告诉框架"怎么编译、怎么安装"

当执行 ./build.sh mylib 时,Lycium 框架按以下顺序执行:

  1. 解析元数据(读取 pkgnamearchsdepends 等)
  2. 为每个架构依次调用 prepare()build()package()
  3. 其中 build() 执行时,框架已注入了交叉编译器环境变量和依赖搜索路径

2. 元数据字段详解

2.1 基本信息

字段 必填 类型 说明 示例
pkgname string 库名,也是安装目录名 mylib
pkgver string 库版本,与上游 tag 一致 1.2.3
pkgrel integer 发布号,同一版本多次适配时递增 012
pkgdesc string 库描述,简短说明用途 "A JSON parsing library"
url string 官网/仓库链接 "https://github.com/..."

pkgrel 的使用场景:当 pkgver 不变,但 HPKBUILD 内容有修改(如修复了交叉编译参数、更新了 patch),递增 pkgrel 即可。例如第一次适配为 pkgrel=0,后续修复为 pkgrel=1

2.2 架构声明

archs=("armeabi-v7a" "arm64-v8a")

规则

  • 框架会为 archs[] 中每个架构顺序执行一遍完整的构建流程
  • 每个架构独立构建、独立安装到 usr/<pkgname>/<arch>/ 目录下
  • 常见组合:只做 arm64-v8a(最主流)、或同时支持 armeabi-v7aarm64-v8a

2.3 许可证

license=("MIT")
# 或
license=("Apache-2.0" "BSD-3-Clause")

注意事项

  • 遵循 SPDX 许可证标识符规范(MITApache-2.0GPL-2.0-only 等)
  • 多许可证时用数组列出所有
  • 必须与 README.OpenSource 中的声明一致

2.4 依赖声明

depends=("libA" "libB")      # 运行时依赖
makedepends=("cmake")        # 构建工具依赖
  • depends[]:运行时依赖库的 pkgname必须与对应 HPKBUILD 的 pkgname 完全一致
  • makedepends[]:构建时需要的命令。框架会在构建前用 which 检测这些命令是否存在。
  • 框架对 depends[] 的处理:
    1. 检查依赖库是否已构建(通过 usr/hpk_build.csv 记录)
    2. 若未构建,退出码 101 → 外层 build.sh 自动先构建依赖库
    3. 依赖库的产物会被挂载到搜索路径(通过 $buildargs$pkgconfigpath

2.5 源码相关

source="https://github.com/example/$pkgname/archive/refs/tags/v$pkgver.tar.gz"
builddir=$pkgname-$pkgver
packagename=$builddir.tar.gz
downloadpackage=true     # 默认 true,框架自动 wget 下载
autounpack=true          # 默认 true,框架自动识别格式解压
字段 必填 说明 技巧
source 源码下载 URL,支持变量替换$pkgname$pkgver GitHub 上 <name>/archive/refs/tags/v<ver>.tar.gz 是常见模式
builddir 解压后的目录名,构建时 cd 进入 通常 = $pkgname-$pkgver,但有时 tag 不含 v 前缀,需自行调整
packagename 下载后的压缩包文件名 通常 = $builddir.tar.gz
downloadpackage 是否自动下载,默认 true 设为 false 时需在 prepare() 中手动处理源码
autounpack 是否自动解压,默认 true 设为 false 时需在 prepare() 中手动解压

变量替换机制source 中的 $pkgname$pkgver 会在运行时被实际值替换,无需硬编码。例如:

pkgname=mylib
pkgver=1.2.3
source="https://github.com/example/$pkgname/archive/refs/tags/v$pkgver.tar.gz"
# 实际下载:https://github.com/example/mylib/archive/refs/tags/v1.2.3.tar.gz

2.6 构建系统类型

buildtools=cmake   # 或 configure / make
含义 框架行为
留空 或 cmake CMake 项目 生成 -DCMAKE_FIND_ROOT_PATH=... -DCMAKE_TOOLCHAIN_FILE=...$buildargs
configure autotools 项目 生成 --prefix=...$buildargs
make 纯 Makefile 项目 框架只设置编译器环境变量,不自动注入额外的 $buildargs

3. 过程函数详解

3.1 必备三函数

prepare() — 预处理

build() 之前调用,每架构一次。典型任务:

prepare() {
    # 1. 创建 out-of-source 构建目录(CMake 项目推荐)
    mkdir -p $builddir/$ARCH-build

    # 2. 应用 patch
    cd $builddir
    patch -p1 < ${OLDPWD}/../mylib_oh_pkg.patch
    cd $OLDPWD

    # 3. 设置软链接(某些库需要特定目录结构)
    # ln -sf ... ...

    # 4. 生成必要的配置文件
    # autoreconf -fi   # autotools 项目需要
}
build() — 编译(核心)

每个架构执行一次,框架已设置好所有编译环境变量:

变量 说明
$CC 交叉编译器(如 aarch64-linux-ohos-clang
$CFLAGS 交叉编译标志(含 --sysroot
$ARCH 当前架构名(如 arm64-v8a
$buildargs 框架自动生成的构建参数(详见下文 3.3)
$buildlog 当前架构的详细日志文件路径
$pkgconfigpath 所有依赖库的 pkgconfig 路径(冒号分隔)
$MAKE 默认 make -j$(nproc)
build() {
    cd $builddir
    # CMake 项目(推荐 out-of-source 构建)
    ${OHOS_SDK}/native/build-tools/cmake/bin/cmake "$@" \
        -DOHOS_ARCH=$ARCH -B$ARCH-build -S./ -L > $buildlog 2>&1
    $MAKE -C $ARCH-build >> $buildlog 2>&1
    ret=$?
    cd $OLDPWD
    return $ret
}

$buildlog 的最佳实践build() 中第一次命令用 >(覆盖),后续用 >>(追加),确保日志文件保存完整的历史记录。

package() — 安装
package() {
    cd $builddir
    $MAKE -C $ARCH-build install >> $buildlog 2>&1
    cd $OLDPWD
}

框架在调用 package() 前已设置了 DESTDIRCMAKE_INSTALL_PREFIX,产物自动安装到 $LYCIUM_ROOT/usr/$pkgname/$ARCH/

3.2 辅助函数

check() — 验证

描述如何在 OH 设备上手动验证库的功能正确性,框架不会自动执行。

check() {
    # 示例:cJSON 的 check 说明
    echo "在 OH 设备上执行以下命令验证:"
    echo "  1. 编写一个简单测试程序,包含 cJSON.h"
    echo "  2. 链接 libcjson.so"
    echo "  3. 运行并检查输出"
}
archive() — 归档产物

将当前架构的编译产物打包为 tar.gz 归档,用于跨架构复用,避免重复编译。

archive() {
    # 默认归档位置:output/<arch>/<pkgname>_<ver>.tar.gz
    # 框架会自动将归档包安装到 target 目录
    # 如需自定义打包逻辑,在此函数内实现
    tar -czf ${OUTPUTDIR}/${arch}/${pkgname}_${pkgver}.tar.gz \
        -C ${INSTALLDIR} usr/
}

归档策略:如果库是纯 C 静态库且不依赖特定架构特性,可以在 arm64-v8a 构建后归档,然后在 armeabi-v7aprepare() 中检查并使用该归档,跳过编译。

install_local() — 鸿蒙本机构建安装

当使用 build_local.sh(DevBox 设备内构建)时调用,将编译产物安装到设备系统路径。

install_local() {
    # 安装头文件
    cp -r include/* /system/library/include/ 2>/dev/null || true
    # 安装动态库
    cp -f $builddir/libcjson.so /system/library/ 2>/dev/null || true
}
cleanbuild() — 清理(必填)
cleanbuild() {
    # 清理构建目录
    rm -rf ${builddir}/$ARCH-build
    # 清理编译产物
    find . -type f -name '*.o' -delete
}
recoverpkgbuildenv() — 恢复环境变量

prepare()build() 中临时修改了环境变量时,在此函数中恢复。

recoverpkgbuildenv() {
    # 示例:如果在 prepare 中 export 了临时变量,在此处 unset
    unset TEMP_VAR
}

3.3 $buildargs 的内容

框架根据 buildtools 自动生成 $buildargs

构建系统 $buildargs 内容
cmake -DCMAKE_FIND_ROOT_PATH="<dep1>;<dep2>..." -DCMAKE_TOOLCHAIN_FILE=<path> -DCMAKE_INSTALL_PREFIX=<path> -G "Unix Makefiles" -DOHOS_ARCH=<arch>
configure --prefix=$LYCIUM_ROOT/usr/$pkgname/$ARCH/
make 空(框架只设置编译器环境变量)

注意:如果需要在 $buildargs 中添加自定义参数(如 -DBUILD_SHARED_LIBS=ON),可以在 build() 函数中用变量追加:

export buildargs="$buildargs -DBUILD_SHARED_LIBS=ON"

同时 $pkgconfigpath 变量被设置为所有依赖库的 lib/pkgconfig 路径(冒号分隔)。

在 build() 中使用 $buildargs

# CMake 项目
cmake "$@" $buildargs ...    # "$@" 展开为 $buildargs,再加自定义参数

# configure 项目
./configure $buildargs ...

4. 三种构建系统的典型写法

4.1 CMake 项目(最常用)

适用场景:大多数现代 C/C++ 库(cJSON、libcurl、protobuf 等)

# HPKBUILD
pkgname=xx
buildtools=cmake  # 不写或写 cmake

prepare() {
    mkdir -p $builddir/$ARCH-build
}

build() {
    cd $builddir
    PKG_CONFIG_LIBDIR="${pkgconfigpath}" \
    ${OHOS_SDK}/native/build-tools/cmake/bin/cmake "$@" \
        -DOHOS_ARCH=$ARCH \
        -B$ARCH-build -S./ -L > $buildlog 2>&1
    $MAKE -C $ARCH-build >> $buildlog 2>&1
    ret=$?
    cd $OLDPWD
    return $ret
}

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

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

CMake 构建的常见问题

问题 解决方案
找不到依赖库 框架通过 $buildargs 注入 -DCMAKE_FIND_ROOT_PATH,但有些库要用 find_package 的特定变量,可能需要额外 -D<PKG>_DIR=...
找不到 pkg-config 模块 设置 PKG_CONFIG_LIBDIR="${pkgconfigpath}" 传递给 cmake 的 build() 环境
编译器检测错误 显式指定 -DCMAKE_C_COMPILER=$CC -DCMAKE_CXX_COMPILER=$CXX
测试编译本机代码 add -DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY 避免链接测试

4.2 configure(autotools)项目

适用场景:历史悠久的老牌 C 库(ncurses、readline、libffi 等)

# HPKBUILD
pkgname=xx
buildtools=configure
autounpack=true

prepare() {
    cd $builddir
    # 有些库需要先生成 configure 脚本
    # autoreconf -fi
    cd $OLDPWD
}

build() {
    cd $builddir
    # 有时需要设置 PKG_CONFIG_PATH
    # export PKG_CONFIG_PATH="${pkgconfigpath}"
    ./configure "$@" $buildargs \
        --host=${ARCH_TRIPLE} \
        --target=${ARCH_TRIPLE} \
        CC=$CC CXX=$CXX \
        CFLAGS="$CFLAGS" \
        LDFLAGS="$LDFLAGS" \
        > $buildlog 2>&1
    $MAKE >> $buildlog 2>&1
    ret=$?
    cd $OLDPWD
    return $ret
}

package() {
    cd $builddir
    $MAKE install >> $buildlog 2>&1
    cd $OLDPWD
}

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

autotools 项目的关键点

  • archs 变量会在运行时导出为 OHOS_ARCH,可在 envset.sh 中查看到对应的 ARCH_TRIPLE(如 aarch64-linux-ohos
  • --host 必须设置为交叉编译的目标三元组
  • 很多 autotools 项目需要 autoreconf -fi 重新生成 configure 脚本,因为上游的 configure 可能不支持交叉编译到 OH
  • 某些项目的 configure 脚本会检测 /dev/random 等 Linux 特有路径,需要 patch 或设置环境变量绕过

4.3 纯 Makefile 项目

适用场景:简单的 C 库,或非标准构建系统的库

# HPKBUILD
pkgname=xx
buildtools=make

prepare() {
    mkdir -p $builddir/$ARCH-build
}

build() {
    cd $builddir
    # $MAKE 默认带 -j$(nproc),${MAKE_FLAGS} 可自定义
    $MAKE CC=$CC CXX=$CXX AR=$AR \
        CFLAGS="$CFLAGS" \
        LDFLAGS="$LDFLAGS" \
        >> $buildlog 2>&1
    ret=$?
    cd $OLDPWD
    return $ret
}

package() {
    cd $builddir
    # make install 时需要指定安装前缀
    $MAKE DESTDIR=$LYCIUM_ROOT/usr/$pkgname/$ARCH/ \
        PREFIX=/ install >> $buildlog 2>&1
    cd $OLDPWD
}

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

纯 Makefile 项目的注意事项

  • 必须手动传递 CCCFLAGS:框架的环境变量不自动注入到 Makefile 中,需要在 make 命令行或通过 export 传递
  • 安装前缀要小心:很多 Makefile 用 PREFIX 而不是 DESTDIR,甚至用自定义变量
  • make -n 查看 Makefile 实际使用了哪些变量,再决定传入什么
  • 如果 Makefile 编译选项固定(硬编码了 gcc),需要 patch 修改或通过 override 传入

5. 构建流程中的可用变量速查

在 HPKBUILD 的 prepare()build()package() 中,框架已设置以下变量供使用:

变量 来源 说明
$OHOS_ARCH envset.sh 当前架构(如 arm64-v8a),与 $ARCH 相同
$ARCH_TRIPLE envset.sh 编译器三元组(如 aarch64-linux-ohos
$CC / $CXX envset.sh 交叉编译器路径
$CFLAGS / $CXXFLAGS envset.sh 编译标志(含 --sysroot
$LDFLAGS envset.sh 链接标志
$AR / $LD / $STRIP envset.sh LLVM 工具路径
$MAKE build_hpk.sh make -j$(nproc)
$buildargs build_hpk.sh 框架自动生成的构建参数
$pkgconfigpath build_hpk.sh 所有依赖库的 pkgconfig 路径
$buildlog build_hpk.sh 当前架构的日志文件路径
$LYCIUM_ROOT build.sh Lycium 框架根目录
$OLDPWD Bash 内置 进入 $builddir 前的目录

下篇预告

HPKBUILD 编写完成后,下一步就是执行构建。下一篇将介绍构建执行的完整流程、构建日志分析、多库递归依赖编译、产物获取以及 HAP 集成。

Logo

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

更多推荐