项目开始毫无疑问一片茫然不过好在这意味着前途坦荡。相比于直接使用 AI 帮我基本完成这个工作我想按部就班地“浪费”一些时间在锻炼我这颗木头人脑上。成为一名开源大师。所以这次的技术博客我更愿意叫做学习笔记。编译构建到手这么巨大的代码头发先掉了一半我低头一看掉落的头发赫然摆成五个大字母——“BUILD”。当我弄明白整个编译构建过程不就了解源代码的整体结构层次了吗我去不早说OpenHarmony 是一个基于 Gn 和 Ninja 的编译构建框架。等等什么是 Gn什么又是 ninja那就学习一下GN 和 Ninja挺好奇 Ninja 怎么读查了查英文单词读作“嫩这”。Ninja 是由 Google 开发的底层构建系统目的是代替 make这就显而易见了这就是另一种“make”。既然用来代替 make一定有一些优势在吧确实速度快轻量级以及更简单的语法。等等语法没错GN 此刻登上舞台Generate Ninja 的缩写见过另一种说法叫 Google 的 Next Generation 构建系统看起来不打靠谱的样子这是用于构建.ninja文件的一种元构建系统简单的脚本语言类似与 Python。在 OpenHarmony 源码中根据产品配置编译生成对应的镜像包。其中编译构建流程为使用 GN 配置构建目标。GN 运行后会生成.ninja文件。通过运行.ninja来执行编译任务。既然是脚本语言那还看什么语法了直接开始实战找到根目录下的 Gn 文件import(//build/core/gn/ohos_exec_script_allowlist.gni) # The location of the build configuration file. buildconfig //build/config/BUILDCONFIG.gn # The source root location. root //build/core/gn # The executable used to execute scripts in action and exec_script. script_executable /usr/bin/env exec_script_whitelist ohos_exec_script_config.exec_script_allowlist # Enable OpenHarmony components for gn. ohos_components_support true对新手很友好啊如此精炼的代码仅仅指定了buildconfig暂且按字面意思理解做构建配置和root这个则更简单如此庞大的代码总要有个“总管”吧后两行注释贴心告诉我们是脚本的执行规则暂且作为黑盒。我们接下来“连根拔起”。BUILDCONFIG.gn来到build/目录下我们找到了BUILDCONFIG.gn文件虽然有1196行代码但是里面注释很清楚“WHAT IS THIS FILE?”她说这是一个“master GN build configuration”并解释说这个文件在bulid/目录的构建参数和顶层的.gn文件之后进行加载文件运行的上下文会在整个构建过程中的其他文件生效也就是内部的变量其实是全局的PLATFORM SELECTION现在来看首段代码if (host_os mac) { check_mac_system_and_cpu_script rebase_path(//build/scripts/check_mac_system_and_cpu.py) check_darwin_system_result exec_script(check_mac_system_and_cpu_script, [ system ], string) if (check_darwin_system_result ! ) { check_mac_host_cpu_result exec_script(check_mac_system_and_cpu_script, [ cpu ], string) if (check_mac_host_cpu_result ! ) { host_cpu arm64 } } } else if (host_os linux) { check_linux_cpu_script rebase_path(//build/scripts/check_linux_cpu.py) check_linux_cpu_result exec_script(check_linux_cpu_script, [ cpu ], string) if (check_linux_cpu_result ! ) { host_cpu arm64 } }虽然我并不会 GN 语言但是英文还是略懂一二这段就是简单的读取了我们编译所用的主机host的系统信息和 CPU 架构信息其中的实现是靠bulid/scripts/下的脚本来实现。接下来使用declare_args()定义了一系列的全局变量看名字大多是表达一些入口参数如预加载输出目录preloader_output_dir等方便后续复用和理解。在后续代码中product_build_config read_file(${preloader_output_dir}/build_config.json, json) global_parts_info read_file(${preloader_output_dir}/parts_config.json, json)这两句起了至关重要的作用它从 preloader 中取出一些关键信息其中就包括许多target的产品配置信息将它们注入全局变量中。为了尽快整体把握整个源代码这部分细节不再追究。等等有一个细节至关重要if (target_cpu ) { if (target_os ohos || target_os android || target_os ios) { target_cpu arm } else { target_cpu host_cpu } }可以看到它只是照顾到了“arm”后续是否应该“else if”一个“riscv”BUILD FLAGS继续贯穿上述“偷懒”的原则后一大段代码配置了一些列的flag用于列出一些构建过程中的输入参数每个都有它的默认值如果与命令行中指定的值发生冲突就会重写这个值。if (custom_toolchain ! ) { set_default_toolchain(custom_toolchain) } else if (_default_toolchain ! ) { set_default_toolchain(_default_toolchain) }注释中告诫我不能在这个文件中添加flag如果需要可以在对应组建的build.gn中添加。嗯规范。同样的也有值得注意的部分if ((target_os ohos target_cpu x86_64) || device_company emulator) { is_emulator true } # different host platform tools directory. if (host_os linux) { if (host_cpu arm64) { host_platform_dir linux-aarch64 } else { host_platform_dir linux-x86_64 } } else if (host_os mac) { if (host_cpu arm64) { host_platform_dir darwin-arm64 } else { host_platform_dir darwin-x86_64 } } else { assert(false, Unsupported host_os: $host_os) }同样的后续也有可能需要完善在此做个记录以防日后需要用的时候找不到地方。TOOLCHAIN SETUP接下来配置了默认的工具链注释中提醒说我们要在不支持的操作系统和 CPU 上进行编译时要尽早配置工具链。虽然我可能用不到这一点但是我希望我未来一定能用上。这里的代码很简单通过host和target的操作系统和 CPU 的架构不同组合来在bulid/toolchain中选择不同的工具链。同样的这里的修改是否也在我们当前的任务列表中呢OS DEFINITIONS这部分最简单一对布尔值为了方便记录下 OS 的情况。太简单了再写一行水字数。SOURCES FILTERS这是个文件过滤器不知道这样称呼人家合适不合适但是值得一提的是使用的规则并非常见的 Regular Expressions只支持*和\b。TARGET DEFAULTS这里对每个特定类型的target设置了一些默认配置这些值会自动配置在对应的target上好消息是注释中告诉了可以按照需求对target进行添加和删除。读到这里我才感觉有点思路前面的部分似乎都是为了最后这一操作做准备除了主编译器和连接器在这里还可以为了某一target定制一些配置override 掉默认的配置也可以直接修改一些默认的配置。不知如此这里还针对标准系统和轻量级的系统做了不同的处理分别使用不同的逻辑进行组装。令我感动的是这里出现了我项目的关键词第一次。if (current_cpu arm64) { arch aarch64 } else if (current_cpu riscv32) { arch riscv32 } else if (current_cpu loongarch64) { arch loongarch64 }虽然仅仅有这一处说明我前面的思考可能是对的。总结一下BUILDCONFIG.gn的主要工作先是 declare_args全局构建参数比如product_name、device_name、use_sandbox然后从 preloader 产物里读配置最关键的是build_config.json和parts_config.json最后是把target_os、target_cpu、product_toolchain这些真正决定编译行为的变量灌进全局环境。这里不是“定义要编什么”而是定义后面所有BUILD.gn解释代码时所处的世界。编译入口在编译构建主目录下我们刚刚了解了编译相关的配置项在build/config/目录现在我们依然通关热烈祝贺接下来进入编译的正式流程在build/core/目录下的BUILD.gn文件江湖人称是整个编译的入口配置是根 BUILD 文件。相比于上一个“BOSS”这个文件显然比较简单那就直接速通了它。整个代码的结构是这样的# gn target defined if (product_name ohos-sdk) { group(build_ohos_sdk) { ...... } } else if (product_name arkui-x) { group(arkui_targets) { ...... } } else { group(make_all) { ...... } ...... }一目了然啊注释先“一鸣惊人”这是在定义 GN 的目标现在一看确实是这样的根据产品类型product_name选择进入三种不同的程序流分别为 SDK 聚合、跨平台 ArkUI SDK 聚合和普通系统的构建链。group()是啥意思顾名思义“集合”目前来看使用方式很好推测括号里面命名大括号中定义依赖也就是代码中省略的一些deps。不然说这 GN 语言简单呢连学都不用学猜猜就能知道意思。我们不难注意到在普通系统即make_all的构建里面有一个特立独行的if语句让我们拎她出来审问一下if (is_standard_system !is_llvm_build) { deps [ :images ] }翻译成人类语言如果这玩意儿是标准系统而且不是 LLVM 构建最终编译会生成 image。唉等会什么是 LLVM 构建GOOGLE 一下Low Level Virtual Machine 的缩写这是一个编译器的框架系统这个可以——好了暂时不学这么多了先完成主线任务这是一种编译的工具方式我们继续。好了当我回过头来看这个文件它主要工作就两个字——封装。只是在顶层构建了许多group并没有底层的实现如同绘制了一个蓝图。这其实对于我们想要快速学习整套源代码有很大的帮助能够让我们在整体上有清晰的把握。ohos.gni进入整个编译要关注build/ohos.gni。为啥呢在文档中说它“汇总了常用的.gni文件方便各个模块一次性 import”看文件的具体内容也是如此