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 set 和 hb 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_HOME、OHOS_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}
看到了吗?核心就三步:
- 设置环境变量:告诉系统当前目录和产品名。
- gn gen:根据产品配置生成ninja构建文件。
- ninja:真正执行编译。
我曾经遇到过一个坑:在某个CI环境里,hb工具因为Python版本问题跑不起来。当时我直接改写了build.sh,绕过了hb,手动调gn和ninja,硬是把编译跑通了。所以,理解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_device 和 parts,就能定义出一套全新的产品。这就是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 一张图看懂构建流程
说了这么多,我们来画一张图,把整个流程串起来:
这张图把整个流程分成了五步:用户输入 → 选择产品 → 读取配置 → 生成构建文件 → 执行编译。每一步都有对应的工具和文件,环环相扣。
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是真正的干活主力。理解了这些,你就能在遇到问题时,快速定位到是哪个环节出了岔子。