ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

鸿蒙源码导读-02:编译构建子系统

2021-08-01 16:06:09  阅读:777  来源: 互联网

标签:02 target 鸿蒙 源码 build ohos hb gn config


本文摘录自 OHOZ 团队的 OpenHarmony 源码导读项目,在线阅读(腾讯云Github Pages)中包含最新的内容。


鸿蒙的编译构建子系统

鸿蒙中可以使用多种工具进行编译,可以将其分为高、中、低三层:
在这里插入图片描述

几种工具的对比:

Build Tool开发语言开发方资源
hpmjsHW
hbpythonHWgitee
gnC++/Pythono-limgithub
ninjaC++/Python/CninjagithubDoc

我们先从底层说起。

gn 和 ninja

说实话,理解 gn 和 ninja 对于没有接触过 make、cmake 的同学是有困难的,很难理解这些跨平台工具出现的真正意义及其要解决的问题是什么。更不要说长期使用 VS、Eclipse、XCode 等成熟 IDE 的同学,这些过程都被 IDE 屏蔽掉了,但在 Linux 和嵌入式开发中它有是空气和水一般的存在,所以,嗯……尽力吧。

编译系统从 make 到 cmake 至 gn+ninja,编译器(前后端工具)从 gcc 到 gcc+llmv 至 clang+llvm,这么多年来经历的变迁不是很多,至少相比各种编程语言的变迁少太多了。

ninja(忍者),google chromium 团队出品,致力于比 make 更快的编译系统,ninja 像是编译器(Compiler)的预处理器,主要目的是递归查找好依赖关系,提前建立依赖树,gcc 可按照依赖树依次编译,大大减少编译期间杂乱的编译顺序造成的查找和重复时间。ninja 首次在 2016 年的 Android N 中使用,当前被广泛应用在希望从编译耗时中解脱出来的大型项目中。

gn 意思是 generate ninja,即生成 Ninja 所需的文件(meta data),所以 gn 自称为元数据构建(meta-build)系统,也是 google chromium 团队出品,gn 的文件后缀为 .gn.gni

gn 类似 cmake,ninja 类似 make,cmake 最终也是生成 makefile,gn 则会生成 ninja 文件,都是为了减少手工写 make/ninja 文件的工作量。

如果使用 harmony 提供的 docker,gn 和 ninja 都已经安装好了:

root@90065f887932:/home/openharmony# gn --version
1717 (2f6bc197)
root@90065f887932:/home/openharmony# ninja --version
1.9.0

gn

由于特殊原因,以下资源都都需要科学上网:

  • gn 官网: https://gn.googlesource.com/
  • git 库: git clone https://gn.googlesource.com/gn
  • 在线文档:docsreference
  • 版本下载:LinuxmacOSwindows

如果不方便科学上网,可以 gitee 上搜索 gn 或 generate-ninja,可以看到网友搬运过来的,比如笔者搬运的 gn,其中 docs 和 examples 目录可以参考。

命令与流程

gn 的准备工作是这样的:

  1. 首先你要按照 gn 的语法在根目录手写一个 .gn 文件,,它没有文件名,只有扩展名,并且在里面至少定义 buildconfig 变量,这个变量的值是一个 config 文件的路径
  2. 这个 config 文件当然也是你手写出来的,该文件主要完成 2 件事:
    • 通过 set_defaults 函数为 4 类编译目标(executable、static_library、shared_library、source_set)定义默认配置参数
    • 通过 set_default_toolchaintool()……函数定义默认的 toolchain
  3. 最后你还要在根目录下再手写一个 BUILD.gn 文件,这个文件真正主要也是完成 1 件事:指定编译目标,即上面的 4 类目标中的一个或几个,包括:
    • 指定要编译的文件
    • 指定 include 文件的路径
    • 指定依赖的编译目标

准备工作完成后,就可以在命令行执行编译了:

  • gn gen out/xxx: 生成 ninja 能够使用的构建文件
    • 在当前目录(找不到就向上找)或 --root 指定目录 或 --dotfile 指定文件查找 .gn,找到后将其所在路径设为 root 或 .gnroot 指定的路径设置为 root,Harmony 通常是 root= 指定根目录为 build/lite/.gn
    • 解析 root 下的 gn 文件以获取 buildconfig 文件名称,执行 buildconfig 文件得到 toolchain 及其 configs。
    • 解析 root 下的 BUILD.gn 文件,加载其依赖的其它目录下的 BUILD.gn 文件
      • BUILD.gn 一般作为模块的工程入口文件,可以配合.gni 文件来划分源码或模块。
      • 当多个模块之间差异很小时,可以利用 BUILD.gn 来统一管理这些模块的设置,利用.gni 来专属负责各个模块源文件管理。
    • 惯例使用 out/xxx 来存放编译出.ninja 文件,比如: out/debugout/v0.1
    • 编译出的 ninja 文件可以使用 ninja -C out/xxx 来完成真正的版本编译。
    • Tips
      • gn gen 还可以针对 IDE 的生成工程文件,可以通过 --ide 来指定,比如:gn gen --ide [vs|xcode|eclipse|qtcreator|json]
  • check: Check header dependencies.
  • ls: List matching targets.
  • format: Format .gn files.
  • refs: Find stuff referencing a target or file.
  • clean: Cleans the output directory.

更多详细的子命令可以查看 gn help Commands 章节。

下面我们来看 gn 文件的语法:

类型与变量

gn 是一门简单的动态类型语言,有变量,变量支持的数据类型有:布尔、有符号数、字符串、列表、作用域(类似字典),用户自定义变量之外,gn 还内建了 20+ 个变量,比如:

  • current_cpu、current_os、current_toolchain
  • target_cpu、target_os、target_name、target_out_dir
  • gn_version
  • python_path

更多详细的 gn 变量可以查看 gn help Built-in predefined variables 章节。

gn 的变量的定义和使用都很直白:

board_arch = "arm"                          # 定义变量
if (board_arch != "") {                     # 使用变量
    cflags += [ "-march=$board_arch" ]      # 字符串中使用变量
    cflags_cc += [ "-march=${board_arch}" ] # 加上 {} 是等效的
}

标识是有格式要求的字符串,最终形成的依赖关系图中所有的元素(目标 Target、配置、工具链)都由标识做唯一识别,它格式要求是:

"//<path>[:<name>][(<toolchain>)]"

除了 path 不能省略外,其他都能省,如果 name 省略了则标识与 path 最后一个字段同名的那么,举例:

  • "//base/test:test_support(//build/toolchain/win:msvc)" 最完整格式,定位到 root/base/test/BUILD.gn 文件中的 test_support
  • "//base/test:test_support"
  • "//base/test" 等价与 "//base/test:test"

函数

gn 支持简单的控制语句,如:if…else、foreach 等,gn 也支持函数,并且内建了很多函数,一般很少见用户自定义函数,估计内建函数已经足够使用了吧。

gn 的函数命名和参数传递与 c、python 等编程语言的不同,参数传递使用 invoker 来传递。

gn 有 30+ 个内建函数,包括:

  • import:引入一个文件,但与 c/c++ 的 include 不同,import 的文件将独立执行并将执行结果放入当前文件。
  • getenv:获取环境变量
  • print:不解释
  • read_filewrite_file
  • foreach:迭代一个 list
  • config:定义 configuration 对象
  • set_defaults:定义某个 target 的成员变量默认值
  • template:定义一套 rule,调用 rule 能够生成一个 target

更多详细的内建 functions 可以查看 gn help Buildfile functions 章节。

举例:如果我们希望定义一些配置数据(并且有嵌套),然后赋值给某个变量,可以这样实现:

# build/config/BUILD.gn
config("cpu_arch") {                    # 用 config 函数定义一个名为 cpu_arch 的配置对象
  cflags = [ "-march=$board_arch" ]
}

config("ohos") {                        # 定义一个名为 ohos 的配置对象
  configs = [
    ":cpu_arch",                        # 包含上面 cpu_arch 的配置对象
    ":stack_protector",
  ]
}

然后就可以使用标识将配置对象赋值给变量

default_target_configs = [ "//build/config:ohos" ]

目标/功能块/Target

gn 中还有个重要概念:target,有些地方翻译成目标,我觉得不是很准确,它是构造表中的一个节点,它含有一些变量,以完成一些操作,变量就像是操作的配置数据,target 就像是一段封装好的操作模块——所以我觉得翻译成功能块更合适些。target 的写法是:

<target>("<name>") {
    <var> = ...
}

举例:copy target 可以根据 sources 和 outputs 变量实现文件拷贝:

copy("compiler") {
    sources = [
      "//prebuilts/gcc/linux-x86/arm/arm-linux-ohoseabi-gcc/arm-linux-musleabi",
      "//prebuilts/gcc/linux-x86/arm/arm-linux-ohoseabi-gcc/arm-linux-ohoseabi",
    ]
    outputs = [ "$ndk_linux_toolchains_out_dir/{{source_file_part}}" ]
  }

举例:action target 可以完成 script 变量指定的脚本,Harmony 中 build/lite/BUILD.gn 中生成跟文件系统的操作,使用了 action target:

  action("gen_rootfs") {
    deps = [ ":ohos" ]

    script = "//build/lite/gen_rootfs.py"           # 执行此 python 文件
    outputs = [ "$target_gen_dir/gen_rootfs.log" ]  # 输出 log 文件
    out_dir = rebase_path("$root_out_dir")

    args = [                                        # python 文件可以接受的命令行参数
      "--path=$out_dir",
      "--kernel=$ohos_kernel_type",
      "--storage=$storage_type",
      "--strip_command=$ohos_current_strip_command",
      "--dmverity=$enable_ohos_security_dmverity",
    ]
  }

由于 gn 就是 python 写的,所以可以完美的兼容 python 脚本来执行操作。

举例source_set 是非常关键的一个 target,定义了源码集,gn 会对其逐一生成 .o 文件,其中 configs 变量定义了编译时送给编译器的参数。比如前文已经定义好了 default_target_configs 变量,现在就可以使用 set_defaults 函数中来定义 source_set target 中的 configs 变量了。

set_defaults("source_set") {
  configs = default_target_configs
}

至于使用哪些编译器,gn 使用 set_default_toolchain 函数定义:

set_default_toolchain("//build/lite/toolchain:gcc-arm-none-eabi")

举例gn help template 给出了一个例子,非常典型的使用了

  • 函数:template、assert、get_target_outputs
  • Target:action_foreach、source_set、executable

首先定义使用 template 定义一个 rule,叫做 my_idl:

template("my_idl") {
    # 先对入参做一个判断,以免报错,对使用者抛error是没有给出错误提示来的优雅。
    assert(defined(invoker.sources),
           "Need sources in $target_name listing the idl files.")

    # 定义一个过程变量
    code_gen_target_name = target_name + "_code_gen"

    # action_foreach 是个 target,能够根据 {...} 内的参数执行 script 指定的脚本
    # 本 action 是希望把 idl 文件生成出 .cc 和 .h 文件
    action_foreach(code_gen_target_name) {
      sources = invoker.sources
      script = "//tools/idl/idl_code_generator.py"
      # 告诉 gn 如何存放 output 文件,"gn help source_expansion" 有更多细节
      outputs = [ "$target_gen_dir/{{source_name_part}}.cc",
                  "$target_gen_dir/{{source_name_part}}.h" ]
    }

    source_set(target_name) {
      sources = get_target_outputs(":$code_gen_target_name")
      deps = [ ":$code_gen_target_name" ] # 指定其依赖上面 action_foreach(code_gen_target_name)
    }
  }

然后看如何使用(invoking)template:

my_idl 是上面 template 定义的一个 rule,rule 使用起来像函数,但千万不要把对应位置理解成入参和函数体,rule 的这 2 个位置分别放置:target 名称、入参。

  # 由 rule 生成一个名为 foo_idl_files 的 target
  my_idl("foo_idl_files") {
    # 这里定义的变量会转发给 rule 定义中,使用 invoker.xxx 引用,所以这是入参
    sources = [ "foo.idl", "bar.idl" ] # 对应 template 中的使用 invoker.sources
  }

我们可 gn 内建 target 的使用,也是 <target_type>(<target_name>) { <target_invoker>}:

  executable("my_exe") {
    deps = [ ":foo_idl_files" ] # my_exe 这个 target 依赖 foo_idl_files 这个 target
  }

gn 默认定义了很多 Target(功能块),比如:

  • 执行某个或某组动作
    • action:单次运行的动作
    • action_foreach:在多个文件中依次运行脚本的 target
    • copy:执行 copy 动作
  • 生成最终目标 —— 下面这 4 个非常必要,必须选择一个或多个进行定义
    • excutable:指定 target 是个可执行文件
    • source_set:定义源码集,会逐一对应生成 .o 文件,即尚未链接(link)的文件
    • shared_library、static_library:声明一个动态(win=.dll、linux=.so)、静态库(win=.lib、linux=.a
  • 辅助类
    • group:声明一个 target

每个 targte 都有自己的用法,ge help <target> 查看,里面都会有一段 demo 代码可以拿来直接使用。

更多详细的 Target 可以查看 gn help Target declarations 章节。

args 传参

用户如何向 gn 传递项目自定义的参数呢?有这样几种方式:

$ gn gen out/debug --args="key=int_value  key1=\"str_value\" key3=\"$(PWD)\""

gn gen 时通过 --args 传递进去,因为后面整体被看做一个参数,所以需要用 "" 引号整体包围,如果 value 是数字,可以不加 \",如果是字符串或 shell 变量,则需要添加 \"

$ gn args out/debug [--args=...]

gn args 会先在 out/debug 下创建 args.gn 文件,然后用编辑器打开(就行 git commit 打开一个编辑器让用户填写 log 一样),等待用户添加 key = value。同时 --args 也可以添加到命令行里,规则和上面一样。

gn 内建了 6 个变量,即:即使用户不做任何指定、赋值,.gnxxx.gnixxx.gn 都可以使用,gn 会自定义赋值的:

  • host_cpu
  • host_os
  • current_cpu
  • current_os
  • target_cpu
  • target_os

root 下的 .gn 文件也可以使用 default_args 变量 为这 6 个变量赋默认值,比如指定不同的 target_cpu:

default_args = {
    target_os = "freertos"
    target_cpu = "cortex-m4"
}

用户自定义的 arg,则需要多一个步骤,在 BUILDCONFIG.gn 或 BUILD.gn (取决于用户希望起效的作用域)中使用 declare_args() 函数进行定义,并赋默认值,如:

declare_args() {
    ohos_build_type = "debug"
}

然后,.gnxxx.gnixxx.gn 中就可以像普通变量一样始终这些 args 了:

if (target_os == "linux") {
    ...
}
if (ohos_build_type == "debug") {
    ...
}

掌握 gn 的命令、函数、变量、标识、Target……等概念,基本就能够使用 gn 完成任务了。我们在后续 hb 章节里则会去分析具体的 Harmony 代码了。

ninja

gn 会在 out/<target>/<board>/ 下生成 build.ninja 文件,这是个普通的文本文件,打开之后甚至觉得就是个 log 文件,这估计就是 ninja 创始人说的极简哲学吧。

$ ninja -C out/<target>/<board>/ 可以编译出目标文件或可执行文件。

参考:

build_lite 与 hb

本章节解析的组件及其对应的目录、git 库如下:

hpm 组件名源码目录gitee url
@ohos/build_litebuild/litehttps://openharmony.gitee.com/openharmony/build_lite

Harmony 还另外开源了一个 build 组件:

hpm 组件名源码目录gitee url
@ohos/buildbuildhttps://openharmony.gitee.com/openharmony/build

build_lite 是为编译嵌入式单板准备的,build 是为编译 Phone 等智能终端准备的。

build_lite 主要功能实现了一个名为 hb 的 Python 包,能够安装到 Python 环境中,除此之外,还包括:

  • 描述文件(.json)
    • components:组件描述文件
  • 制作脚本(.sh)
    • make_rootfs:文件系统制作脚本
  • 配置文件(.gn、.gni)
    • config:编译相关的配置项(组件、内核、子系统)
    • ndk:Native API 相关编译脚本与配置参数
    • toolchain
  • xx 文件(.ld)
    • platform:

gn 总入口

鸿蒙把 build/lite 独立出一个开源项目,将其设计为 gn 的 root,已经替我们生成好了相关的 gn 文件,build/lite/.gn 文件就是 gn 编译 Harmony 的总入口,让我们一探其源码吧。

.gnBUILDCONFIG.gn

里面只有 2 句:

buildconfig = "//build/lite/config/BUILDCONFIG.gn"
root = "//build/lite"

BUILDCONFIG.gn 代码关键架构:

import("//build/lite/ohos_var.gni")
import("${device_path}/config.gni")

  target_os = "ohos"        # 这是个 gn 内建变量,对其赋值
  target_cpu = board_cpu    # 这是个 gn 内建变量,对其赋值

# 配置 toolchain
if (board_toolchain != "" && use_board_toolchain) { # 使用 device 指定的 toolchain
    # 其中会用到 board_toolchain、board_toolchain_path、board_toolchain_prefix 等变量
    # 这些变量都是从 import("${device_path}/config.gni") 而来的
    # 最终初始化了几个核心变量
    ohos_current_cc_command = "${compile_prefix}gcc"
    ohos_current_cxx_command = "${compile_prefix}g++"
    ohos_current_ar_command = "${compile_prefix}ar"
} else {                                            # 使用默认 toolchain
}

上面这段定义了编译鸿蒙的 toolchain,其中 ${device_path} 是下面章节中 hb 命令送入的,一般对应整个工程源码目录下的 device/.../.../config.gni,比如 device/hisilicon/hispark_pegasus/sdk_litos/config.gni,其中可以看到 device 对 toolchain 的个性化定义。

BUILDCONFIG.gn 文件下面会使用 set_defaults 函数完成 executable、static_library、shared_library、source_set 四个 target 的创建。

# 定义临时变量 default_target_configs
default_target_configs += [
  "//build/lite/config:board_config",
  "//build/lite/config:cpu_arch",
  "//build/lite/config:common",
  "//build/lite/config:default_link_path",
  # 下面还要几十行代码继续对 default_target_configs 追加赋值
]

# 创建 source_set target,其他 executable、static_library、shared_library 同理
set_defaults("source_set") {
  configs = default_target_configs
}

"//build/lite/config:cpu_arch" 是前文所说的标识,表示从 build/lite/config 下的 BUILD.gn 文件中提取 cpu_arch 对象,它的定义

config("cpu_arch") {
  cflags = []
  if (board_arch != "") {
    cflags += [ "-march=$board_arch" ]
  }
  if (board_cpu != "") {
    cflags += [ "-mcpu=$board_cpu" ]
  }
  cflags_cc = cflags
  ldflags = cflags
}

这是一个使用 config 函数定义的 object,目的是根据 board_arch 定义不同的 -mcpu 参数。

整个 BUILDCONFIG.gn 文件主要完成了 2 件事:

  1. 定义 toochain
  2. 定义 4 个 target:executable、static_library、shared_library、source_set

为后续的 gn 操作准备最基础的内容。

BUILD.gn

回忆前文 gn 的总流程,执行完 .gn 后,就要执行 root 下的 BUILD.gn 文件了。该文件整体架构也非常简单:定义 4 个 target:2 个 group、2 个 action。

import("//build/lite/ndk/ndk.gni")

# 定义 2 个 group target
group("ohos") {...}
group("ndk") {...}

# 定义 2 个 action target
if (ohos_build_target == "") {
  action("gen_rootfs") {...}
if (ohos_build_type == "debug" && product != "") {
  action("gen_testfwk_info") {...}

读到这里,我们可以看出,gn 是一个非常灵活的语言,写起来像 python,比 makefile 要方便的多,可以在 object 中嵌入 if…else…,使用标识可以像 url 定位一样快速引入一段其他文件定义的代码段。

OK,gn 先解读到这里,BUILD.gn*.gni 文件应该已经都能看懂了,那还差一个问题:鸿蒙是最初进入 gn 的 root 之前,到底还做了什么?那就是下面 hb 要做的事情了,让我们继续往下读。

python 包 hb 及其命令行

build_lite/hb 是个 Python Package,其 setup.py 中可见其生成了一个 hb 的命令,入口地址是 build/lib/hb/__main__.py 文件中的 main 函数。

setup(
    name='ohos-build',
    package_dir={'hb': 'hb'},
    entry_points={'console_scripts': ['hb=hb.__main__:main',]},
)

既然 build_lite 是一个普通的 Python 包,那么在实际场景中就有多种方式了:

  • 获取 build_lite 源码
    1. git clone https://openharmony.gitee.com/openharmony/build_lite
    2. hpm i @ohos/hispark_pegasusbuild/lite 则已经被下载
  • 安装 hb
    1. 可以把 hb 安装到系统的 Python 环境中: pip install [--user] build/lite
    2. 可以把 hb 安装到 python 虚拟环境中:python3 -m venv venv; source venv/bin/activate; pip install build/lite
    3. 由于 hb 已经是 Pypi 上的发布包,名字是 ohos-build,所以也可以这样安装 pip install [--user] ohos-build
    4. 如果使用 docke run ...,则 hb 已经安装好了
  • 使用 hb
    1. 源代码使用:python3 -c 'import inspect,hb; print(inspect.getfile(hb))' 查看 hb 安装的路径,能够正确查询则表示可以 import hb,后续按照 python 规则开发即可。
    2. 命令行使用:hb [set|env|build]
    3. python build.py [build|clean|debug|release] 直接使用 build/lite 目录下的 build.py 文件

请添加图片描述

$ ls build/lite/hb
__init__.py __pycache__ clean       cts         env
__main__.py build       common      deps        set

hb 将每个子命令的实现放在一个文件夹中:set、build、clean、env……

当执行 hb sethb build 的时候进入每个文件夹中执行 exec_command() 函数。

hb set

执行 build/lite/hb/set/set.py 中的 exec_command() 函数:

def exec_command(args):
    return set_root_path() == 0 and set_product() == 0

set_root_path()set_product() 分别解析出 root 路径和产品相关信息,写入 ohos_config.json 文件中。

def set_root_path(root_path=None):
    config = Config()
    if root_path is None:
        root_path = get_input('[OHOS INFO] Input code path: ')
    config.root_path = root_path
    return 0

命令行里执行 hb set 给出的提示即此上面函数打印。Config 是一个单例 class(即:此函数配置的 config 实例值,其他函数都可获取):

class Config(metaclass=Singleton):

Config 单例定义了多个属性:root_path、board、kernel、product、product_path、device_path、out_path……,当做左值的时候会写入 ohos_config.json 文件。

另外一个函数 set_product() 即是为了配置 Product,Product 是 hb 为产品定义的 class,包含几个静态方法,基本都是解析出配置值,写入 ohos_config.json 文件:

$ cat common/product.py|grep -B1 'def '
    @staticmethod
    def get_products():
--
    @staticmethod
    def get_device_info(product_json):
--
    @staticmethod
    def get_features(product_json):
--
    @staticmethod
    def get_components(product_json, subsystems):

静态方法望文知意:

  • get_products(): 获取产品信息,递归查找 vender/ 下包含 config.json 文件的目录,每找到一个即算一个 Product,其中的 config.json 通常包括 vender 预先定义好的发行版配置。
$ find vendor/ -name config.json
vendor/hisilicon/hispark_aries/config.json
vendor/hisilicon/hispark_pegasus/config.json
vendor/hisilicon/hispark_taurus/config.json

上面是 repo sync 获取的源码中的 vender 情况,所以在执行 hb set 时会提示 3 个选项:

$ hb set
[OHOS INFO] Input code path: .
OHOS Which product do you need?  (Use arrow keys)

hisilicon
 ❯ ipcamera_hispark_aries
   wifiiot_hispark_pegasus
   ipcamera_hispark_taurus
  • get_device_info()get_features()get_components(): 获取 vender 定义的 config.json 中的各种信息,比如:
$ cat vendor/hisilicon/hispark_pegasus/config.json | head -n18
{
    "product_name": "wifiiot_hispark_pegasus",
    "ohos_version": "OpenHarmony 1.0",
    "device_company": "hisilicon",
    "board": "hispark_pegasus",
    "kernel_type": "liteos_m",
    "kernel_version": "",
    "subsystems": [
      {
        "subsystem": "applications",
        "components": [
          { "component": "wifi_iot_sample_app", "features":[] }
        ]
      },
      {
        "subsystem": "iot_hardware",
        "components": [
          { "component": "iot_controller", "features":[] }

前面 hb set 给出的 3 个选项是这里的 product_name。device_info 包括上面的 device、board、kernel;features 和 components 是每个 subsystems 中的信息。

每个 subsystem 对应一个源代码的目录,component 是它依赖的模块,统一放在 ohos_bundles 下面。

hb build

执行 build/lite/hb/build/build.py 中的 exec_command() 函数,该函数主要处理用户的入参,如:

  • -b:debug 或 release
  • -c:指定编译器,默认是 clang
  • -t:是否编译 test suit
  • -f:full,编译全部代码
  • -t:是否编译 ndk,本地开发包,这也是 @ohos/build_lite 组件的一部分
  • -T:单模块编译
  • -v:verbose

使用这些入参实例化 Build 类:

class Build():
    def __init__(self):
        self.config = Config()
        ......

    def build(self, full_compile, ninja=True, cmd_args=None):
        ......

    def check_in_device(self):
        ......

    def gn_build(self, cmd_args):
        ......

    def ninja_build(self, cmd_args):
        ......

实例化后调用 build.build(),它会依次调用 check_in_device()gn_build()ninja_build()

  • check_in_device():读取编译配置,根据产品选择的开发板,读取开发板 config.gni 文件内容,主要包括编译工具链、编译链接命令和选项等。
  • gn_build():调用 gn gen 命令,读取产品配置生成产品解决方案 out 目录和 ninja 文件。核心代码如下:
            gn_cmd = [gn_path,
                    '--root={}'.format(self.config.root_path),
                    '--dotfile={}/.gn'.format(self.config.build_path),
                    'clean',
                    self.config.out_path]
            exec_command(gn_cmd, log_path=self.config.log_path)
    
  • ninja_build():调用 ninja -C out/board/product 启动编译。核心代码如下:
            ninja_cmd = [ninja_path,
                        '-w',
                        'dupbuild=warn',
                        '-C',
                        self.config.out_path] + ninja_args
            exec_command(ninja_cmd, log_path=self.config.log_path, log_filter=True)
    
  • 系统镜像打包:将组件编译产物打包,设置文件属性和权限,制作文件系统镜像。

python build.py

根目录下的 build.py 通常是 build/lite/build.py 的软连接,执行 python build.py 时会运行到 build.py 的 build() 函数:

def build(path, args_list):
    cmd = ['python3', 'build/lite/hb/__main__.py', 'build'] + args_list
    return check_output(cmd, cwd=path)

可见,仍是执行 hb build,入参也可以平移过来,所以可以这么使用:

python build.py ipcamera_hi3518ev300 -b debug # 全量编译为 debug 版本
python build.py ipcamera_hi3518ev300 -T applications/sample/camera/app:camera_app # 单模块编译

可以说,build.py 实现了“不安装 hb 也能编译”的目的,其他好像没做什么。

简要流程

在这里插入图片描述

History

  • 2020.12.05: 内核从 liteos_riscv 更名为 liteos_m,build 做适配。
$ git -P log -n1 897188
commit 8971880bd4f08a2ea01e83dfaadcf7cda7aae858
Author: p00452466 <p00452466@notesmail.huawei.com>
Date:   Sat Dec 5 03:07:19 2020 +0800

    Description:add Change kernel type from liteos_riscv to liteos_m
    Reviewed-by:liubeibei
  • 20210318: 支持独立的外接设备驱动组件编译
$ git -P log -n1 814c81
commit 814c816f9b7f900113bed0f75a8122dba5555f65
Merge: 3dc5b1d 5353b23
Author: openharmony_ci <7387629+openharmony_ci@user.noreply.gitee.com>
Date:   Thu Mar 18 19:58:42 2021 +0800

    !44 组件化解耦修改--支持独立的外接设备驱动组件编译
    Merge pull request !44 from kevin/0316_release_build
  • 2021.03.20: 本模块已经提交到 pypi,链接
$ git -P log -n1  958189
commit 95818940a0bc47d25e7454c4d37732e90f7d2df8
Author: pilipala195 <yangguangzhao1@huawei.com>
Date:   Sat Mar 20 12:35:48 2021 +0800

    Upload ohos_build to Pypi
  • 2021.04.03: 构建不再需要先 hb set,可以直接 hb build
$ git -P log -n1 32d740
commit 32d7402125db0c46c43b05322e588a692f96827a
Author: SimonLi <likailong@huawei.com>
Date:   Sat Apr 3 08:55:13 2021 +0800

    IssueNo: #I3EPRJ
    Description: build device with no need to hb set
    Sig: build
    Feature or Bugfix: Feature
    Binary Source: No

hpm

hpm 是 2020 下半年开始,HW 开发的包管理平台,js 语言,npm 安装和更新:

$ npm install -g @ohos/hpm-cli # 安装
$ npm update  -g @ohos/hpm-cli # 更新
$ npm rm      -g @ohos/hpm-cli # 卸载

基本命令

  • hpm init [-t template] 在一个文件夹中初始化一个 hpm 包,主要是创建 bundle.json 文件
$ hpm init -t dist
Initialization finished.
$ cat bundle.json
{
  "name": "dist",
  "version": "1.0.0",
  "publishAs": "distribution",
  "description": "this is a distribution created by template",
}
  • hpm i|install [name] 下载依赖并安装,必须在已经 hpm init 的目录下执行
$ hpm i @ohos/hispark_pegasus
  • hpm d|download [name] 仅下载指定包(tgz 文件),不下载依赖,可以在任何目录中执行
$ hpm d @ohos/hispark_pegasus
$ ls @ohos-hispark_pegasus-1.0.3.tgz
@ohos-hispark_pegasus-1.0.3.tgz
  • hpm list 打印依赖关系图
$ hpm list
+--dist@1.0.0
│ +--@ohos/hispark_pegasus@1.0.3
│ │ +--@ohos/bootstrap@1.1.1
│ │ +--@ohos/bounds_checking_function@1.1.1
  • hpm pack 打包组件(bundle),生成 tgz 文件。
$ hpm pack
> Packing dist-1.0.0.tgz /home/kevin/workspace/harmony/src/hpm.i/@hihope-neptune_iot
>   directory .
>     . . bundle.json
>     . . README.md
>     . . LICENSE
> Packing dist-1.0.0.tgz finished.

harmony 的组件(bundle)和发行版(distribution)之间是包含关系,组件由代码 + bundle.json + README + LICENSE 组成,发行版由 多个组件 + scripts 组成,官方给出的关系图:
请添加图片描述

  • hpm ui 创建 http 访问的前端,在浏览器上可查看多种信息,执行多种命令,也可以在 docker 中执行,在 host 中浏览器访问。
    请添加图片描述

hpm 迭代很快,尤其是 2021.6.2 发布 Harmony2.0 以后,几天一更新,所以,即使使用 docker 容器,也建议先升级一下 hpm,以获取最新版本的特性。

源码解析

hpm 相比 hb,增加了包管理的概念,不再是纯的编译框架,hb 无法管理包之间的依赖关系,以及同一个包的多版本控制,hpm 类似 pip、npm 解决这些问题。

hpm-cli 在 npm 官网 上看,2020.8 提交 0.0.1 版本,但一直都没什么下载量,直到 2021.5 才开始有下载。源码暂时没找到,只能从其安装路径中看到一些:

$ hpm -V
1.2.6
$ which hpm
/home/kevin/.nvm/versions/node/v14.15.0/bin/hpm
$ ls ~/.nvm/versions/node/v14.15.0/lib/node_modules/@ohos/hpm-cli
bin  hpm-debug-build.js  lib  LICENSE  node_modules  package.json  README.md  README_ZH.md

hpm 为每个子命令定义了一个 js 文件

$ ls ~/.nvm/versions/node/v14.15.0/lib/node_modules/@ohos/hpm-cli/lib/commands
build.js        download.js      init.js     publish.js  uninstall.js
checkUpdate.js  extract.js       install.js  run.js      update.js
code.js         fetch.js         lang.js     script.js
config.js       generateKeys.js  list.js     search.js
distribute.js   index.js         pack.js     ui.js

每次执行 hpm xxx 命令,main.js 解析入参并转给相应的 command(lib/commands/xxx.js),每个命令的执行逻辑可参考代码,比如 dist 会检查 build 框架,然后交权给 build 命令,build 会先检查依赖,然后进行单线程 or 多线程编译,这里的编译依然会使用 gn 和 ninja,编译完毕后 dist 会进行打包。
在这里插入图片描述

History

  • 1.1.0(202104):新增 GUI,hpm ui 启动
  • 1.2.3(202106):新增 fetchdownloadcode 子命令

DevEco Device Tool

HUAWEI DevEco Device Tool(下文简称 DDT)是 HarmonyOS 面向智能设备开发者提供的一站式集成开发环境,它比 hpm 提供了更多的功能:组件按需定制,支持代码编辑、烧录和调试等。

所以 DDT 已经不再局限与本文所讨论的编译,但 DDT 的编译过程又比较特殊,它更加灵活的使用 hb、hpm 等工具,并又开发了一个 hos。当你使用 DDT build 的时候,执行了这个命令:

/home/kevin/.deveco-device-tool/core/deveco-venv/bin/hos run --project-dir /home/kevin/workspace/harmony/src/bearpi --environment bearpi_hm_nano

DDT 安装在 ~/.deveco-device-tool,主要含 3 个文件夹:core、platforms、plugins

core 包含了编译、调试、烧录工具,和 python 的虚拟环境:

$ ls .deveco-device-tool/core
arm_noneeabi_gcc  deveco-venv                     tool_hiburn                 tool_openocd
contrib_pysite    feature-toggling-manifest.json  tool_lldb                   tool_scons
deveco-home       tool_burn                       tool_openlogic_openjdk_jre  tool_trace

platforms 包含针对不同 SoC 厂家的编译工具,海思、联盛德、NXP……每家一个文件夹,大多是 python 实现,其中有些含 hb.py,有些没有 hb,看来定制化已经让编译工具五花八门,HW 也不管了,自家分开玩儿吧。

$ ls .deveco-device-tool/platforms
asrmicro  bestechnic  blank  bouffalo  hisilicon  nxp  realtek  winnermicro  xradio

其中的 asrmicro(翱捷科技)、bestechnic(恒玄科技)、bouffalo(博流科技)、xradio(芯之联)都还没见到其开发板,应该在开发中或已经 alpha 状态了。

plugins 中包含 VSCode 的扩展文件

$ ls .deveco-device-tool/plugins
deveco-device-tool-2.2.0+285431.76f4090e.vsix

由于 DDT 既不开源,也缺乏文档,所以暂时很难解读,以后再说。

官方资源:

总结

总体流程图

在这里插入图片描述

下载方式-编译方式对比表

对比项HarmonyOS (repo)neptune (hpm)pegasus (hpm)3861 (DDT)bearpi (DDT)3516/8 (DDT)
别称*HH-SLNPT10xHi3861V100
SoC*WinnerMicro W800Hi3861Hi3861Hi3861Hi3516/18
SoC Kernel*玄铁 804(RISC-V)RISC-V同左同左Cortex-A7
外设*2MB(F)+288KB®
特色*WiFi、BT2.4GHz WiFi同左
Vendor*润和(hihope)海思(HiSili)同左小熊派
/build.pyY--
/.deveco---YY
/.vscode---YY
/deviceY[Y]-
/vendorY-Y
/build/YYYY--
/build/lite/hbY-Y---
hb buildY
python build.pyY
hpm distYY
DDT buildYYY
  • DDT 方式下载的有 .deveco.vscode 文件夹,编译也需要 .deveco
  • DDT 可以使用已经安装的 platforms 中的 build 工具,所以 build/lite 都不需要了,hb buildpython build.py 也不可用了。

参考

  • pegasus: 飞马、天马
  • neptune:海王星
  • taurus:金牛座
  • aries:白羊座
  • WinnerMicro:北京联盛德微电子

标签:02,target,鸿蒙,源码,build,ohos,hb,gn,config
来源: https://blog.csdn.net/kevin881/article/details/119298709

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有