鸿蒙内核源码分析(gn应用篇) | gn语法及在鸿蒙的使用

gn是什么?

gn 存在的意义是为了生成 ninja,如果熟悉前端开发,二者关系很像 SassCSS的关系.
为什么会有gn,说是有个叫even的谷歌负责构建系统的工程师在使用传统的makefile构建chrome时觉得太麻烦,不高效,所以设计了一套更简单,更高效新的构建工具gn+ninja,然后就被广泛的使用了.

gn语法和配置

gn 有大量的内置变量和库函数,熟悉这些库函数基本就知道gn能干什么,gn的官方文档很齐全.
gn 的语法简单,了解以下几点基本就能看懂gn代码.重点了解下函数的调用方式,和其他高级语言不太一样.

字符串

a = "mypath"
b = "$a/foo.cc"  # b -> "mypath/foo.cc"
c = "foo${a}bar.cc"  # c -> "foomypathbar.cc"

列表

a = [ "first" ]
a += [ "second" ]  # [ "first", "second" ]
a += [ "third", "fourth" ]  # [ "first", "second", "third", "fourth" ]
b = a + [ "fifth" ]  # [ "first", "second", "third", "fourth", "fifth" ]

条件语句

if (is_linux || (is_win && target_cpu == "x86")) {
    sources -= [ "something.cc" ]
  }else {
    ...
  }

循环

foreach(i, mylist) {
  print(i)  # Note: i is a copy of each element, not a reference to it.
}

函数调用

print("hello, world")
assert(is_win, "This should only be executed on Windows") # 如果is_win为真,就打印后面的内容
static_library("mylibrary") { 
  sources = [ "a.cc" ]
}

解读:

  • print,assert,static_library都是库函数,或者叫内置函数.
  • static_library的调用有点奇怪,它是个函数,函数内部会使用sources这个内置变量,sources = [ "a.cc" ]相当于先传参给这个函数的内置变量,并调用这个函数.学习ng得习惯这种语法方式,被大量的使用.

模板 | Templates

gn提供了很多内置函数,使用偏傻瓜式,若构建不复杂的系统,熟悉这些内置函数,选择填空就可以交作业了,如果想高阶点,想自己定义函数怎么办? 答案是模板,模板就是自定义函数.

#定义模板, 文件路径: //tools/idl_compiler.gni, 后缀.gni 代表这是一个 gn import file
template("idl") { #自定义一个名称为 "idl"的函数
  source_set(target_name) { #调用内置函数 source_set
    sources = invoker.sources #invoker为内置变量,含义为调用者内容 即:[ "a", "b" ]的内容
  }
}


#如何使用模板, 用import,类似 C语言的 #include
import("//tools/idl_compiler.gni")
idl("my_interfaces") { #等同于调用 idl
  sources = [ "a", "b" ] #给idl传参, 参数的接收方是 invoker.sources
}

明白了模板的使用,阅读鸿蒙gn代码就不会有太大的障碍.

目标项 | Targets

目标是构建图中的一个节点。它通常表示将生成某种可执行文件或库文件。整个构建是由一个个的目标组成.
目标包含:

action: 运行脚本以生成文件
executable: 生成可执行文件
group: 生成依赖关系组
shared_library: 生成.dll或.so动态链接库
static_library: 生成.lib或.a 静态链接库
...

配置项 | Configs

记录完成目标项所需的配置信息,例如:

config("myconfig") {#创建一个标签为`myconfig`的配置项
    include_dirs = [ "include/common" ]
    defines = [ "ENABLE_DOOM_MELON" ]
  }

executable("mything") {#生成可执行文件
  configs = [ ":myconfig" ]#使用标签为`myconfig`的配置项来生成目标文件
}

gn在鸿蒙中的使用

有了以上基础铺垫,正式开始gnopenharomny中的使用.

从哪开始

在构建工具篇中已经说清楚了 hbpython部分有个工作任务是生成gn命令所需参数. gn生成ninja的命令是 gn gen ...

  /home/tools/gn gen /home/openharmony/code-v1.1.1-LTS/out/hispark_aries/ipcamera_hispark_aries \
  --root=/home/openharmony/code-v1.1.1-LTS \
  --dotfile=/home/openharmony/code-v1.1.1-LTS/build/lite/.gn \
  --script-executable=python3 \
  '--args=ohos_build_type="debug" \
          ohos_build_compiler_specified="clang" \
          ohos_build_compiler_dir="/home/tools/llvm" \
          product_path="/home/openharmony/code-v1.1.1-LTS/vendor/hisilicon/hispark_aries" \
          device_path="/home/openharmony/code-v1.1.1-LTS/device/hisilicon/hispark_aries/sdk_liteos" \
          ohos_kernel_type="liteos_a" \
          enable_ohos_appexecfwk_feature_ability = false \
          ohos_full_compile=true'

解读

  • root,dotfile,script-executable是gn内置的固定参数,一切从dotfile指向的文件开始.即build/lite/.gn 相当于main()函数的作用,
  • 打开 build/lite/.gn看下内容,只有两句,先配置好内部参数再工作.
    # The location of the build configuration file. #1.完成gn的配置工作
    buildconfig = "//build/lite/config/BUILDCONFIG.gn" 

    # The source root location. #2.完成gn的编译工作
    root = "//build/lite" 
  • args为用户自定义的参数,它们将会在解析BUILD.gn,BUILDCONFIG.gn过程中被使用.

BUILDCONFIG.gn | 构建配置项

BUILDCONFIG.gnBUILD.gn做准备,填充好编译所需的配置信息.即生成配置项
详细请查看 build/lite/config/BUILDCONFIG.gn文件全部内容,本篇只贴出部分.

import("//build/lite/ohos_var.gni")
import("${device_path}/config.gni")
....
arch = "arm"
if (ohos_kernel_type == "liteos_a") {
  target_triple = "$arch-liteos"
} else if (ohos_kernel_type == "linux") {
  target_triple = "$arch-linux-ohosmusl"
}
...
template("executable") { #生成可执行文件
  executable(target_name) {
    forward_variables_from(invoker, Variables_Executable)
    if (!defined(invoker.deps)) {
      deps = [ "//build/lite:prebuilts" ]
    } else {
      deps += [ "//build/lite:prebuilts" ]
    }
    if (defined(invoker.configs)) {
      configs = []
      configs += invoker.configs
    }
  }
}
set_defaults("executable") {#设置目标类型的默认值
  configs = default_executable_configs
  configs += [ "//build/lite/config:board_exe_ld_flags" ]
}
...

解读

  • ${device_path}为命令行参数,本篇为home/openharmony/code-v1.1.1-LTS/device/hisilicon/hispark_aries/sdk_liteos
  • 查看构建-配置内容,BUILDCONFIG主要任务就是对其中的内置变量赋值.例如:
    • 指定编译器 clang,
    • 如何生成可执行文件的方法等

BUILD.gn | 启动构建

查看 build/lite/BUILD.gn文件,文件注解较多.

#目的是要得到项目各个模块的编译入口
group("ohos") {
  deps = []
  if (ohos_build_target == "") {
    # Step 1: Read product configuration profile.
    # 第一步:读取配置文件product_path的值来源于根目录的ohos_config.json,如下,内容由 hb set 命令生成
    # {
    #  "root_path": "/home/openharmony",
    #  "board": "hispark_aries",
    #  "kernel": "liteos_a",
    #  "product": "ipcamera_hispark_aries",
    #  "product_path": "/home/openharmony/vendor/hisilicon/hispark_aries",
    #  "device_path": "/home/openharmony/device/hisilicon/hispark_aries/sdk_liteos",
    #  "patch_cache": null
    #}
    product_cfg = read_file("${product_path}/config.json", "json")

    # Step 2: Loop subsystems configured by product.
    # 第二步:循环处理各自子系统,config.json中子系统部分格式如下hb
    #"subsystems": [
    #  {
    #    "subsystem": "aafwk",
    #    "components": [
    #      { "component": "ability", "features":[ "enable_ohos_appexecfwk_feature_ability = false" ] }
    #    ]
    #  },
    #  ...
    #  {
    #    "subsystem": "distributed_schedule",
    #    "components": [
    #  	{ "component": "system_ability_manager", "features":[] },
    #  	{ "component": "foundation", "features":[] },
    #  	{ "component": "distributed_schedule", "features":[] }
    #    ]
    #  },
    #  {
    #      "subsystem": "kernel",
    #      "components": [
    #        { "component": "liteos_a", "features":[] }
    #      ]
    #   },
    #]
    foreach(product_configed_subsystem, product_cfg.subsystems) {#对子系统数组遍历操作
      subsystem_name = product_configed_subsystem.subsystem	#读取一个子系统 aafwk,hiviewdfx,security ==
      subsystem_info = {
      }

      # Step 3: Read OS subsystems profile.
	    # 第三步: 读取各个子系统的配置文件
      subsystem_info =
          read_file("//build/lite/components/${subsystem_name}.json", "json")

      # Step 4: Loop components configured by product.
      # 第四步: 循环读取子系统内各控件的配置信息
      # 此处以内核为例://build/lite/components/kernel.json"
      # "components": [
      #   {
      #     "component": "liteos_a",              # 组件名称
      #     "description": "liteos-a kernel",     # 组件一句话功能描述
      #     "optional": "false",                  # 组件是否为最小系统必选
      #     "dirs": [                             # 组件源码路径
      #       "kernel/liteos_a"
      #     ],
      #     "targets": [                          # 组件编译入口
      #       "//kernel/liteos_a:kernel"
      #     ],
      #     "rom": "1.98MB",                      # 组件ROM值
      #     "ram": "",                            # 组件RAM估值
      #     "output": [                           # 组件编译输出
      #       "liteos.bin"
      #     ],
      #     "adapted_board": [                    # 组件已适配的主板
      #       "hispark_aries",
      #       "hispark_taurus",
      #       "hi3518ev300",
      # 	  "hi3516dv300",
      #     ],
      #     "adapted_kernel": [ "liteos_a" ],     # 组件已适配的内核
      #     "features": [],                       # 组件可配置的特性
      #     "deps": {
      #       "components": [],                   # 组件依赖的其他组件
      #       "third_party": [                    # 组件依赖的三方开源软件
      #         "FreeBSD",
      #         "musl",
      #         "zlib",
      #         "FatFs",
      #         "Linux_Kernel",
      #         "lwip",
      #         "NuttX",
      #         "mtd-utils"
      #       ]
      #     }
      #   },
      # ]
      foreach(product_configed_component,
              product_configed_subsystem.components) { #遍历项目控件数组
        # Step 5: Check whether the component configured by product is exist.
		    # 第五步: 检查控件配置信息是否存在
        component_found = false #初始为不存在
        foreach(system_component, subsystem_info.components) {#项目控件和子系统中的控件遍历对比
          if (product_configed_component.component ==
              system_component.component) { #找到了liteos_a
            component_found = true
          }
        }
		    #如果没找到的信息,则打印项目控件查找失败日志
        assert(
            component_found,
            "Component \"${product_configed_component.component}\" not found" +
                ", please check your product configuration.")

        # Step 6: Loop OS components and check validity of product configuration.
		# 第六步: 检查子系统控件的有效性并遍历控件组,处理各个控件
        foreach(component, subsystem_info.components) {
          kernel_valid = false	#检查内核
          board_valid = false	#检查开发板

          # Step 6.1: Skip component which not configured by product.
          if (component.component == product_configed_component.component) {
            # Step 6.1.1: Loop OS components adapted kernel type.
            foreach(component_adapted_kernel, component.adapted_kernel) {
              if (component_adapted_kernel == product_cfg.kernel_type && 
                  kernel_valid == false) { #内核检测是否已适配
                kernel_valid = true
              }
            }
			# 如果内核未适配,则打印未适配日志
            assert(
                kernel_valid,
                "Invalid component configed, ${subsystem_name}:${product_configed_component.component} " + "not available for kernel: ${product_cfg.kernel_type}!")

            # Step 6.1.2: Add valid component for compiling.
			# 添加有效组件进行编译
            foreach(component_target, component.targets) {//遍历组件的编译入口
 
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值