【FFH】OpenHarmony构建编译实战 原创 精华

未尽的清平乐
发布于 2024-7-25 11:12
浏览
4收藏

OpenHarmony构建系统

个人简介:深圳技术大学超鸿数智联合实验室成员,深圳技术大学第一届开源鸿蒙菁英班学生,学习研究鸿蒙南向开发知识。
博客主页:https://ost.51cto.com/person/posts/16275438

前言

本人是一名大一学生,有幸被选拔进了深圳技术大学第一届开源鸿蒙菁英班,并在暑期培训进行线上分享,故将讲解的内容也制作成帖子发上来作为学习笔记。在准备分享的过程中,我基于学长们的先前成果,结合开源鸿蒙源码的最新版本进行了相应的调整和优化,帮助大家更好地理解和应用开源鸿蒙技术。

环境

  • OpenHarmony-4.0 源码
  • 九联 unionpi_whale 开发板

一、OpenHarmony源码的模块划分

1、OpenHarmony内核分类

系统分为三种不同体量的系统,本文主要讲解基于Linux的标准系统构建。
【FFH】OpenHarmony构建编译实战-鸿蒙开发者社区

2、系统代码版本

Openharmony有主干代码与发行版代码两种,代码的获取请参考官方文档
【FFH】OpenHarmony构建编译实战-鸿蒙开发者社区
【FFH】OpenHarmony构建编译实战-鸿蒙开发者社区

3、OpenHarmony源码体系

OpenHarmony的源码架构基于模块化设计,为了方便系统的功能的增加和裁剪,设计了基于GN构建的模块系统。整个模块可从大到小划分为产品(product)、领域/子系统集(domain)、子系统(sub_system)、部件(component)、模块/组件(module)、特性(feature)几个部分,这种模块化的树状编译框架,非常方便根据目标产品硬件资源的大小进行灵活的裁剪,从而实现**“统一0S,弹性部署”**的目标。

4、各部分的关系

​一个产品(product)可以包含1-n个子系统(subsystem),一个子系统可以包含1-~n个部件(component),一个部件可以包含1-n个模块(modue),不同产品的中的相同部件可以编译不同的特性(feature),**子系统集(domain)**在源代码一级根目录有体现。

​不同产品相同模块可以通过特性feature进行差异化定义。

【FFH】OpenHarmony构建编译实战-鸿蒙开发者社区

5、南向开发的概念

​开源鸿蒙系统的南向开发,主要是针对设备或者终端的软件开发,例如各种智能家居设备、穿戴设备等。由于这些设备种类繁多,硬件配置和操作系统都各不相同,因此需要进行适配和优化,以确保设备能够顺利地运行开源鸿蒙系统。


二、OpenHarmony源码的产品、子系统、部件、组件配置详解

1、产品

​产品解决方案为基于开发板的完整产品,主要包含产品对OS的适配、部件拼装配置、启动配置和文件系统配置等。产品解决方案的源码路径规则为:vendor/{产品解决方案厂商}/{产品名称}

vendor/company/product/config.json config.json为编译构建的主入口,包含了开发板、OS部件和内核等配置信息。

2、领域

​OpenHarmony技术架构中有四大子系统集:“系统基本能力子系统集”、“基础软件服务子系统集"、“增强软件服务子系统集"、“硬件服务子系统集”。四大子系统不会直接出现在编译选项或者参数中,而是有对应的一级源代码文件夹:“系统基本能力子系统集”对应源码foundation文件夹;“基础软件服务子系统集”和“硬件服务子系统集”对应源码base文件夹;“增强软件服务子系统集"对应源码domains文件夹。

【FFH】OpenHarmony构建编译实战-鸿蒙开发者社区

  • vendor仓:存放厂商驱动代码,配置文件
  • device仓:存放硬件设备代码,板级配置和驱动代码

3、子系统

子系统是一个逻辑概念,它具体由对应的组件构成。配置规则如下:

{
  "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"
  },
  ...
}

​ 子系统的配置规则主要是在build/subsystem_config.json中指定子系统的路径和子系统名称。

4、部件(组件)

​对子系统的进一步拆分,可复用的软件单元,它包含源码、配置文件、资源文件和编译脚本;能独立构建,以二进制方式集成,具备独立验证能力的二进制单元。

部件的bundle.json放在部件源码的根目录下。配置规则如下:

{
    "name": "@ohos/sensor_lite",	 # HPM部件英文名称,格式"@组织/部件名称"
    "description": "Sensor services",	# 部件功能一句话描述	
    "version": "3.1",		# 版本号,版本号与OpenHarmony版本号一致
    "license": "MIT",		# 部件License
    "publishAs": "code-segment",		 # HPM包的发布方式
    "segment": {										
        "destPath": ""			
    },		 # 发布类型为code-segment时为必填项,定义发布类型code-segment的代码还原路径(源码路径)			
    "dirs": {"base/sensors/sensor_lite"},	 # HPM包的目录结构,字段必填内容可以留空
    "scripts": {},			            # HPM包定义需要执行的脚本,字段必填,值非必填
    "licensePath": "COPYING",			
    "readmePath": {
        "en": "README.rst"
    },
    "component": {			               # 部件属性
        "name": "sensor_lite",			   # 部件名称		
        "subsystem": "",		           # 部件所属子系统
        "syscap": [],				      # 部件为应用提供的系统能力
        "features": [],                     # 部件对外的可配置特性列表,一般与build中的sub_component对应,可供产品配置
        "adapted_system_type": [],		   # 轻量(mini)小型(small)和标准(standard),可以是多个
        "rom": "92KB",                     # 部件ROM值
        "ram": "~200KB",                   # 部件RAM估值       
        "deps": {                      
        "components": [                    # 部件依赖的其他部件
          "samgr_lite",
          "ipc_lite"
        ],
        "third_party": [                   # 部件依赖的三方开源软件
          "bounds_checking_function"
        ],
        "hisysevent_config": []           # 部件HiSysEvent打点配置文件编译入口
      }         
        "build": {				         # 编译相关配置
            "sub_component": [
                ""//base/sensors/sensor_lite/services:sensor_service"",  # 部件编译入口
            ],			                                                # 部件编译入口,模块在此处配置
            "inner_kits": [],						                   # 部件间接口
            "test": []							                       # 部件测试用例编译入口
        }
    }
 }

5、模块

编译子系统通过模块、部件和产品三层配置来实现编译和打包。模块就是编译子系统的一个目标,包括(动态库、静态库、配置文件、预编译模块等)。模块要定义属于哪个部件,一个模块只能归属于一个部件。这里列出了初学者常用的几个模版及其配置规则:

# C/C++模板
ohos_shared_library  # 动态库gn脚本
ohos_static_library  # 静态库gn脚本
ohos_executable      # 可执行文件gn脚本

# 配置文件
ohos_prebuilt_etc    # etc模块gn脚本

​配置中只有sourcespart_name是必选,其他都是可选的。

  • 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_executable示例

    import("//build/ohos.gni")
    ohos_executable("helloworld") {
      configs = []                       # 配置  
      part_name = ""                     # 部件名称 
      subsystem_name = ""                # 子系统名称
      deps = []                          # 部件内模块依赖
    
      external_deps = [                  # 跨部件模块依赖定义,
      "part_name:module_name",           # 定义格式为 "部件名:模块名称"
      ]                                  # 这里依赖的模块必须是依赖的部件声明在inner_kits中的模块。
      ohos_test = []
      test_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 = []
      static_link = []
      install_images = []
      module_install_dir = ""            # 模块安装路径,从system/,vendor/后开始指定
      relative_install_dir = ""
      symlink_target_name = []
      output_dir = [directory]           # 存放输出文件的目录
      install_enable = [boolean]
      version_script = []
      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 = []
    }
    

6、特性

​特性是部件用于体现不同产品之间的差异,通常不同特性可以定义不同编译宏或者代码。


三、子系统样例源码实战

1、单模块示例

本示例目标是编写一个helloworld程序。在OpenHarmony源码目录下创建子系统文件夹Mysample,其下创建部件文件夹Myhello,下设includesrc文件夹分别用于存放头文件和源码。创建如下文件:
【FFH】OpenHarmony构建编译实战-鸿蒙开发者社区
(忽略图片中我自己的其他部件)
Mysample/Myhello/include/helloworld.h

#ifndef HELLOWORLD_H //条件编译指令
#define HELLOWORLD_H
#ifdef __cplusplus //C++ 编译器
#if __cplusplus
extern "C" { //表示下面的代码将以 C 语言的方式进行编译
#endif
#endif

void HelloPrint();

#ifdef __cplusplus
#if __cplusplus
}
#endif
#endif
#endif // HELLOWORLD_H

Mysample/Myhello/src/helloworld.c

#include <stdio.h>
#include "helloworld.h"
// #include "../include/helloworld.h"

int main(int argc, char **argv)
{
    HelloPrint();
    return 0;
}

void HelloPrint()
{
    printf("\n\n");
    printf("\n\t\tHello World!\n");
    printf("\n\n");
}

在部件文件夹Myhello下创建配置文件:
Mysample/Myhello/BUILD.gn

import("//build/ohos.gni")  # 导入编译模板
ohos_executable("helloworld") { # 可执行模块,target组件、模块
  sources = [       # 模块源码
    "src/helloworld.c"
  ]
  include_dirs = [  # 模块依赖头文件目录
    "include" 
  ]
  cflags = []
  cflags_c = []
  cflags_cc = []
  ldflags = []
  configs = []
  deps =[]    # 部件内部依赖
  part_name = "Myhello"    # 所属部件名称,必选
  install_enable = true  # 是否默认安装(缺省默认不安装),可选
}

Mysample/Myhello/bundle.json

{
    "name": "@ohos/Myhello", 
    "description": "Hello world example.",
    "version": "3.1",
    "license": "Apache License 2.0",
    "publishAs": "code-segment",
    "segment": {
        "destPath": "Mysample/Myhello"
    },
    "dirs": {},
    "scripts": {},
    "component": {
        "name": "Myhello",
        "subsystem": "Mysample",
        "syscap": [],
        "features": [],
        "adapted_system_type": [ "mini", "small", "standard" ],
        "rom": "10KB",
        "ram": "10KB",
        "deps": {
            "components": [],
            "third_party": []
        },
        "build": {
            "sub_component": [
                "//Mysample/Myhello:helloworld"
            ],
            "inner_kits": [],
            "test": []
        }
    }
}

修改模块配置文件:
vendor/unionman/unionpi_whale/config.json

	{
      "subsystem": "Mysample",
      "components": [
        {
          "component": "Myhello",
          "features": []
        }
      ]
    }

修改子系统配置文件:
build/subsystem_config.json

"Mysample": {
    "path": "Mysample",
    "name": "Mysample"
  },

2、多模块示例

本示例目标是带大家使用几个编译模板。在OpenHarmony源码目录下创建子系统文件夹Mysample,其下创建部件文件夹Mycomponent,下设module1module2和’module3`文件夹分别用于3个模块。创建如下文件:
【FFH】OpenHarmony构建编译实战-鸿蒙开发者社区

  • 需要注意的是:每个模块有自己的配置文件BUILD.gn,组件还需要一个group模板BUILD.gn

Mysample/Mycomponent/module1/include/hello1.h

#ifndef HELLO1_H //条件编译指令
#define HELLO1_H
#ifdef __cplusplus //C++ 编译器
#if __cplusplus
extern "C" { //表示下面的代码将以 C 语言的方式进行编译
#endif
#endif

void HelloPrint1();

#ifdef __cplusplus
#if __cplusplus
}
#endif
#endif
#endif // HELLO1_H

Mysample/Mycomponent/module1/src/hello1.cpp

#include <iostream>
#include "hello1.h"

void HelloPrint1()
{
    printf("\n\n");
    printf("\n\t\tHello1!\n");
    printf("\n\n");
}

Mysample/Mycomponent/module1/BUILD.gn

import("//build/ohos.gni")

config("hello_lib_config") {
 include_dirs = [ "include" ]
}

ohos_shared_library("hello_lib") {
  sources = [
    "include/hello1.h",
    "src/hello1.cpp",
  ]
  public_configs = [ ":hello_lib_config" ]
  part_name = "Mycomponent"
}

Mysample/Mycomponent/module2/include/hello2.h

#ifndef HELLO2_H //条件编译指令
#define HELLO2_H
#ifdef __cplusplus //C++ 编译器
#if __cplusplus
extern "C" { //表示下面的代码将以 C 语言的方式进行编译
#endif
#endif

void HelloPrint2();

#ifdef __cplusplus
#if __cplusplus
}
#endif
#endif
#endif // HELLO2_H

Mysample/Mycomponent/module2/src/hello2.cpp

#include <iostream>
#include "hello1.h"
#include "hello2.h"

int main(int argc, char **argv)
{
    HelloPrint2();
    return 0;
}

void HelloPrint2()
{
    HelloPrint1();
    printf("\n\n");
    printf("\n\t\tHello2!\n");
    printf("\n\n");
}

Mysample/Mycomponent/module2/BUILD.gn

import("//build/ohos.gni")

ohos_executable("hello_bin") {
  sources = [
    "src/hello2.cpp"
  ]
  include_dirs = [ "include" ]
  deps = [                                # 依赖部件内模块
    "../module1:hello_lib"
  ]
  install_enable = true                   # 可执行程序缺省不安装,需要安装时需要指定
  part_name = "Mycomponent"
}

Mysample/Mycomponent/module3/src/config.conf

var_a = 10
var_b = 20
var_c = 30

Mysample/Mycomponent/module3/BUILD.gn

import("//build/ohos.gni")

ohos_prebuilt_etc("hello_etc") {
  source = "src/config.conf"
  relative_install_dir = "init"    #可选,模块安装相对路径,相对于默认安装路径;默认在/system/etc目录
  part_name = "Mycomponent"
}

在部件文件夹Mycomponent下创建配置文件:
Mysample/Mycomponent/BUILD.gn

import("//build/ohos.gni")

group("mycomponent"){
    deps = [
        "module1:hello_lib",
        "module2:hello_bin",
        "module3:hello_etc"
    ]
}

Mysample/Mycomponent/bundle.json

{
    "name": "@ohos/Mycomponent", 
    "description": "Hello world example.",
    "version": "3.1",
    "license": "Apache License 2.0",
    "publishAs": "code-segment",
    "segment": {
        "destPath": "Mysample/Mycomponent"
    },
    "dirs": {},
    "scripts": {},
    "component": {
        "name": "Mycomponent",
        "subsystem": "Mysample",
        "syscap": [],
        "features": [],
        "adapted_system_type": [ "mini", "small", "standard" ],
        "rom": "10KB",
        "ram": "10KB",
        "deps": {
            "components": [],
            "third_party": []
        },
        "build": {
            "sub_component": [
                "//Mysample/Mycomponent:mycomponent"
            ],
            "inner_kits": [],
            "test": []
        }
    }
}
修改模块配置文件:

vendor/unionman/unionpi_whale/config.json

	{
      "subsystem": "Mysample",
      "components": [
        {
          "component": "Myhello",
          "features": []
        },
        {
          "component": "Mycomponent",
          "features": []
        }
      ]
    },

3、编译

  • 命令行方式

    ./build.sh --product-name {product_name} #全量编译
    ./build.sh --product-name {product_name}  --build-target {target_name} #单独编译部件
    ./build.sh --product-name {product_name}  --build-target {target_name} --fast-rebuild #快速重建
    
  • hb方式

    hb set #设置编译参数
    hb build #全量编译
    hb build -T {target_name} #单独编译部件
    hb build -T {target_name} --fast-rebuild #快速重建
    
  • 我们这里使用hb方式来进行编译。在终端输入命令hb set,选择standardunionpi_whale,在终端输入命令hb build -T Myhello。耗时1:26,编译成功日志如下:
    【FFH】OpenHarmony构建编译实战-鸿蒙开发者社区

  • 由于编译Myhello部件的已经检查了一遍编译关系,而我们又没有改动编译构建文件,故可以使用快速重建命令hb build -T Mycomponent --fast-rebuild编译Mycomponent部件。编译成功日志如下:
    【FFH】OpenHarmony构建编译实战-鸿蒙开发者社区

  • 可见所用时间大大减少。

  • 编译产物在out/board/product目录下。
    【FFH】OpenHarmony构建编译实战-鸿蒙开发者社区

4、烧录

  • 全量烧录: 适合更新版本或者代码大变动
    打包镜像->RKDevTool烧录。

  • HDC工具:适合代码更新时单独发送所需文件(以helloworld为例)

    • 找到可执行文件helloworld,并将其放置到电脑hdc.exe同级目录下。
    • 连接设备:将开发板上电,并连接电脑。
      【FFH】OpenHarmony构建编译实战-鸿蒙开发者社区
    • hdc文件夹下进入终端,输入hdc list targets检查是否连接好,检测到设备后输-入hdc smode授予进程root权限,再输入hdc shell mount -o rw,remount /挂载分区,并且赋予可写权限。
    • 输入hdc shell进入开发板终端,mkdir sample创建文件夹,exit退出终端。
    • hdc file send ./helloworld /sample/传输文件。(将当前目录下的hello文件传输到开发板的sample目录下)
    • hdc shell再次进入开发板终端,cd sample进入文件夹,chmod 777 *给程序赋予可执行权限。

5、烧录测试并执行

  • 烧录:
    【FFH】OpenHarmony构建编译实战-鸿蒙开发者社区
  • whale开发板烧录口为蓝色USB口上层口,使用USBtoUSB线烧录。
  • 需要注意:可执行文件可以发送到任意目录,而动态库文件libhello_lib.z.so需要发送到/system/lib/目录下。
  • 使用命令./可执行程序名执行程序。在终端查看输出结果。
    【FFH】OpenHarmony构建编译实战-鸿蒙开发者社区
  • 测试成功

+++ 总结
在本人看来,入门开源鸿蒙南向设备学习,有很大可能会被这么多的配置选项和新概念劝退,所以在暑期培训我选择分享这部分的内容,希望对大家有帮助。And 本人第一次发帖,有不足之处还请不吝赐教。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
5
收藏 4
回复
举报
2条回复
按时间正序
/
按时间倒序
红叶亦知秋
红叶亦知秋
确实第一次搭建是最困难的,感谢分享!
2
回复
2024-7-25 14:12:15
0aaron
0aaron

了解下Linux标准

回复
2024-8-14 14:40:16
回复
    相关推荐