[FFH]openharmony南向研究-编译架构和编译框架(2) 原创 精华

发布于 2022-5-9 12:38
浏览
2收藏

【FFH】系统移植和驱动开发(2) -openharmony的编译架构和编译框架

[FFH]openharmony南向研究-编译架构和编译框架(2)-开源基础软件社区

综述

openharmony的编译构架系统基于系统的设计初衷和一些基本特性,包括微内核和可拆分性,针对不同的子系统使用着不同的构建方式和构建单元,目前在代码中可以看到主要分为以前使用的make方式来构建gcc,以及目前正在迭代和加入的gn和ninja编译方案。对于编译这一块我不是非常了解,但是我认为这部分作为这个系列第二章的原因是必须要了解openharmony的各个子系统的通用构建方式和原理在进行新的子系统开发时会减少很多阻碍更加流畅开发

参考文献

  1. 浅析鸿蒙中的 Gn 与 Ninja(一)-开源基础软件社区-51CTO.COM唐老师写的这一篇虽然是系列中唯一一篇,但是给了人很大启发

GN与Ninja

GN与Ninja简介

很多人使用过一些常用的构建工具,如我们熟知的c++相关的makefile,cmake,java中相关的,ant,maven,gradle等等他们的作用都是一种将源码生成可执行应用程序的自动化过程工具,构建包括了编译链接和代码打包为可执行文件。

其实openharmony的整套系统也是需要进行构建,而且是被划分为一个一个的子系统进行编译的,而更具体到代码则是通过gn脚本对应模块来实现的。

gn和ninja的历史来源于Google Chrome 项目,为了提高大体量系统级代码的编译构建速度才设计使用的。

ninja是直接接触到工程文件的部分,gn是用来描述并生成ninja文件的方式。

  • 子系统 子系统是一个逻辑概念,它由一个或多个具体的组件组成。OpenHarmony整体遵从分层设计,从下向上依次为:内核层、系统服务层、框架层和应用层。系统功能按照“系统 > 子系统 > 组件”逐级展开,在多设备部署场景下,支持根据实际需求裁剪某些非必要的子系统或组件。
  • 华为给出了命令行工具hb(openharmony Build),这是结合了gn和ninja构建工具的构建系统,但是目前对于ohos.build文件的理解和书写还是停留在注册一个模组的程度,其他的一些配置和参数还没有查到具体的写法

GN与ninja的关系以及用法和语法深入讲解

ninja是直接接触到工程文件的部分,gn是用来描述并生成ninja文件的方式。在其他的项目工程中如果gcc,cmake用多了厌烦了,直接使用gn和ninja也是个有趣的尝试。

Gn和ninja与openharmony的结合

Gn用于生成ninja且可以在全局目录下递归查找子Gn文件进行联合构建,最终通过ohos的hb编译脚本ohos.build进行总体解释,再将生成的动态链接库或者动态依赖库绑定在全局(通过harmonyos\build\subsystem_config.json添加模组进行绑定)。

真实案例 在openharmony中添加一个子模组的流程

必要参考文献

docs/标准系统如何添加一个模块.md · OpenHarmony/build - Gitee.com

实现流程

通过Hi3516Dv300实现一个napi接口子系统的过程,代码测试正常,跟着流程做即可,解析会在后面几篇文章中给出

在harmonyos目录下新建demo目录

建立子目录

|-- BUILD.gn

|-- demo.cpp

|-- ohos.build

填写每个文件,复制粘贴即可

  1. BUILD.gn
 import("//build/ohos.gni")
    ohos_shared_library("demo") {
      sources = [
        "demo",
      ]
      deps = [ "//foundation/ace/napi:ace_napi" ]
      relative_install_dir = "module"
      subsystem_name = "demo"
      part_name = "demo_part"
    }   

由此编译完成后会产生一个动态链接库,位置在out/system/lib/module/libdemo.z.so

  1. ohos.build
{
  "subsystem": "demo",
  "parts": {
    "demo_part": {
      "module_list": [
        "//demo:demo"
      ],
    }
  }
}
  1. demo.cpp
#include <assert.h>

#include "napi/native_api.h"
#include "napi/native_node_api.h"

static napi_value Method(napi_env env, napi_callback_info info) {
  napi_status status;
  napi_value str;
  status = napi_create_string_utf8(env, "NAPI!", 256, &str);
  assert(status == napi_ok);
  return world;
}

static napi_value Init(napi_env env, napi_value exports) {
  napi_status status;
    napi_property_descriptor desc[] = {
        DECLARE_NAPI_FUNCTION("demo", Method),
    };
  status = napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
  assert(status == napi_ok);
  return exports;
}

NAPI_MODULE(demo, Init)

接下来把子系统进行绑定

到达openharmony/build/subsystem_config.json

[FFH]openharmony南向研究-编译架构和编译框架(2)-开源基础软件社区

 "demo": {
      "project": "hmf/demo",
      "path": "demo",
      "name": "demo",
      "dir": ""
  }

紧接着到达openharmony/productdefine/common/products/Hi3516Dv300添加到产品输出目录中作为子系统

[FFH]openharmony南向研究-编译架构和编译框架(2)-开源基础软件社区

最终在out/ohos-arm-release/packages/phone/images/ 中可以找到刚刚提到的动态链接库,在通过hdc工具烧录到系统中,或者直接烧录整个镜像,在北向代码中即可进行测试,或者用logconsloe工具可以测试结果

(因为时间比较紧张没时间写新的去测试,这是项目中测过的改了一下显示的东西,后面会更新V2的时候更换新的样例)

gn和ninja的基础语法和命令,参考文档,以及一个小训练

  1. 交叉编译 :参考 cross_compile.md

  2. gn官网:gn - Git at Google (googlesource.com)

  3. 语法:v60.04 鸿蒙内核源码分析(gn应用) | 如何构建鸿蒙系统 | 百篇博客分析OpenHarmony源码 - 知乎 (zhihu.com)

  4. 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) {//遍历组件的编译入口
                  deps += [ component_target ] #添加到编译列表中
                }
              }
            }
          }
        }
    
        # Step 7: Add device and product target by default.
        # 第七步: 添加设备和项目的编译单元
        # "product_path": "/home/openharmony/vendor/hisilicon/hispark_aries",
        # "device_path": "/home/openharmony/device/hisilicon/hispark_aries/sdk_liteos",
          deps += [
          "${device_path}/../", #添加 //device/hisilicon/hispark_aries 进入编译项
          "${product_path}"        #添加 //vendor/hisilicon/hispark_aries 进入编译项
        ]
      } else {#编译指定的组件,例如 hb build -T targetA&&targetB
        deps += string_split(ohos_build_target, "&&")
      }
    }
    
    1. 必须要提及的点和要指出的问题

      在总体的设计中注册一个子模块的步骤讲的挺清楚了,那么我们对于刚刚的一些最常用的gn语法分析和讲解一下

      mport("//drivers/adapter/uhdf2/uhdf.gni")
       import("//build/ohos.gni")
       
          ohos_shared_library("demo") {
            # 指定编译源文件
              include_dirs = [  # 模块依赖头文件目录
                "include/" 
              ]
            sources = [
              "demo.cpp",
            ]
            # 指定编译依赖,如果依赖第三方库,需要在此添加
            deps = [ "//foundation/ace/napi:ace_napi" ]
      
        
            # 指定库生成的路径
            relative_install_dir = "module"
            # 子系统及其组件,后面会引用
            subsystem_name = "demo"
            part_name = "demo_part"
          }   
      
          ohos_executable("hello") {
              include_dirs = [  # 模块依赖头文件目录
                  "//base/compileruntime/js_util_module/util",
          "//ark/js_runtime",
          "//foundation/ace/napi",
          "//foundation/ace/napi/interfaces/kits",
          "//foundation/ace/napi/native_engine",
          "//foundation/ace/napi/native_engine/impl/ark",
          "//third_party/icu/icu4c/source/common",
          "//third_party/googletest/include",
          "//third_party/node/src",
          "//utils/native/base/include",
                "include/" 
              ]
           sources = [
            "hello.c"
           ]
            deps = [ "//foundation/ace/napi:ace_napi",
            "//ark/js_runtime:libark_jsruntime",
          "//base/compileruntime/js_util_module/util:util_packages",
          "//foundation/ace/napi/:ace_napi",
          "//foundation/ace/napi/:ace_napi_ark",
          "//third_party/icu/icu4c:static_icuuc",
          "//third_party/libuv:uv_static",
          "//utils/native/base:utils",
          "//utils/native/base:utilsecurec", ]
            # 子系统及其组件,后面会引用
      
       
           subsystem_name = "applications"
           part_name = "prebuilt_hap"
        }
      
      

这部分是我在项目中使用,在一个子系统(subsystem)中注册两个模组,一个是ohos_executable–hello,另外一个是ohos_shared_librarydemo

分别有一些子选项需要填写

include_dirs --存放头文件

sources — 存放源文件

deps—指定依赖

其他的部分都有注释,有人可能会问这些的详细参数表在哪里去找,答案是目前只能在各个子系统的gn里去翻,我这边还没找到更有效的参考文献,如果哪位大佬有,欢迎评论区留言讨论。

小训练

  1. 在读完上面的文献和本文的一些知识后可以尝试做两件事,先写一个ninja脚本去编译一套c语言代码。

    1. 尝试在跑通样例的基础上,再制作一个可以往命令行打印的子系统
    2. 答案和参考后续会补文章,暂时工作量比较大时间不太够。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2022-5-9 13:04:24修改
3
收藏 2
回复
举报
回复
添加资源
添加资源将有机会获得更多曝光,你也可以直接关联已上传资源 去关联
    相关推荐