4、构建系统初探:hb工具入门、build.sh脚本分析、产品配置与编译命令解析

好,咱们正式开始动手了。前面几章聊了那么多概念和架构,说实话,光看不动手,心里总是没底。这一章我们就来点实在的——把OpenHarmony的构建系统跑一遍,看看它到底是怎么工作的。

我个人习惯,学一个新系统,先找它的入口。OpenHarmony的构建入口,说白了就是两个东西:hb工具build.sh脚本。一个是对外封装的命令行工具,一个是底层的shell脚本。咱们一个一个来。

4.1 hb工具:你与构建系统的第一道门

hb 是 "harmony build" 的缩写。你想想看,一个这么大的系统,总不能每次都去手动敲一堆复杂的gn和ninja命令吧?hb就是帮你把这些脏活累活包起来的那个“管家”。

我第一次接触hb时,第一反应就是敲 hb --help。嗯,这习惯救过我很多次。你会看到类似这样的输出:

usage: hb [-h] [--version] <command> ...

positional arguments:
  command
    build       构建OpenHarmony
    set         设置产品配置
    env         显示当前环境
    clean       清理构建产物
    tool        调用底层工具(gn/ninja)

常用的就这几个命令。我个人觉得,hb sethb build 是使用频率最高的两个,占了日常工作的90%。

4.1.1 hb set:选产品,定乾坤

构建之前,你得告诉系统:我要给哪个设备编译?是开发板?还是手机?还是智能家居设备?

hb set 就是干这个的。它会扫描你源码目录下的 productdefine 文件夹,列出所有可用的产品配置。运行后,你会看到一个交互式菜单:

OHOS Which product do you need? (Use arrow keys)
 > hi3516dv300
   rk3568
   qemu_virt_mini
   ...

选好之后,hb会帮你设置好环境变量,比如 OHOS_BUILD_HOMEOHOS_PRODUCT 这些。你不需要手动去export,它全包了。

小技巧: 如果你不想用交互式菜单,可以直接指定产品名:hb set -p hi3516dv300。这在自动化脚本里特别有用。

4.1.2 hb build:一键编译

设置好产品后,直接敲 hb build,系统就开始编译了。它会自动调用gn生成ninja文件,然后执行ninja进行编译。

这里有个细节:hb build 默认是增量编译。如果你改了代码,它只会编译受影响的部分。我第一次用的时候没注意,以为每次都是全量编译,结果发现第二次编译快得离谱——嗯,这就是ninja的功劳。

如果你想强制全量编译,可以加 -f 参数:

hb build -f

这个命令会清空之前的构建产物,重新生成。我建议你在切换产品或者修改了全局配置后,用一次全量编译,避免一些奇怪的缓存问题。

4.2 build.sh脚本:剥开hb的外衣

hb虽然好用,但它毕竟是个封装。有时候出了问题,你得知道它背后到底调了什么。这时候,build.sh 就是你的“底牌”。

打开源码根目录下的 build.sh,你会发现它其实就是一个shell脚本,核心逻辑并不复杂。我摘一段关键代码给你看:

# 设置环境变量
export OHOS_BUILD_HOME=$(pwd)
export OHOS_PRODUCT=${product}

# 调用gn生成ninja文件
gn gen out/${product} --args="ohos_product=\"${product}\" ..."

# 调用ninja执行编译
ninja -C out/${product} -j${jobs}

看到了吗?核心就三步:

  1. 设置环境变量:告诉系统当前目录和产品名。
  2. gn gen:根据产品配置生成ninja构建文件。
  3. ninja:真正执行编译。

我曾经遇到过一个坑:在某个CI环境里,hb工具因为Python版本问题跑不起来。当时我直接改写了build.sh,绕过了hb,手动调gn和ninja,硬是把编译跑通了。所以,理解build.sh,关键时刻能救命。

注意: 直接运行build.sh时,确保你已经用 hb set 设置过产品,或者手动传入 --product 参数。否则它会报错说找不到产品配置。

4.3 产品配置:构建的“配方”

产品配置,说白了就是一份“配方”。它告诉构建系统:你要编译哪些组件?用哪个内核?启动流程是什么样的?

产品配置文件通常放在 productdefine/common/products 目录下,以 .json.gni 格式存在。我拿 hi3516dv300.json 举个例子:

{
  "product_name": "hi3516dv300",
  "product_company": "hisilicon",
  "product_device": "hi3516dv300",
  "type": "standard",
  "parts": {
    "common": ["init", "hdf", "hilog"],
    "hisilicon": ["hdf_hisilicon", "kernel_linux"]
  }
}

这里的关键字段是 parts。它定义了当前产品需要包含哪些子系统(subsystem)和组件(component)。比如 init 是启动管理,hdf 是驱动框架,hilog 是日志系统。

你想想看,如果我要做一个新的开发板,我只需要复制一份这个json,修改 product_deviceparts,就能定义出一套全新的产品。这就是OpenHarmony的模块化设计思想。

4.4 编译命令解析:从gn到ninja

好,现在我们把目光聚焦到最底层:gn和ninja。这两个工具是构建系统的“发动机”。

4.4.1 gn:生成ninja文件

gn(Generate Ninja)是一个元构建系统。它不直接编译代码,而是读取 .gn.gni 文件,生成ninja能识别的 .ninja 文件。

一个典型的gn构建规则长这样:

# BUILD.gn
executable("hello_world") {
  sources = ["main.c"]
  deps = ["//utils/lib:libutils"]
}

这段代码的意思是:编译一个名为 hello_world 的可执行文件,源文件是 main.c,依赖 utils/lib 这个库。

gn的强大之处在于它的条件编译能力。比如,你可以根据产品类型决定是否包含某个组件:

if (ohos_product == "hi3516dv300") {
  deps += ["//drivers/hdf_core"]
}

我在项目中遇到过一个问题:某个组件在A产品上编译正常,在B产品上却报链接错误。最后发现是gn文件里少了一个条件判断,导致B产品没有链接必要的库。所以,写gn文件时,一定要考虑不同产品的差异。

4.4.2 ninja:真正的“搬砖工”

ninja是一个极简的构建工具,它的设计哲学就是“快”。它不处理复杂的逻辑,只负责执行gn生成的构建指令。

ninja的构建文件是 build.ninja,里面全是类似这样的规则:

rule cc
  command = gcc -c $in -o $out
  description = CC $out

build out/main.o: cc main.c

说白了,ninja就是按照依赖关系,一条一条地执行编译命令。它支持并行编译,默认会使用所有CPU核心。你可以用 -j 参数控制并行度:

ninja -j4

嗯,这里要注意:如果你的机器内存不大,别把 -j 设得太高,否则编译时内存爆了,系统会卡死。我一般设为核心数减1,留一个核心给系统用。

4.5 一张图看懂构建流程

说了这么多,我们来画一张图,把整个流程串起来:

OpenHarmony 构建流程概览 用户输入:hb build hb set:选择产品配置 读取 productdefine/*.json gn gen:生成 .ninja 文件 ninja:执行编译

这张图把整个流程分成了五步:用户输入 → 选择产品 → 读取配置 → 生成构建文件 → 执行编译。每一步都有对应的工具和文件,环环相扣。

4.6 避坑指南

最后,分享几个我踩过的坑,希望能帮你省点时间:

  • Python版本问题:hb工具依赖Python 3.7+。如果你系统默认Python是2.7,记得用 python3 或者虚拟环境。我曾经在Ubuntu 18.04上折腾了半天,最后发现是Python版本不对。
  • 缓存问题:如果你改了产品配置,但编译时没生效,试试 hb clean 清空缓存。有时候gn的缓存会“记住”旧的配置。
  • 磁盘空间:全量编译一次,out目录可能占用几十GB。确保你的磁盘有足够空间。我建议至少留50GB。
  • 并行度:别贪心。如果你的机器只有4核,设 -j8 并不会更快,反而可能因为内存不足导致编译失败。

嗯,这一章的内容就到这里。构建系统初探,说白了就是搞清楚“谁调谁,谁生成谁”。hb是门面,build.sh是后门,gn和ninja是真正的干活主力。理解了这些,你就能在遇到问题时,快速定位到是哪个环节出了岔子。


专注资料整理