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

一、编译入口

我们先看看纯血鸿蒙系统的开源代码官方介绍说明:

即直接执行./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 → NinjaPreuiltsService(预建件)+ 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 / templateHPM 生态扩展类型(工具插件、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 跨语言调用。
  • 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文件或者二进制文件。

Logo

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

更多推荐