OpenHarmony学习笔记【篇一:编译构建机制】
在学习纯血鸿蒙系统各个模块之前,我们需要先了解纯血鸿蒙的编译构建机制。本篇主要基于鸿蒙系统的编译,和各模块的编译配置两个维度去进行梳理。另外编译构建相关代码在如下目录:

一、编译入口
我们先看看纯血鸿蒙系统的开源代码官方介绍说明:

即直接执行./build.sh --product-name xxx 即可开始完整的鸿蒙OS全量编译,但是在开源代码的根目录中并没有找到build.sh文件。
1、开源代码build.sh

如上编译命令执行的build/build_scripts/build.sh脚本,其主要逻辑如下几步:
https://xrefandroid.com/OpenHarmony-v6.0-Release/xref/build/build_scripts/build.sh
步骤一:环境检查

步骤二:工具包初始化

步骤三:命令参数解析

步骤四:编译入口

如上鸿蒙系统最核心的编译入口其实就在build/hb/main.py build 脚本的调用。
2、黄区项目build_system.sh
如下介绍一下我在HW黄区看到的纯血鸿蒙系统的编译模式。还是如上的机制,在根目录中存在build.sh和build_system.sh等脚本,使用方式同上类似。我看到的这个项目的编译命令如下:
./build_system.sh --abi-type generic_arm_32 --device-type hisi_xxx --ccache
虽然有些参数的变化,但是不影响,最终目的还是指定product name,如上命令中编译的是generic_arm_32这个项目。这一块的定义同安卓基本一致,即在device目录下会初始当前项目,至于安卓编译三部曲这块我没有具体深入研究,大概率是不支持了。
如上build_system.sh脚本中有一段核心代码:
${PYTHON3}${source_root_dir}/build/hb/main.py /
build --product-name ${product_name}${main_params}||{
echo -e "${HMOS_BUILD_INFO}"
exit 1
}
这段代码直接调用了根目录/build/hb/main.py城西,根据如上命令拼接出来之后的命令变成:
/build/hb/main.py build --product-name generic_arm_32 all_phone_standard
如上鸿蒙系统最核心的编译入口其实就在build/hb/main.py build脚本的调用。
3、编译入口build/hb/main.py

如上代码,因为传递的是build参数,所以执行的第93行代码,如果is_indep_build为真就进行独立构建系统,否则就普通构建,两则区别如下:
常规构建 _init_build_module(:139) | 独立构建 _init_indep_build_module(:195) | |
|---|---|---|
| 触发 | hb set 选产品后 hb build | 组件目录下 hb build <组件名> -i(或 --indep-build) |
| 前提 | 完整 OpenHarmony 源码树 | 只需要单个组件仓库,强制检查当前目录存在 build/indep_configs/build_indep.sh,否则报 you are running hb build command in wrong dir! 退出 |
| 组装链路 | OHOSPreloader → OHOSLoader(加载产品/部件清单)→ Gn → Ninja | PreuiltsService(预建件)+ Hpm(包管理器,自动拉依赖的二进制 prebuilts)+ IndepBuild 服务 |
| 依赖来源 | 源码树内全量源码 | 从 repo.harmonyos.com 下载依赖组件的预编译产物,本地只 gn/ninja 编当前组件 |
| 适用 | 整机/整镜像构建、产品级集成 | 组件级开发、CI 单仓验证,不用拉几十 GB 全树 |
后续的编译构建就不详细介绍了,因为我也不是很清楚。只需要知道编译构建子系统提供了一个基于Gn和ninja的编译构建框架,根据产品配置,编译生成对应的镜像包,其中编译构建流程为:
- 使用Gn配置构建目标。
- Gn运行后会生成ninja文件。
- 通过运行ninja来执行编译任务。
详细参考官方文档说明:https://gitee.com/openharmony/build/blob/master/README_zh.md

二、编译结构
在接触鸿蒙系统源代码之后,经常会看到XXX子系统。官方文档对它的定性是: "子系统是一个逻辑概念,它具体由对应的部件构成" ,"子系统是某个路径下所有部件的集合,一个部件只能属于一个子系统"。
1、subsystem子系统
我们先来看看子系统长什么样子的,源代码build/subsystem-config.json文件中是开源主干的子系统注册表,配置了如下子系统:
//build/subsystem_config.json
{
"arkui": {
"path": "foundation/arkui",
"name": "arkui"
},
"ai": {
"path": "foundation/ai",
"name": "ai"
},
"account": {
"path": "base/account",
"name": "account"
},
"distributeddatamgr": {
"path": "foundation/distributeddatamgr",
"name": "distributeddatamgr"
},
"security": {
"path": "base/security",
"name": "security"
},
"useriam": {
"path": "base/useriam",
"name": "useriam"
},
"accesscontrol": {
"path": "base/accesscontrol",
"name": "accesscontrol"
},
"startup": {
"path": "base/startup",
"name": "startup"
},
"hiviewdfx": {
"path": "base/hiviewdfx",
"name": "hiviewdfx"
},
"utils": {
"path": "utils",
"name": "utils"
},
"commonlibrary": {
"path": "commonlibrary",
"name": "commonlibrary"
},
"bundlemanager": {
"path": "foundation/bundlemanager",
"name": "bundlemanager"
},
"appexecfwk": {
"path": "foundation/appexecfwk",
"name": "appexecfwk"
},
"ability": {
"path": "foundation/ability",
"name": "ability"
},
"notification": {
"path": "base/notification",
"name": "notification"
},
"communication": {
"path": "foundation/communication",
"name": "communication"
},
"location": {
"path": "base/location",
"name": "location"
},
"systemabilitymgr": {
"path": "foundation/systemabilitymgr",
"name": "systemabilitymgr"
},
"hdf": {
"path": "drivers",
"name": "hdf"
},
"updater": {
"path": "base/update",
"name": "updater"
},
"developtools": {
"path": "developtools",
"name": "developtools"
},
"sensors": {
"path": "base/sensors",
"name": "sensors"
},
"graphic": {
"path": "foundation/graphic",
"name": "graphic"
},
"window": {
"path": "foundation/window",
"name": "window"
},
"time": {
"path": "base/time",
"name": "time"
},
"inputmethod": {
"path": "base/inputmethod",
"name": "inputmethod"
},
"request": {
"path": "base/request",
"name": "request"
},
"print": {
"path": "base/print",
"name": "print"
},
"theme": {
"path": "base/theme",
"name": "theme"
},
"multimedia": {
"path": "foundation/multimedia",
"name": "multimedia"
},
"castplus": {
"path": "foundation/CastEngine",
"name": "castplus"
},
"multimodalinput": {
"path": "foundation/multimodalinput",
"name": "multimodalinput"
},
"telephony": {
"path": "base/telephony",
"name": "telephony"
},
"global": {
"path": "base/global",
"name": "global"
},
"powermgr": {
"path": "base/powermgr",
"name": "powermgr"
},
"usb": {
"path": "base/usb",
"name": "usb"
},
"applications": {
"path": "applications",
"name": "applications"
},
"xts": {
"path": "test/xts",
"name": "xts"
},
"ostest": {
"path": "test/ostest",
"name": "ostest"
},
"testfwk": {
"path": "test/testfwk",
"name": "testfwk"
},
"distributedhardware": {
"path": "foundation/distributedhardware",
"name": "distributedhardware"
},
"arkcompiler": {
"path": "arkcompiler",
"name": "arkcompiler"
},
"iothardware": {
"path": "base/iothardware",
"name": "iothardware"
},
"kernel": {
"path": "kernel",
"name": "kernel"
},
"msdp": {
"path": "base/msdp",
"name": "msdp"
},
"deviceprofile": {
"path": "foundation/deviceprofile",
"name": "deviceprofile"
},
"filemanagement": {
"path": "foundation/filemanagement",
"name": "filemanagement"
},
"resourceschedule": {
"path": "foundation/resourceschedule",
"name": "resourceschedule"
},
"barrierfree": {
"path": "foundation/barrierfree/accessibility",
"name": "barrierfree"
},
"customization": {
"path": "base/customization",
"name": "customization"
},
"web": {
"path": "base/web",
"name": "web"
},
"thirdparty": {
"path": "third_party",
"name": "thirdparty"
},
"ide": {
"path": "ide",
"name": "ide"
},
"advertising": {
"path": "domains/advertising",
"name": "advertising"
},
"tee": {
"path": "base/tee",
"name": "tee"
},
"llvmproject": {
"path": "toolchain/llvm-project",
"name": "llvmproject"
},
"sdk": {
"path": "interface/sdk-js",
"name": "sdk"
}
}
如上配置了OpenHarmony开源主干上面的57个子系统。每一个子系统都有两部分组成:
- path:代码路径
- name:子系统名称
由此可见,所以子系统 ≈ 一张"目录 → 领域名"的映射表 + 部件的逻辑分组,不是一个有独立构建产物的实体。
Q1:可以自定义subsystem吗?
build/subsystem_config.json 里 57 个是主干定义的子系统(startup、powermgr、security……),除此之外,芯片方案商和产品厂商可以在 vendor/、device/ 目录下放自己的子系统配置扩展注册。
证据在构建脚本 hb/util/loader/subsystem_scan.py:它读主干配置后还会 merge 一份 example_subsystem_config,且 _check_path_prefix() 明确只允许新增路径以 vendor/device 开头。
Q2:编译时会读subsystem_config然后逐个子系统编译吗?
编译的时候,会读取subsystem_config配置文件,但没有"针对每个子系统编译"这个动作。 读它的目的是拿到扫描路径表。subsystem_scan.py源码逻辑:
子系统全程只是"去哪里找部件"的路径索引,从不参与"编不编"的决策。 决定编译范围的永远是产品配置里的部件清单——qemu-min 产品的 startup 子系统下只勾了 init 和 startup_l2 两个部件,这个子系统下其余部件即使存在也不编。
Q3:能单独编译某个子系统吗?
不能。官方文档原话:"编译构建可以编译产品、部件和模块,但是不能编译子系统。"因为"编一个子系统"语义不成立:
子系统是逻辑分组,构建系统只为部件生成 GN group target,没有子系统级的聚合 target;
同一个子系统下不同部件被不同产品按 features 裁剪,编译需要的 toolchain(arm/arm64)、内核、features 开关、inner_kits 可见性全部来自产品上下文——脱离产品谈"编子系统",这些参数无处来。
Q4:终极理解
OpenHarmony的子系统的设计目的,是站在架构分层、代码分类的角度上引申出来的。即相关联的代码,我放在一起,统一用子系统来进行命名,用来给工程师看的。等同于Android中的目录划分,例如framework/base,可以把它当成一个子系统,但是却无法直接单编译framework/base。
2、bundle部件
一个子系统可能包含多个部件,而官方对bundle部件的描述内容如下:
{
"name": "@ohos/<component_name>", # HPM部件英文名称,格式"@组织/部件名称"
"description": "xxxxxxxxxxxxxxxxxxx", # 部件功能一句话描述
"version": "3.1", # 版本号,版本号与OpenHarmony版本号一致
"license": "MIT", # 部件License
"publishAs": "code-segment", # HPM包的发布方式,当前默认都为code_segment
"segment": {
"destPath": ""
}, # 发布类型为code_segment时为必填项,定义发布类型code_segment的代码还原路径(源码路径)
"dirs": {}, # HPM包的目录结构,字段必填内容可以留空
"scripts": {}, # HPM包定义需要执行的脚本,字段必填,值非必填
"licensePath": "COPYING",
"readmePath": {
"en": "README.rst"
},
"component": { # 部件属性
"name": "<component_name>", # 部件名称
"subsystem": "", # 部件所属子系统
"syscap": [], # 部件为应用提供的系统能力
"features": [], # 部件对外的可配置特性列表,一般与build中的sub_component对应,可供产品配置
"adapted_system_type": [], # 轻量(mini)小型(small)和标准(standard),可以是多个
"rom": "xxxKB" # ROM基线,没有基线写当前值
"ram": "xxxKB", # RAM基线,没有基线写当前值
"deps": {
"components": [], # 部件依赖的其他部件
"third_party": [] # 部件依赖的三方开源软件
},
"build": { # 编译相关配置
"sub_component": ["部件包含模块的gn目标"], # 部件编译入口,新增模块在此处配置
"inner_kits": [], # 部件间接口
"test": [] # 部件测试用例编译入口
}
}
}
1)bundle.json配置解读
如上配置主要可以划分为两部分,部件的一些基本信息,部件内部的一些定义。
配置1:部件发布声明
我们先看看audio_framework部件的一些基本信息:

- "name": "@ohos/audio_framework"
ohos表示这个部件的发布者是OpenHarmony官方维护,只要是官方开发的都是ohos,用来表示一个机构或者组织,类似于android中的Google一样。当然在vendor/里面可能还有其他一些企业或者公司开发的一些部件,例如 @hihope/xxx表示润和公司。
- "publishAs": "code-segment"
| publishAs | 发的是什么 | 装到哪 | 类比 Android |
|---|---|---|---|
| code-segment | 源代码片段,整目录原样打包 | 按 destPath 还原进源码树,要再编译 | repo sync 拉下来的单个 git project |
| source | 源码,但按 dirs 声明挑选打包(src/headers 分离) | 留在依赖目录,独立编译 | 早期 npm 风格的源码库 / 独立模块 |
| binary | 预编译二进制(.so/.a,带 branch/os/arch/variant 维度) | binarys/<destPath>/<分支>/<OS>/<架构>/<variant>/<版本>/,不编译直接链接 | 厂商 prebuilt blob(vendor/xxx/proprietary 里只给 .so 不给源码的库) |
| distribution | 发行版:不含代码,只有 bundle.json 依赖清单 + scripts.dist 编译脚本,hpm dist 拉齐一批部件编出整机镜像 | 当前工程根 | AOSP 的设备 manifest + 编译出的 factory image 整包 |
| plugin / model / chip-definition / template | HPM 生态扩展类型(工具插件、AI 模型、芯片定义、工程模板),与系统部件关系不大 | — | — |
配置2:部件属性定义
接下来再看看另外一部分component的配置,这里面对当前部件的一些属性进行具体的定义,同样先看看component前面的字段:

-
"name": "audio_framework"
component里面的name表示当前部件的名字,必须和前面的name一致,这里表示是audio_framework部件。
- "subsystem": "multimedia"
这里的subsystem表示当前所属的子系统,注意要和前文的subsystem-config.json定义的子系统要对齐,这里表示当前部件属于multimedia子系统。当然当前代码路径也是在subsystem-config.json定义的path里面。
- "adapted_system_type": [ "standard" ]
adapted_system_type表示当前部件使用于鸿蒙什么系统,standard表示标准鸿蒙系统,从这里[]来看可以配置多个系统,即某些部件可以配置为支持多套内核系统。
- "syscap": 系统能力清单
如上配置的SystemCapability.Multimedia.Audio.XXX 是部件向系统注册"我提供哪些能力"的标准格式:
子系统(Multimedia)→ 部件(Audio)→ 具体能力(Renderer 渲染、Capturer 录音、等)
- 运行时谁用?应用/三方开发者调 canIUse("SystemCapability.Multimedia.Audio.Spatialization") 查询设备是否具备该能力;
- 设备上也可 param get const.SystemCapability.Multimedia.Audio.Renderer 直接看
- 与Android 对照就是 Android 的 PackageManager.hasSystemFeature() 机制——APK 在 AndroidManifest 里 <uses-feature android:name="android.hardware.usb.host">,系统在 /system/etc/permissions/ 下放 feature XML,运行时查询。
- "features":编译裁剪开关清单
这 15 个 audio_framework_feature_xxx 是功能编译开关,命名规律固定:<部件名>_feature_<功能>。feature 的完整生命周期如下:
- 部件声明:bundle.json features 数组列出开关名(注意:只列名字,默认值不在这里)。
- 产品可覆盖:产品 config.json 勾选部件时可写
"features": ["audio_framework_feature_distributed_audio=true"]。- 强校验:
loader.py:230 _check_product_part_feature——产品若配了部件 bundle.json 里没声明的 feature,编译直接报错 2006:"The product use a feature that is not supported by this part"。- BUILD.gn 消费:feature 名直接变成 GN 变量。audio_framework 自己的
services/audio_service/BUILD.gn(767 行)里满屏实证:
类似于Android的编译宏控开关:
if (audio_framework_feature_offline_effect) {
defines += [ "FEATURE_OFFLINE_EFFECT" ] // :284-285
}
if (audio_framework_feature_inner_capturer) {
defines += [ "HAS_FEATURE_INNERCAPTURER" ] // :288-289
}
if (audio_framework_feature_distributed_audio == true) { // :361
... # 整个分布式音频源码块编/不编
}
if (!audio_framework_feature_new_engine_flag) { # :405 旧引擎代码块
...
}
配置3:部件编译入口

PS:如上每一行配置都是一个模块的编译入口,即一个部件是由多个模块组成。
在继续看看build的inner_kits字段,他是一个数组,参考如下代码:

看起来是为每个模块指定了头文件的路径。
配置4:模块依赖inner_kits
inner_kits 是部件对外开的"接口窗口"——跨部件依赖的唯一合法通道。部件之间想互相调用,不能直接 deps 别人的模块,必须走两扇登记:提供方在 inner_kits 里"开窗",使用方在 BUILD.gn 里用 external_deps "走窗"。这是 OH 部件化解耦的强制机制。
inner_kits每个条目的结构如下:
{
"name": "//foundation/multimedia/av_session/frameworks/native/session:avsession_client",
"header": {
"header_files": ["avsession_manager.h", "av_session.h", ...5个...],
"header_base": "//foundation/multimedia/av_session/interfaces/inner_api/native/session/include"
}
}
name:窗口后面是哪个 GN 目标——必须是个可链接的库(这里是avsession_client,ohos_shared_library)。别的部件 external_deps 写的就是这个目标名。header.header_base:公开头文件所在的目录(include 根)。header.header_files:这个目录里"允许被别人看到"的头文件白名单——这就是你问的"为什么有无数个 header"。
2)component.build解读
我们再来深究一下部件的编译入口配置,先举一个例子

Q1:base_group/fwk_group/service_group有什么区别?
如果鸿蒙系统的多媒体子系统(如 audio_framework)将所有代码、接口、服务等全部混在一起,会带来什么后果?耦合度极大,架构不清晰,安全风险大等情况。
因此 bundle.json 中的 group_type 实际上是按“职责”和“安全边界”对代码模块进行物理隔离与分层。
base_group(基础组件层):- 职责:存放整个子系统最基础的、被其他所有分组依赖的底层公共代码(如底层工具类、公共基础协议等)。
- 特点:它是整个子系统的“地基”。
fwk_group(框架层 / Framework):- 职责:存放向上层应用提供服务的代码。比如 ArkTS 应用要播放音乐,调用的
@ohos.multimedia.audio对应的接口实现就在这里。 - 特点:它是“门面”,负责将底层的复杂操作包装成简单易用的 API 给开发者使用。你截图中红线标注的
cj_multimedia_audio_ffi就属于框架层,用于 ArkTS 跨语言调用。
- 职责:存放向上层应用提供服务的代码。比如 ArkTS 应用要播放音乐,调用的
service_group(服务层 / Service):- 职责:存放系统内部进程间通信(IPC)的服务端代码。比如音频策略控制、音频服务后台进程(SA)等。
- 特点:它是“幕后黑手”,通常运行在独立的系统进程中,普通应用不能直接调用它,必须通过
fwk_group暴露的接口间接访问。
Q2://foundation/multimedia/audio_framework/frameworks/cj:cj_multimedia_audio_ffi前后到底表示什么意思?
在像鸿蒙这样拥有千万行代码的巨型系统中,如果编译系统只根据文件夹路径去找代码,一旦某个文件夹下有两个同名的 .gn 文件,系统该编译哪一个?
因此,这种格式是 GN 构建系统中用于精确唯一锁定一个编译目标(Target)的标准写法,类似于编程语言中的 包名.类名
- 冒号
:前面(路径部分)://foundation/multimedia/audio_framework/frameworks/cj- 表示源代码的目录位置。GN 编译器会找到这个路径下的
BUILD.gn文件。双斜杠//代表源码根目录。
- 冒号
:后面(目标名称部分):cj_multimedia_audio_ffi- 表示在对应目录的
BUILD.gn文件中定义的具体模块目标名称(Target Name)。 - 例如,在
//.../cj/BUILD.gn文件中,一定有一句类似这样的定义:
ohos_shared_library("cj_multimedia_audio_ffi") { ... }
这串字符的含义就是告诉构建系统:“请去 foundation/.../cj/ 这个文件夹里,找到名字叫 cj_multimedia_audio_ffi 的那个构建目标,并把它(的编译产物)加入到我当前的模块依赖中。
3)multimedia子系统有哪些部件?
先看看subsystem_config.json里面对multimedia子系统是如何定义的:

其源码路径在foundation/multimedia目录下,该目录下有如下多个子目录:

那么如上目录下是,是不是每个目录都有一个bundle.json呢?
- foundation\multimedia\audio_framework\bundle.json
{
"name": "@ohos/audio_framework",
"description": "Audio standard provides managers and provides the audio resources to application for play/record audio",
"version": "4.0",
"license": "Apache License 2.0",
"publishAs": "code-segment",
"segment": {
"destPath": "foundation/multimedia/audio_framework"
},
- foundation\multimedia\audio_lite\bundle.json

- foundation\multimedia\av_codec\bundle.json
{
"name": "@ohos/av_codec",
"description": "Media standard provides atomic capabilities",
"version": "3.1",
"license": "Apache License 2.0",
"publishAs": "code-segment",
"segment": {
"destPath": "foundation/multimedia/av_codec"
},
"dirs": {},
"scripts": {},
"component": {
"name": "av_codec",
"subsystem": "multimedia",
"adapted_system_type": [ "standard" ],
- foundation\multimedia\av_session\bundle.json
{
"name": "@ohos/av_session",
"description": "Audio and Video Session Management",
"version": "4.0",
"license": "Apache License 2.0",
"publishAs": "code-segment",
"segment": {
"destPath": "foundation/multimedia/av_session"
},
"dirs": {},
"scripts": {},
"component": {
"name": "av_session",
"subsystem": "multimedia",
"syscap": [
"SystemCapability.Multimedia.AVSession.AVCast",
"SystemCapability.Multimedia.AVSession.Core",
"SystemCapability.Multimedia.AVSession.ExtendedDisplayCast",
"SystemCapability.Multimedia.AVSession.Manager",
"SystemCapability.Multimedia.AVSession.AVInputCast = false"
],
"features": [ "av_session_enable_start_stop_on_demand" ],
"adapted_system_type": [ "standard" ],
"rom": "3000KB",
"ram": "5120KB",
"hisysevent_config": [
"//foundation/multimedia/av_session/hisysevent.yaml"
],
每个目录下都有一个这样的bundle.json,即每个目录下都是一个部件,而multimedia子系统有16个部件,其中一些部件适用于standard,有一些部件适用于mini或者small。
3、模块
模块就是编译子系统的一个目标,包括(动态库、静态库、配置文件、预编译模块等)。模块要定义属于哪个部件,一个模块只能归属于一个部件。OpenHarmony使用定制化的Gn模板来配置模块规则,Gn语法相关的基础知识请参考官网手册。
1)BUILD.gn目标配置
每个模块对应的BUILD.gn文件,对标与android.bp文件。 可以使用提供的模板,也可以使用gn语法规则自定义编写。
详细参考:模块配置规则
支持如下常用的模块配置规则:
# C/C++模板
ohos_shared_library
ohos_static_library
ohos_executable
ohos_source_set# 预编译模板:
ohos_prebuilt_executable
ohos_prebuilt_shared_library
ohos_prebuilt_static_library#hap模板
ohos_hap
ohos_app_scope
ohos_js_assets
ohos_resources#rust模板
ohos_rust_executable
ohos_rust_shared_library
ohos_rust_static_library
ohos_rust_proc_macro
ohos_rust_shared_ffi
ohos_rust_static_ffi
ohos_rust_cargo_crate
ohos_rust_systemtest
ohos_rust_unittest
ohos_rust_fuzztest#其他常用模板
#配置文件
ohos_prebuilt_etc#sa配置
ohos_sa_profile
ohos_shared_library示例:可执行二进制文件
import("//build/ohos.gni")
ohos_shared_library("helloworld") {
sources = ["file"]
include_dirs = [] # 如有重复头文件定义,优先使用前面路径头文件。
cflags = [] # 如重复冲突定义,后面的参数优先生效,也就是该配置项中优先生效。
cflags_c = []
cflags_cc = []
ldflags = [] # 如重复冲突定义,前面参数优先生效,也就是ohos_template中预制参数优先生效。
configs = []
deps = [] # 部件内模块依赖
external_deps = [ # 跨部件模块依赖定义
"part_name:module_name", # 定义格式为 "部件名:模块名称"。
] # 这里依赖的模块必须是依赖的部件声明在inner_kits中的模块。
output_name = [string] # 模块输出名
output_extension = [] # 模块名后缀
module_install_dir = "" # 模块安装路径,缺省在/system/lib64或/system/lib下; 模块安装路径从system/,vendor/后开始指定。
relative_install_dir = "" # 模块安装相对路径,相对于/system/lib64或/system/lib;如果有module_install_dir配置时,该配置不生效。
part_name = "" # 必选,所属部件名称
output_dir
# Sanitizer配置,每项都是可选的,默认为false/空。
sanitize = {
# 各个Sanitizer开关
cfi = [boolean] # 控制流完整性检测
cfi_cross_dso = [boolean] # 开启跨so调用的控制流完整性检测
integer_overflow = [boolean] # 整数溢出检测
boundary_sanitize = [boolean] # 边界检测
ubsan = [boolean] # 部分ubsan选项
all_ubsan = [boolean] # 全量ubsan选项
...
debug = [boolean] # 调测模式
blocklist = [string] # 屏蔽名单路径
}
testonly = [boolean]
license_as_sources = []
license_file = [] # 后缀名是.txt的文件
remove_configs = []
no_default_deps = []
install_images = []
install_enable = [boolean]
symlink_target_name = []
version_script = []
use_exceptions = []
}
ohos_static_library示例:静态库
import("//build/ohos.gni")
ohos_static_library("helloworld") {
sources = ["file"] # 后缀名是.c的相关文件
include_dirs = ["dir"] # 包含目录
configs = [] # 配置
deps = [] # 部件内模块依赖
part_name = "" # 部件名称
subsystem_name = "" # 子系统名称
cflags = []
external_deps = [ # 跨部件模块依赖定义,
"part_name:module_name", # 定义格式为 "部件名:模块名称"
] # 这里依赖的模块必须是依赖的部件声明在inner_kits中的模块。
lib_dirs = []
public_configs = []
# Sanitizer配置,每项都是可选的,默认为false/空
sanitize = {
# 各个Sanitizer开关
cfi = [boolean] # 控制流完整性检测
cfi_cross_dso = [boolean] # 开启跨so调用的控制流完整性检测
integer_overflow = [boolean] # 整数溢出检测
boundary_sanitize = [boolean] # 边界检测
ubsan = [boolean] # 部分ubsan选项
all_ubsan = [boolean] # 全量ubsan选项
...
debug = [boolean] # 调测模式
blocklist = [string] # 屏蔽名单路径
}
remove_configs = []
no_default_deps = []
license_file = [] # 后缀名是.txt的文件
license_as_sources = []
use_exceptions = []
}
ohos_prebuilt_etc示例
import("//build/ohos.gni")
ohos_prebuilt_etc("helloworld") {
# ohos_prebuilt_etc模板最常用属性:
source = "file" # 指定单个原文件
module_install_dir = "" # 模块安装路径,从system/,vendor/后开始指定
subsystem_name = "" # 子系统名
part_name = "" # 必选,所属部件名称
install_images = []
relative_install_dir = "" # 模块安装相对路径,相对于system/etc;如果有module_install_dir配置时,该配置不生效。
# ohos_prebuilt_etc模板不常用属性:
deps = [] # 部件内模块依赖
testonly = [boolean]
visibility = []
public_configs = []
symlink_target_name = [string]
license_file = [string]
license_as_sources = []
}
ohos_sa_profile示例
import("//build/ohos.gni")
ohos_sa_profile("helloworld") {
sources = [".xml"] # xml文件
part_name = "" # 部件名
subsystem_name = "" # 子系统名
}
2)BUILD.gn语法解读
在回到foundation/multimedia/audio_framework/bundle.json中的配置的一个编译入口:
"//foundation/multimedia/audio_framework/services/audio_service:audio_service_packages",
如上配置指向foundation/multimedia/audio_framework/services/audio_service目录下的BUILD.gn里面的audio_service_packages。我们来看看audio_service_packages是什么玩意?居然不是固定一个模块,而是group,参考如下代码:

Q1:group 到底是什么?
在 GN 构建系统中,group 是一个非常巧妙且常用的概念。我们可以用“逻辑分组”或“快捷方式”来理解它。核心定义:group 不编译任何代码,也不生成任何可执行文件或库文件。它本质上是一个逻辑上的打包器或快捷方式。
- 为什么设计
group?
大型系统(如鸿蒙)包含成百上千个模块(.so库、可执行程序等)。如果需要把这几十个模块一起编译,或者在其他地方统一引用这几十个模块,难道要写几十行deps依赖吗?那太繁琐了。
因此,开发者用group把这些相关的目标“框”在一起。 - 如上的例子:
group("audio_service_packages")就是把audio_common、audio_server_init、audio_service这三个音频服务相关的模块,打包成了一个叫audio_service_packages的逻辑整体。
Q2:deps如何把多个目标进行打包?
deps 是 group(以及 ohos_shared_library 等其他构建规则)的一个参数,意思是“依赖项(Dependencies)”。它里面的字符串(如 ":audio_common")是对当前目录或其他目录下编译目标的引用路径。
- 冒号
::表示“当前目录”。它是指向当前BUILD.gn文件中定义的编译目标的简写。如果写成"//foundation/...:xxx"则表示绝对路径。- 名称
audio_common:这必须对应当前目录(或指定目录下)某个BUILD.gn中定义过的目标名称。- 关联你的第一张图:注意看你的代码截图第33行:
ohos_shared_library("audio_common") {。这里定义了一个名为audio_common的动态库目标。- 结论:第27行的
":audio_common"就是在告诉构建系统:“把第33行开始定义的那个audio_common动态库,作为依赖包含进来。”
Q3:BUILD.gn的机制
你可以把这整个 BUILD.gn 想象成一个“车间组装说明书”:
ohos_shared_library("audio_common")、ohos_shared_library("audio_service")等是在生产具体的零件(编译出一个个.so动态库)。group("audio_service_packages")是一个“组装篮子”。deps里面的内容就是告诉工人:“把生产好的audio_common、audio_server_init和audio_service这三个零件,全都扔进这个篮子里。”- 最终效果:当系统的其他地方需要编译音频服务的所有组件时,只需要在它的
deps里写上一句依赖"//foundation/multimedia/audio_framework/services/audio_service:audio_service_packages",就能一键拉取并编译所有的音频服务模块,极大简化了构建脚本。
3)init部件拥有哪些目标模块?
先看看init部件的bundle.json定义:

如上代码,init部件拥有两个分组:init_fwk_group和init_service_group,其中每个分组可能就不止一个目标模块,我们依次来看看这两个分组是如何定义的:

其中init_fwk_group包含了9个分组,init_service_group包含3个分组,我们这里挑选其中一个来看看:
services:startup_init 表示当前目录下的services目录下的BUILD.gn文件里面定义的startup_init:

start_init也是一个组,其中使用了判断语法来判断当前编译的是mini系统还是标准系统,mini系统的只需要编译init/lite:init和etc_files即可,但如果是标准系统,编译的东西就多了,当然这里主要还是init/standard:init和etc_files,其中当前目录下的init/standard的init模块就是标准系统的主要init进程的编译入口:

4、产品
官方对产品的概念描述如下:

可以看出来如上配置对标android里面的device/产商/设备的概念,即我们编译的时候需要指定一个产品,即在android编译三部曲中的第二步lunch aosp-arm-eng的概念。
1)OpenHarmony产商配置
参考官方文档的介绍,vendor/产品厂商/产品名,我们先来看看OpenHarmony开源代码中有哪些产商的定义,有是三个产商:hihope、hisilicon、ohemu

- hihope产商

- hisilicon产商

- ohemu目录

2)OpenHarmony产品配置
解析来我们拿hihope产商里面的大禹210产品来详细介绍一下,先看看该目录的config.json配置:

P1:产品与硬件基础信息
这部分告诉编译系统,你当前是在为哪一款设备、哪块芯片进行构建
roduct_name/device_company/target_cpu:明确该产品名称为“dayu210”,设备厂商是“HiHope”,目标 CPU 架构为“arm64”。board:指定了硬件主板型号为“rk3588”,这直接决定了系统底层需要加载哪些板级支持包(BSP)。type/api_version:说明该系统属于支持丰富交互、高性能的“standard”(标准)系统,且基于第 8 级 API。
P2:系统构建特性开关
这部分是一组布尔值(true/false)开关,用于开启或关闭特定的系统底层构建特性:
enable_ramdisk: true:启用了 ramdisk(内存虚拟盘),通常用于在系统启动的极早期加载必要的初始化文件和驱动。enable_abysstem: false:关闭了 A/B 分区机制(即没有启用两套系统分区无缝热切换更新)。build_selinux: false/build_seccomp: false:关闭了 SELinux 和 Seccomp 安全沙盒机制。这通常意味着该版本是一个用于快速验证和开发的非安全加固版本,方便调试,但实际商用时会开启。
P3:子系统与组件继承
这是整个配置的核心,定义了系统需要“拼装”哪些功能模块:
"inherit"(基础继承):系统直接继承了rich.json和chipset_common.json两个配置。这意味着它直接打包了富设备系统(如支持标准窗口、多任务)的基础框架,以及 RK3588 芯片的通用驱动和基础组件。subsystems(子系统扩展):security子系统:虽然关闭了安全加固,但依然保留了基础的selinux_adapter组件,以维持系统的基本兼容性。device_rk3588子系统:这是针对该硬件的专属配置,引入了device_rk3588组件。这包含了该开发板特有的硬件外设驱动(如屏幕、触控、摄像头、网络接口等)。
3)OpenHarmony板级配置
前面介绍的大禹产品config.json配置中还有一项非常重要:

device_build_path是最核心的硬件对接配置,告诉 OpenHarmony 的构建系统:“去哪里找这个板子的底层硬件驱动和系统适配代码”。我们来看看这个目录下都有些什么东西?

如上目录,我简单进去看了看,也都是一些配置文件。AI回到如下内容:
Q1:drvice/xxx/xxx目录包含什么代码?
指向这个路径,意味着构建系统会把该目录下的所有内容纳入编译。这个文件夹里通常包含以下几个关键子目录:
kernel/linux/config:存放 Linux 内核的.config配置文件。它定义了内核要开启哪些驱动(比如是否开启 USB 3.0、WIFI 模块、GPU 驱动等),这是系统能跑在 RK3588 上的核心。board:存放板级描述文件(如.dts设备树文件),用来告诉系统主板上接了什么外设。sdk或uboot:存放引导程序和厂商提供的 SDK 驱动源码(比如 RK3588 的电源管理、显示驱动、音频驱动等)。BUILD.gn:该目录的顶级编译入口,构建系统从这里开始读取编译规则。
Q2:drvice/xxx/xxx目录编译过程做了什么?
当编译系统读到这行配置时,它会完成以下任务:
- 绑定硬件与软件:把抽象的 OpenHarmony 标准系统与物理的 RK3588 硬件“绑”在一起。没有这个路径,系统就不知道该怎么驱动 RK3588 的 GPU、NPU 和各种接口。
- 引入厂商依赖:Dayu210 作为一个标准产品,其底层依赖大量瑞芯微(Rockchip)的闭源或开源驱动。
device/board/hihope/dayu210就是 HiHope 把这些驱动“包”进 OpenHarmony 系统的入口。 - 定制系统启动:决定了系统启动时加载哪个内核、加载哪些初始化脚本、以及系统的分区表是如何划分的。
总结来说:是物理硬件到操作系统的“桥梁”。只要你想在另一块不同的开发板(比如基于全志或者高通芯片的板子)上运行 OpenHarmony,你第一件事就是要新建一个类似的目录,并在这里配置好对应的 "device_build_path"。
Q3:drvice/xxx/xxx和vendor/xxx/xxx有何区别?
这两个目录虽然都存放了大量的 .json、.gni 等配置文件,看起来非常相似,但在 OpenHarmony 的架构中,它们的职责划分非常清晰。
这背后遵循的是业界经典的 SoC(芯片)与 Board(开发板)解耦 设计思想。简单来说,就是把“芯片本身的底层能力”和“开发板上外扩的硬件”拆分开来适配。
以下是这两个目录的详细区别与对应关系:
device/board/hihope/dayu210/ (板级适配)
这个目录的核心是 Board(开发板)。它负责适配这块板子上除了主芯片(RK3588)之外的所有外围硬件。
- 对应硬件:
- 开发板上的板载外设(如板载的特定 Flash 型号、板载 Wi-Fi/蓝牙模组)。
- 引出的扩展接口(如引出的摄像头接口、HDMI 接口、USB 接口对应的设备树描述)。
- 开发板的电源管理逻辑(如果与芯片默认配置不同的话)。
- 开发板专属的 U-Boot 配置和分区表。
- 配置文件包含:
- 设备树(Device Tree):描述板子上接了什么外设。
- 内核配置:开启或关闭某些板载外设驱动的内核开关。
- 编译入口:该目录下的
BUILD.gn负责把板级的源码和配置打包。
vendor/hihope/dayu210/ (产品级定义)
这个目录的核心是 Vendor(终端产品)。它是整个 OpenHarmony 产品的顶层管理器和总开关。它不关心具体的驱动怎么写,它只关心“我这个产品需要哪些系统能力”。
- 对应内容:
- 这个目录下的配置文件(如你上一张图片展示的
config.json)是整个编译过程的起点和总指挥。- 定义产品的基本信息(如
product_name: dayu210,api_version: 8)。- 开启或关闭系统的大模块(如是否开启安全子系统
security,是否集成rk3588芯片包)。- 设置系统的编译特性(如是否开启
ramdisk,是否关闭selinux)。- 配置文件包含:
- 产品清单(Product Config):决定最终镜像里打包哪些子系统(Subsystem)和部件(Component)。
- 继承文件(Inherit):通过
inherit字段,将底层的芯片配置和系统基础配置“吸”上来。
它们之间有什么联系?
你可以把它们看作是 “项目经理” 和 “硬件工程师” 的关系:
- 自上而下的调度(总指挥):
当你在终端输入编译指令时,系统首先找到的是vendor/hihope/dayu210/config.json(项目经理)。它会说:“我要做一个 Dayu210 平板,我需要安全模块,也需要瑞芯微的芯片包。”- 自下而上的支撑(被调用者):
vendor中的配置通过inherit或依赖关系,去读取device/board/hihope/dayu210(硬件工程师)提供的底层硬件配置。- 共同构成编译蓝图:
vendor目录决定了系统的骨架和功能模块,而device目录提供了系统跑在实体板子上的肌肉和神经(驱动)。两者结合,才能编译出一个既能运行 OpenHarmony 框架,又能点亮 Dayu210 这块板子的完整镜像。
三、编译相关总结
在介绍编译之前先介绍一下产品、子系统、部件、模块之间的关系图:

1、如何编译一个产品?

如上官方网站的描述,当编译某个产品的时候,需要在编译命令中添加--product-name参数:
./build.sh --product-name dayu210
如上命令指向之后,编译系统会依次做如下事情:
步骤一:编译任务启动
./build.sh是整个编译过程的总入口。它会启动 OpenHarmony 的底层构建引擎(GN 构建系统)
步骤二:寻找产品
参数--product-name会告诉编译引擎“去
vendor目录下,找到dayu210这个产品的config.json作为我的‘总配置文件’。于是系统精准定位到了
vendor/hihope/dayu210/config.json
步骤三:加载产品配置----vendor/产商/产品/config.json
系统读取
vendor/.../config.json,开始解析里面的指令:
- 基本信息:记录下“这是一个基于 arm64 的、面向标准系统的 dayu210 产品”。
- 继承(inherit): 看到
inherit字段,系统会立刻去拉取它所依赖的通用配置(如chipset_common.json),把底层的芯片通用框架先搭好。- 编译子系统 (subsystems): 根据config.json中配置的子系统列表去依次编译和安装。
步骤四:加载板级配置----device/board/产商/产品/
解析到配置文件中
"device_build_path": "device/board/hihope/dayu210"这一行时,系统会立刻调头,去该目录下寻找该路径下的顶级BUILD.gn文件。
- 这一步就完美实现了你之前提到的“总指挥(vendor)”去调动“底层硬件驱动和适配代码(device/board)”。
2、如何编译一个模块?
前文介绍了通过--product-name参数指定产品名称,这是一个“全家桶”式的命令,它会根据 config.json 的定义,把整个系统(内核、系统服务、UI 框架等)全部编译一遍。这个过程非常耗时。
如果你只是想调试或者修改系统中的某一部分,只需要单独编译它。可以通过--build-target参数来完成编译目标的指定。
# 编译dayu210产品中的audio_framework部件中所有模块
./build.sh --product-name dayu210 --build-target audio_framework
Q1:--build-target后面具体跟什么?
这个目标名可以是:
- 部件名(最常见):当你输入一个部件名,直接找到你要编译的部件目录下的bundle.json里面配置的name配置(如
ace_engine),构建系统会自动编译该部件下定义的所有模块。 - 模块名:如果你想非常精细,也可以直接指定某个
BUILD.gn里定义的编译目标名称。 - 注意:编译系统无法识别“子系统”名。你不能直接写
--build-target security,系统不知道你要编译安全子系统下的哪一个部件。
3、如何新增一个模块?
新建模块可以分为以下三种情况。主要的添加逻辑如下面的流程图所示,若没有子系统则需新建子系统并在该子系统的部件下添加模块,若没有部件则需新建部件并在其中添加模块,否则直接在原有部件中添加模块即可,需要注意的是芯片解决方案作为特殊部件是没有对应子系统的。
-
在原有部件中添加一个模块。
-
新建部件并在其中添加模块。
-
新建子系统并在该子系统的部件下添加模块。

详细可以参考官方指导文档:新增并编译模块
1)原有部件新增模块
此场景是在已经有的部件中添加一个新的模块,所以此种情况主要需要如下几个步骤:
步骤一:在bundle.json部件配置文件添加新模块的编译入口

步骤二:添加新的模块,并添加BUILD.gn文件
步骤三:成功添加验证,编译完成后打包到image中去,生成对应的so文件或者二进制文件
2)新增部件新增模块
此场景是在一个已有的子系统中,添加新的部件,和新的模块,此种情况有如下几个步骤:
步骤一:在vendor/产商/产品/config.json配置文件中指定子系统中添加部件

步骤二:新建一个bundle.json文件,bundle.json文件均在对应子系统所在文件夹下
步骤三:在模块目录下配置BUILD.gn,根据模板类型选择对应的gn模板。这一步与在原有部件中添加一个模块的方法基本一致,只需注意该模块对应BUILD.gn文件中的part_name为新建部件的名称即可。
步骤四:成功添加验证,编译完成后打包到image中去,生成对应的so文件或者二进制文件。
3)新增子系统新增部件新增模块
此种场景是需要添加一个新的子系统,并且添加新的部件,添加新的模块,此种情况有如下几个步骤:
步骤一:在build/subsystem_config.json添加新的子系统
{
"子系统名1": { # 原有子系统1
"path": "子系统目录1",
"name": "子系统名1"
},
"子系统名2": { # 原有子系统2
"path": "子系统目录2",
"name": "子系统名2"
},
"子系统名new": { # 新增子系统new
"path": "子系统目录new",
"name": "子系统名new"
},
}
该文件定义了有哪些子系统以及这些子系统所在文件夹路径,添加子系统时需要说明子系统path与name,分别表示子系统路径和子系统名。
步骤二:在vendor/产商/产品/config.json配置文件中指定子系统中添加部件
同上
"subsystems": [
{
"subsystem": "arkui", # 原有的子系统名
"components": [ # 单个子系统下的所有部件集合
{
"component": "ace_engine_standard", # 原有的部件名
"features": []
},
{
"component": "napi", # 原有的部件名
"features": []
}
{
"component": "component_new1", # 原有子系统新增的的部件名component_new1
"features": []
}
]
},
{
"subsystem": "subsystem_new", # 新增的子系统名
"components": [
{
"component": "component_new2", # 新增子系统新增的的部件名component_new2
"features": []
}
]
},
]
步骤三:在新建的子系统目录下每个部件对应的文件夹下创建bundle.json文件,定义部件信息。这一步与新建部件并在其中添加模块中对应的步骤并无区别。
步骤四:在模块目录下配置BUILD.gn,根据模板类型选择对应的gn模板。这一步与新建部件并在其中添加模块中对应的步骤并无区别。
步骤五:成功添加验证:编译完成后打包到image中去,生成对应的so文件或者二进制文件。
更多推荐




所有评论(0)