项目开始,毫无疑问,一片茫然,不过好在这意味着前途坦荡。
相比于直接使用 AI 帮我基本完成这个工作,我想按部就班地“浪费”一些时间在锻炼我这颗木头人脑上。成为一名开源大师。
所以,这次的技术博客,我更愿意叫做学习笔记

编译构建

到手这么巨大的代码,头发先掉了一半,我低头一看,掉落的头发赫然摆成五个大字母——“BUILD”。当我弄明白整个编译构建过程不就了解源代码的整体结构层次了吗?我去,不早说!

OpenHarmony 是一个基于 Gn 和 Ninja 的编译构建框架。等等,什么是 Gn?什么又是 ninja?那就学习一下!

Gn 和 Ninja

挺好奇 ninja 怎么读?查了查英文单词读作“嫩这”。

Ninja 是由 Google 开发的底层构建系统,目的是代替 make,这就显而易见了,这就是另一种“make”。既然用来代替 make,一定有一些优势在吧?确实,速度快,轻量级,以及更简单的语法。等等,语法?没错,Gn 此刻登上舞台,Generate ninja 的缩写,这是用于构建 .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 上进行编译时要尽早配置工具链。虽然我可能用不到这一点,但是我希望我未来一定能用上。
这里的代码很简单,通过 hosttarget 的操作系统和 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_namedevice_nameuse_sandbox;然后从 preloader 产物里读配置,最关键的是 build_config.jsonparts_config.json;最后是把 target_ostarget_cpuproduct_toolchain 这些真正决定编译行为的变量灌进全局环境。这里不是“定义要编什么”,而是定义后面所有 BUILD.gn 解释代码时所处的世界

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐