暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

好家伙!鸿蒙系统的编译流程

鸿蒙技术社区 2021-04-26
1417

因为新接触鸿蒙系统的开发,我对鸿蒙的编译系统基本上没什么理解,都是凭着经验和百度一边学习一边总结。

本文是我在学习网上关于鸿蒙系统的编译相关资料后做的总结,经过了本人的实际操作验证,部分知识点可能还有理解不到位的地方,未来会持续更新。


01

准备工作


我的本地代码是基于最新发布的 OpenHarmony 1.1.0 LTS(2021-04-01)版本抓取的,代码根目录 OHOS1_1_0LTS:
$repo init -u https://gitee.com/openharmony/manifest.git -b refs/tags/OpenHarmony_release_v1.1.0 --no-repo-verify

$repo sync


我还有 Hi3861_Wifiiot 开发板和开发环境,如下:

  • 在 Linux 环境下的 DecEco IDE 下创建新工程“Test_Wifiiot”。

  • 在“HPM”标签下找到“@ohos/wifi_iot”,点击“Install to project”选择“Test_Wifiiot”项目,开始下载并安装组件到项目里。


02

全编译+模块编译


①OHOS1_1_0LTS 的全代码编译


在代码根目录下执行:
OHOS1_1_0LTS$ hb set
[OHOS INFO] Input code path: .
OHOS Which product do you need?  
ipcamera_hispark_pegasus@hisilicon
-> ipcamera_hispark_taurus@hisilicon
ipcamera_hispark_aries@hisilicon


这一步完成后,会在代码根目录下生成“ohos_config.json”文件,然后就可以开始编译了:
OHOS1_1_0LTS$ hb build
OHOS1_1_0LTS$ python build.py
OHOS1_1_0LTS$ python build.py -p ipcamera_hispark_taurus@hisilicon


效果都是一样的。第一次全编译了 2491 个文件,以后再操作上面的命令就不是全编译了,只编译需要编译的部分。


②Test_Wifiiot 项目的全编译


新下载的项目代码根目录下并没有“build.py”文件。
$ hb set


会生成“ohos_config.json”,但没有完成配置,hb build 会失败。


可以执行:
$ln -s ./build/lite/build.py build.py


创建 build.py 的软链接,再执行:
$python build.py wifiiot


就可以正常编译了。


Test_Wifiiot 项目还可以用 DevEco IDE 进行一键编译,操作步骤见官方的文档:
https://device.harmonyos.com/cn/docs/ide/user-guides/service_introduction-0000001050166905


③OHOS1_1_0LTS 的组件/仓库/Target 编译


一开始,关于模块编译,我在网上搜到了:《HarmonyOS单模块编译与源码导读》(Link: https://harmonyos.51cto.com/posts/3094)


这篇文章(下文简称《导读》),仔细阅读,并对比本地代码 OHOS1_1_0LTS 查看了 build\lite 目录下,并没有上文所提到的 compile.py 文件和 product 目录,这个可能是鸿蒙系统迭代更新版本给拿掉了。


查看 OHOS1_1_0LTS\build\lite 目录下的 README_zh.md,虽然看到增加了 -T 的说明:
-T [TARGET [TARGET ...]], --target [TARGET [TARGET ...]]
  Compile single target


但没做进一步使用说明,因为对鸿蒙的编译系统还没多少了解,我还是不大清楚如何使用操作。


官方文档“轻量和小型系统编译构建指导”:
https://device.harmonyos.com/cn/docs/develop/subsystems/oem_subsys_build_des-0000001060646620


可能没有更新,甚至没有对“-T”参数的描述。


不过官方文档中“组件描述位于 build/lite/components 下”这句话倒提醒了我,去里面看了一下,原来这就是鸿蒙系统所有组件描述文件的存放位置,每个组件文件内又有多个【组件和 target】的描述。


打开“applications.json”看一下,这里就有上面《导读》提到的东西:
          "component""camera_sample_app",
          "description""Camera related samples.",
          "optional""true",
          "dirs": [
            "applications/sample/camera/launcher",
            "applications/sample/camera/cameraApp",
            "applications/sample/camera/setting",
            "applications/sample/camera/gallery",
            "applications/sample/camera/media"
          ],
          "targets": [
 "//applications/sample/camera/launcher:launcher_hap",       "//applications/sample/camera/cameraApp:cameraApp_hap",
"//applications/sample/camera/setting:setting_hap",
"//applications/sample/camera/gallery:gallery_hap",
"//applications/sample/camera/media:media_sample"
          ],


赶紧操作一下:
OHOS1_1_0LTS$ hb build -T //applications/sample/camera/launcher:launcher_hap


编译 OK,不过时间还是有点长。


官方文档说“组件即为 gn 中的编译单元,可以为静态库、动态库或可执行文件。”编译构建流程图上也是写 hb build [component]:
OHOS1_1_0LTS$ hb build camera_sample_app


编译 OK,但感觉与 target 编译以及最上面 hb build 是一样的效果~~


我还在 OHOS1_1_0LTS\applications\sample\camera\README_zh.md  文档中看到对媒体子系统组件的使用说明:
    单仓的编译构建,在根目录下进行单仓的构建和编译
    # 开发板选择
    hb set  
    # 单仓构建和编译
    hb build camera_lite


再查看一下 build\lite\components\multimedia.json,原来单仓编译就是组件编译,而 Target 编译,实际上看起来又与组件编译差别不大,有些组件本身就是一个 target,有些组件分多个 target。
      "component""camera_lite",
      "description""Camera service.",
      "optional""true",
      "dirs": [
        "foundation/multimedia/camera_lite",
        "foundation/multimedia/utils/lite/hals"
      ],
      "targets": [
"//foundation/multimedia/camera_lite/frameworks:camera_lite"

      ],


小结:鸿蒙系统所有组件描述文件的存放在 build/lite/components/ 目录下,每个组件文件内又有多个组件和 target 的描述。


组件/仓库编译指令:
$hb build component_name


Target 编译指令(可一次编译多个 target,用&&连接):
$hb build -T target_name
$hb build -T target1_name&&target2_name


03

编译系统 build 目录结构


首先必须要仔细研读 build\lite\README_zh.md 文档,文档上没有的地方,我这里补充一下我的理解,做成表格更容易管理。


这个表格会在以后学习过程中根据新的理解做更新:

04

编译结果 out 目录结构


打开“os-release”文件查看:

VERSION="OpenHarmony 1.0"
RELEASE_TIME="2021-04-18 07:38:53"


分别是鸿蒙系统的版本号,和当次编译(内核?系统?)的时间。


05

构建系统 Gn+Ninja


在学习鸿蒙之前,我对 Gn/Ninja 并不了解,只是知道有这么个东西,因为用不到所以也没有去学习的动力。


这几天因为想深入了解一下鸿蒙的编译系统,就通过网络资源简单学习了一下,也只是知道了个大概。


首先最重要的“gn help <command>”和官方文档“The Ninja build system”,随用随查。


PS:下文中对 gn 定义的关键字不再解释,请自行随时 gn help。


为了快速理解 Gn+Ninja 是如何工作的,我自己做了一个 GnHelloWorld 工程来做测试和验证,源代码放在 gitee 上:
https://gitee.com/liangkzgitee/GnProjs.git


压缩包也添加在本文附件里,请大家按 readme.txt 文档操作,逐步进行跟踪和分析。


readme.txt 文档内容如下:


这是一个最简单的 Gn+Ninja 构建系统的例子工程,通过这个简单的工程和下面的操作,学习 Gn+Ninja 构建系统是如何构建编译我们的源代码的。


原始工程目录如下:
GnHelloWorld/                  #工程根目录
├──build/                    #编译构建主目录
│     └──config/               #编译相关的配置项
│             ├──toolchans/         #编译工具链相关
│             │     └──BUILD.gn      #编译选项、链接选项等等
│             └──BUILDCONFIG.gn    #指定默认编译工具链
├──src/                      #源代码目录
│     ├──BUILD.gn              #(*)
│     └──hello.c                #(*)源代码
├──.gn                       #Gn构建系统入口
├──BUILD.gn                  #
└──Readme.txt                #(*)本文


Step1:在"GnHelloWorld"目录下执行:"gn gen out"命令后,会生成一个"out"目录。
GnHelloWorld/
├──......[略]
├──out/
│  ├──obj/
│  │      └──src/
│  │             └──hello.ninja    #编译hello.c的ninja脚本
│  ├──args.gn              #构建参数
│  ├──build.ninja          
│  ├──build.ninja.d
│  └──toolchain.ninja
├──......[略]


Step2:建议操作这步之前,先备份 out 目录,如: "cp -r out out_bak",在"GnHelloWorld"目录下执行: "ninja -C out",会在 out 目录下生成。
GnHelloWorld/
├──......[略]
├──out_bak/               #Step1 的 out 目录的副本
│     ├──obj/
│     │     └──src/
│     │             └──hello.ninja
│     ├──args.gn
│     ├──build.ninja
│     ├──build.ninja.d
│     └──toolchain.ninja
├──out/                     #out目录有更新,见#
│    ├──obj/
│    │     ├──src
│    │     │     ├──hello.ninja
│    │     │     └──hello.o        #编译链工具根据规则生成的 .o
│    │     └──all.stamp          #
│    ├──.ninja_deps            #
│    ├──.ninja_log             #
│    ├──args.gn
│    ├──build.ninja
│    ├──build.ninja.d
│    ├──hello                 #编译后生成的可执行文件
│    └──toolchain.ninja
├──......[略]


Step3:在"GnHelloWorld"目录下执行: 执行"./out/hello",输出"Hello Gn World!"


Step1 做了如下工作:

  • 执行 gn gen 时带的参数被记录下来,生成 out/args.gn,本例不带 args 参数。

  • 找到 “.gn”文件并将其所在的目录设为“souce root”,解析该文件以获取 buildconfig。

  • 执行 buildconfig 所指向的文件 BUILDCONFIG.gn,设置一个默认的编译工具链,生成 out/toolchain.ninja。

  • 加载“souce root”目录下的“BUILD.gn”文件,根据其内容加载它依赖的其他目录下的 BUILD.gn 文件,生成 out/build.ninja.d。

  • 根据 out/build.ninja.d 中各个 BUILD.gn 的内容,递归解决各自的依赖关系,解决掉依赖关系之后,就在 out/obj/ 对应目录下,生成“.ninja”,如例子中的“out/obj/src/hello.ninja”。

  • 解决掉所有的依赖关系后,在 out/ 目录下生成一个“build.ninja”。


Step2 做了如下工作:根据上面的 .ninja 文件所定义的规则和依赖关系,依次编译出各自的中间文件,最终生成可执行文件“out/hello”。


理解了上面的东西之后,你就可以开始进一步学习更复杂的东西了,可以参考 Gn 的官方文档,或者网络上的其它资源,自己动手做验证。


06

编译流程分析


我们 repo/sync 完整个鸿蒙代码后,要编译系统,一般做以下三步:

  • 首次编译,需要首先 hb set。

  • 首次编译,hb build 会启动全编译。

  • 修改某个文件,比如 init_lite 的 main 文件 main 函数加行 log,再次编译 hb build 或者 hb build init_lite。


这三步操作,鸿蒙的编译系统都做了哪些具体工作?带着这个疑问,我希望能够抽丝剥茧,一步一步来确认,可能中间会有暂不理解的,先跳过,未来再做进一步完善。


可惜目前我对 python 的了解也不多,无法对相关脚本做非常详细的分析,对 Gn 构建系统的理解也还不够深入,无法给出理想的分析结果,所以只能先把我现在知道的写下来,以后学习过程中逐步补充完善。


①设置环境 hb set


查看 build/lite/README_zh.md,设置 OpenHarmony 源码目录和要编译的产品,在代码根目录生成“ohos_config.json”文件。


具体 hb set 命令怎么调用 build/lite/hb/ 目录下的相关脚本,再结合系统环境变量 $PATH 等相关必要信息来生成这个 .json 文件的,懂 python 的可以进去看一下。


打开“ohos_config.json”文件查看:
{
  "root_path""/home/lkz/Work/OHOS1_1_0LTS",
  "board""hispark_taurus",
  "kernel""liteos_a",
  "product""ipcamera_hispark_taurus",
  "product_path""/home/lkz/Work/OHOS1_1_0LTS/vendor/hisilicon/hispark_taurus",
  "device_path""/home/lkz/Work/OHOS1_1_0LTS/device/hisilicon/hispark_taurus/sdk_liteos"
}


这些将作为非常重要的参数交给下一步编译使用。


②全编译 hb build


查看/build/lite/README_zh.md。编译产品、开发板或者组件。解决方案编译实现如下:

  • 读取开发板配置:主要包括开发板使用的编译工具链、编译链接命令和选项等。

  • 调用 gn:调用 gn gen 命令,读取产品配置(主要包括开发板、内核、选择的组件等)生成解决方案 out 目录和 ninja 文件。

  • 调用 ninja:调用 ninja -C out/company/product 启动编译。

  • 系统镜像打包:将组件编译产物打包,制作文件系统镜像。


说的很“框架”,下面我们一步一步来看一下。


A.检测/读取/配置所有的必要参数


包括但不限于以下列出来的几个文件:

  • ohos_config.json。

  • Build\lite\ohos_var.gni:定义使用于所有组件的全局变量。

  • device\hisilicon\hispark_pegasus\sdk_liteos\config.gni:这是编译 LiteOS_A 内核所需要用到的配置。

  • vendor\hisilicon\hispark_taurus\config.json:这是 hisilicon 提供的产品全量配置表:子系统、组件列表等等。

  • 通过 hb build 传进去的参数,比如 -n 表示编译 NDK,则会将 ohos_build_ndk 变量由默认的 FALSE 改为 TRUE。


B.调用 gn gen 命令生成 out 目录和 ninja 文件


很复杂,也很简单。复杂是中间涉及到太多的 Python 和 Gn 语法,我暂时无法完整理解。


简单是因为我做过了上面的 GnHelloWorld 工程来做测试和分析,知道了框架。更详细的分析过程,请看《过程分析》这篇文章。


下面是我对 build/lite/BUILD.gn 这个文件中的 group("ohos") 的分解,最终得到"ohos"的完整的依赖关系:

C.调用 ninja 启动编译

这里就开始根据上一步生成的 .ninja 文件里的规则调用编译工具链来编译各目录下的源文件了,生成 .o/.a/.so/ 可执行文件 等等。


其中 .so 文件会先在 out\hispark_taurus\ipcamera_hispark_taurus\ 目录下生成,等编译完之后会转移到 out\hispark_taurus\ipcamera_hispark_taurus\libs\usr\ 目录下,还生成了 .ninja_log 和 .ninja_deps 文件。

D. 打包制作系统镜像


暂未清晰理解,略。

③增量编译 hb build [component]


暂未清晰理解,略。


附录一:hb build -T target 的验证


多图,点击“阅读原文”见附件文档,此处略。

送福利啦

关注鸿蒙技术社区,回复【鸿蒙】《鸿蒙应用开发实战书籍(数量有限,先到先得),还可以免费下载鸿蒙入门资料

👇点击立刻关注👇

专注开源技术,共建鸿蒙生态


“阅读原文”了解更多

文章转载自鸿蒙技术社区,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论