14 Star 81 Fork 39

aosp-riscv/working-group

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
20210111-soong-process.md 26.58 KB
一键复制 编辑 原始数据 按行查看 历史

文章标题:AOSP Soong 创建过程详解

注意 按照我最新的理解来看,我们现在并不需要花费那么多的精力在 Soong 上,一是考虑到目前 Android Build System 的整体流程其实仍然是基于 make/kati 的,Soong 到目前位置还是停留在对部分单个 module 的构建描述上;还有一个就是考虑到目前 Google 的策略,其实后面 Soong 也会淡出,被 Basel 所代替。所以我们为什么还要花那么多时间在研究 soong 上呢。

以上是我目前的理解。至少从我学习的经历来看,Soong 已经不值得我再深入去看了,所以本文的内容实际上包含了部分原 aosp 10 的内容和部分我改动后基于 12 的内容,是一个混杂的描述。仅供大家参考,大家看的时候注意结合实际代码甄别。是否会继续全部更新到 12,我暂时还未决定。

1. 前言

本文已更新到 aosp 12

前阵子总结了一下 AOSP Build 背后涉及的相关知识,具体可以参考这篇知乎文章《AOSP Build 背后涉及的相关知识汇总》。当时总结了 AOSP 引入 Soong 后的编译过程,参考如下图所示:

这个流程图是一个非常 high-level 的描述,特别是对 Soong 是如何将输入的各个模块的 Android.bp 文件转化为 out/soong/build.ninja 只是一笔带过了。由于移植的需要,最近还是仔细又看了一下,这篇文章就来把这个过程的细节再扒拉扒拉,顺便记着备忘。

首先提醒大家一下,这篇文章关注的是下图中用红色方框圈起来的部分。即引入 Soong 后,AOSP 是如何分析 Android.bp,并将其转换为 build.ninja 文件。

2. 两个入口,一个结局

大家现在应该已经知道,当前 AOSP 的编译框架还是保留了 make 和 soong 两套,后续的 roadmap 会逐渐用 soong 方式替换掉剩余的 make,这样就完全统一为 Soong 了。但至少现在 AOSP 依然存在两套构造的入口,基于 make 的和基于 Soong 的。从后面的分析我们可以看出,在目前最新版本的 AOSP 中,缺省情况下,make 流程内部已经被悄悄替换为 soong 了。

备注:Google 看上去已经没有耐心再等 soong 了,Google 又发起了一个将 Android 的 Build 系统迁移到 Bazel 的计划,详细可以参考 《Bazel 和 AOSP 介绍》

2.1. 入口之一:make

我们先简单看一下基于 make 的构造流程。所谓 make 流程,指在操作上输入 make 方式的操作模式。当我们在 AOSP 总目录下 make 时,默认寻找当前路径下的 Makefile。看一下这个文件,发现没有什么特别的。

$ cat Makefile
### DO NOT EDIT THIS FILE ###
include build/make/core/main.mk
### DO NOT EDIT THIS FILE ###

无非就是导入了另一个文件 build/make/core/main.mk,看一下这个文件:

ifndef KATI
$(warning Calling make directly is no longer supported.)
$(warning Either use 'envsetup.sh; m' or 'build/soong/soong_ui.bash --make-mode')
$(error done)
endif

$(info [1/1] initializing build system ...)
# 以下省略 ......

关注一下这里的 ifndef ... endif 部分。如果我们直接运行 make 则 KATI 没有定义,所以会直接报错退出。当我们执行 m 的过程中走到 CKati 步骤时还会到这里来,不过此时 KATI 会被定义,就会走入下面打印 initializing build system ...

可见在 aosp 12 中,如果在源码根目录下执行 make 是会直接报错退出的,这点和在 aosp 10 是不同的。

$ make
build/make/core/main.mk:2: Calling make directly is no longer supported.
build/make/core/main.mk:3: Either use 'envsetup.sh; m' or 'build/soong/soong_ui.bash --make-mode'
build/make/core/main.mk:4: *** done.  Stop.

提示告诉我们现在执行构建,代替原来 make 的做法有两种:

  • 方式一:执行 m
  • 方式二:执行 build/soong/soong_ui.bash --make-mode

目前以上两个方式已经统一,所以我们重点看方式一。

2.2. 入口之二:m

我们重点来看看现在推荐的构建过程,这个只要是编译过 Android 的同学再熟悉不过的了,标准的构造操作步骤分三步:

$ source build/envsetup.sh
$ lunch [<product>-<variant>]
$ m [<options>] [<targets>] [<variable>=<value>...]
  • 第一步执行 . build/envsetup.shbuild/envsetup.sh 实际只是一个符号链接,指向 build/make/envsetup.sh,这个脚本定义了用于编译的很多命令,包括标准构建流程中的 m/mm/mmm 等,所以 source 这个脚本相当于导入了这些命令函数,为第三步做准备。
  • 第二步暂略不表,大致的行为是通过菜单选择定义了一些最基本的环境变量,执行后会打印出来,也是为第三步 m 做准备。
  • 第三步执行构造,我们来看看定义在 build/make/envsetup.sh 中的这些函数:
function m()
(
    _trigger_build "all-modules" "$@"
)

function mm()
(
    _trigger_build "modules-in-a-dir-no-deps" "$@"
)

function mmm()
(
    _trigger_build "modules-in-dirs-no-deps" "$@"
)

function mma()
(
    _trigger_build "modules-in-a-dir" "$@"
)

function mmma()
(
    _trigger_build "modules-in-dirs" "$@"
)

这些函数目前都是调用一个统一的入口 _trigger_build, 第一个参数明确解释了这些函数的区别。注意现在 mma 的效果和 mm 是一样的,而 mmmammm 的效果也是一样的。这个从 envsetup.sh 开头的注释中就可以看出来。

function _trigger_build()
(
    local -r bc="$1"; shift
    if T="$(gettop)"; then
      _wrap_build "$T/build/soong/soong_ui.bash" --build-mode --${bc} --dir="$(pwd)" "$@"
    else
      echo "Couldn't locate the top of the tree. Try setting TOP."
    fi
)

是不是很熟悉,这个函数的核心同样还是调用了 build/soong/soong_ui.bash 这个脚本。而这个 build/soong/soong_ui.bash 脚本正是进入 Soong 世界的统一入口。下面我们就以执行 m 为例,重点来看看 Soong 的构造过程。

3. 站在 Soong 的门前并踹上一脚

严格地说,进入 build/soong/soong_ui.bash 的时候有关 Soong 的东西除了一堆源码,什么还没有被创建出来,而 build/soong/soong_ui.bash 的主要作用就是先把这个门(UI)给做出来。而这个所谓的 “门” 就是一个叫做 soong_ui 的可执行程序。

看一下 build/soong/soong_ui.bash 这个脚本:

$ cat build/soong/soong_ui.bash
......
source ${TOP}/build/soong/scripts/microfactory.bash

soong_build_go soong_ui android/soong/cmd/soong_ui

cd ${TOP}
exec "$(getoutdir)/soong_ui" "$@"

soong_ui.bash 脚本中三行代码对应着三步核心处理逻辑:

  • 第一步:导入 ${TOP}/build/soong/scripts/microfactory.bash。这个脚本主要做了以下几件事情:

    • 设置 GOROOT 环境变量,指向 prebuild 的 go 编译工具链
    • 定义一些函数,特别地 soong_build_go() 这个函数会在第二步中被调用。
    • 导入 ${TOP}/build/blueprint/microfactory/microfactory.bash 这个脚本,这个脚本中定义了其他辅助函数,特别注意 build_go() 这个函数,这个函数会被 soong_build_go() 调用执行实际的动作,结果是会根据参数编译 go 源码生成相应的程序,这里简单举个例子理解一下:build_go soong_ui android/soong/cmd/soong_ui 就是根据 AOSP 源码树目录 soong/cmd/soong_ui 的 package 生成一个可执行程序叫 soong_ui。
  • 第二步:soong_build_go soong_ui android/soong/cmd/soong_ui。其作用是调用 soong_build_go 函数。这个函数有两个参数,从第一步的分析可以知道,soong_build_go 实际上是一个对 build_go() 函数的调用封装,所以以上语句等价于 build_go soong_ui android/soong/cmd/soong_ui。第一参数 soong_ui 是指定了编译生成的可执行程序的名字, soong_ui 是一个用 go 语言写的程序,也是 Soong 的实际执行程序。在第二个参数告诉 soong_build_go 函数,soong_ui 程序的源码在哪里,这里制定了其源码路径 android/soong/cmd/soong_ui(实际对应的位置是 build/soong/cmd/soong_ui

  • 第三步:就是在前述步骤的基础上通过 exec 命令创建进程执行上一步生成的 soong_ui, 并接受所有参数并执行,这一步相当于等价替换了原来传统意义上的 make $@。举个例子,假设我们当前 pwd 为 /home/jack/aosp12/aosp-riscv 我们调用了 m libart,最终等价于调用 out/soong_ui --build-mode --all-modules --dir=/home/jack/aosp12/aosp-riscv libart

总而言之,build/soong/soong_ui.bash 的最终效果就是帮助我们制作出了一个叫做 soong_ui 的应用程序,放在 out 下。然后一脚将门踹开,执行这个程序,从而进入 Soong 的世界。

顺便说一下,当我们执行完 lunch 命令的时候 soong_ui 这个程序就已经被制作出来了。

到这里,我们可以顺便体会一下 soong_ui 是个啥意思,Google 官方的说法,就是 “soong native UI” 的缩写,我猜 UI 应该就是 user interface 的缩写吧,表示这是面向 Soong 用户的操作接口。

4. 欢迎进入 Soong 的世界

我们来看看 soong_ui 这个程序都干了些什么。Soong 子系统都是用 go 语言写的,其主文件是 build/soong/cmd/soong_ui/main.go

在 Android 12 中调用 soong_ui 时,必需要带下面参数中的一个:

  • --make-mode
  • --dumpvar-mode
  • --dumpvars-mode
  • --build-mode

其中

  • --dumpvars-mode--dumpvar-mode 用于 dump the values of one or more legacy make variables 譬如例子:

    $ ./out/soong_ui --dumpvar-mode TARGET_PRODUCT
    sdk_phone64_riscv64

    或者:

    $ ./out/soong_ui --dumpvars-mode --vars="TARGET_PRODUCT TARGET_DEVICE"
    TARGET_PRODUCT='sdk_phone64_riscv64'
    TARGET_DEVICE='emulator_riscv64' 

    具体可以输入 ./out/soong_ui --dumpvar-mode --help 或者 ./out/soong_ui --dumpvars-mode --help 查看使用帮助

  • --make-mode 参数告诉 soong_ui,是正儿八经要开始编译。也就是说 soong_ui --make-mode 可以替代原来的 make, 所以后面还可以带一些参数选项。这些参数可能都是为了兼容 make 的习惯。

关键是看 soong/cmd/soong_ui/main.go 中的 main 函数。代码不多,为方便解释我就直接在关键代码周围加注释了(加粗加黑标出)。

func main() {
  	// 省略 ......

	/*
	 * soong_ui 的命令格式如下:
	 * soong_ui <command> [<arg 1> <arg 2> ... <arg n>]
	 * 其中 command 指定了 soong_ui 执行的具体动作类型
	 * getCommand 的作用是检查输入的 command 选项
	 * soong_ui 目前支持四个 command:
	 * - `--make-mode`
	 * - `--dumpvar-mode`
	 * - `--dumpvars-mode`
	 * - `--build-mode`
	 */
	c, args, err := getCommand(os.Args)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error parsing `soong` args: %s.\n", err)
		os.Exit(1)
	}

	// 省略 ......

	/*
	 * 定义一个 build.Context 的对象,
	 * build.Context 定义参考 `soong\ui\build\context.go`
	 * 我理解 Context 对象是一个容器,包含了在 build 过程中可能会涉及的 log,
	 * trace 等等辅助对象, 会传给其他函数,负责提供一些辅助功能,譬如
	 * 我们可以条用 Context 对象在执行过程中打印日志
	 */
	buildCtx := build.Context{ContextImpl: &build.ContextImpl{
		Context: ctx,
		Logger:  log,
		Metrics: met,
		Tracer:  trace,
		Writer:  output,
		Status:  stat,
	}}

	/*
	 * 定义了一个 build.Config 类型的 config 对象
	 * 这个 Config 对象的类定义参考 `soong\ui\build\config.go` 中的 configImpl,
	 * 我们在输入命令行时会带入各种选项,`build.NewConfig()` 这个函数的作用就是
	 * 解析这些参数并把它们记录在 config 对象中,这些参数会影响后面 make/m 的行为。
	 * 这个 run 函数根据 command 的不同被重载为不同的操作
	 * - "--make-mode": build.NewConfig(ctx, args...)
	 * - "--dumpvar-mode": dumpVarConfig
	 * - "--dumpvars-mode": dumpVarConfig
	 * - "--build-mode": buildActionConfig
	 * 这些操作最终都会走到 `build.NewConfig()` 这个函数,这个函数定义在 `build/soong/ui/build/config.go`
	 * 可以注意看一下这个文件中的 `parseArgs()` 文件,里面列出了会影响后面 build 的参数
	 * - - showcommands: 显示详细(verbose)的 build log
	 * - --skip-ninja: 不执行 ninja
	 * - --skip-make: 不执行和 make 有关的所有操作,包括 kati
	 * - --skip-kati: 不执行 kati
	 * - --soong-only: 只运行 soong
	 * - --skip-soong-tests:跳过 soong 的 test
	 * - -j:
	 * - -k:
	 * - dist:
	 * - checkbuild: 是否要在 build 后执行额外的 check 操作
	 */
	config := c.config(buildCtx, args...)

	if err := loadEnvConfig(); err != nil {
		fmt.Fprintf(os.Stderr, "failed to parse env config files: %v", err)
		os.Exit(1)
	}

	/*
	 * 下面的步骤主要是在准备输出目录以及各种 log 等
	 */
	build.SetupOutDir(buildCtx, config)

	if config.UseBazel() && config.Dist() {
		defer populateExternalDistDir(buildCtx, config)
	}

	// Set up files to be outputted in the log directory.
	logsDir := config.LogsDir()

	// 省略 ......

	/*
	 * build.FindSources 会创建 `out/.module_paths` 这个目录并在这个目录下产生一些
	 * 特殊的文件记录。譬如我们比较关心的 `out/.module_paths/Android.bp.list` 这个
	 * 文件,打开这个文件我们会看到里面记录了 AOSP 项目中所有 Android.bp 文件的路径
	 * 届时后面的操作会根据这里记录的项目对这些 Android.bp 文件进行分析,进而产生
	 * 最终的 build.ninja 文件。
	 */
	// Create a source finder.
	f := build.NewSourceFinder(buildCtx, config)
	defer f.Shutdown()
	build.FindSources(buildCtx, config, f)

	/*
	 * 这个 run 函数根据 command 的不同被重载为不同的操作
	 * - "--make-mode": runMake
	 * - "--dumpvar-mode": dumpVar
	 * - "--dumpvars-mode": dumpVars
	 * - "--build-mode": runMake
	 */
	c.run(buildCtx, config, args, logsDir)
}

具体 soong_ui 命令的使用,我们另外找个机会总结,这里我们主要以 m 为例。假设我们当前 pwd 为 /home/jack/aosp12/aosp-riscv。我们调用了 m,最终等价于调用 out/soong_ui --build-mode --all-modules --dir=/home/jack/aosp12/aosp-riscv

进入 c.run() (这里就是 runMake()) 之前的 Soong 系统状态如下,和前面相比也就是多了一些数据文件,譬如 out/.module_paths/Android.bp.list

看到这里读者可能会疑问,难道赫赫有名的 Soong 只是这么一个 soong_ui 么?答案当然不是,如果打个不恰当的比喻,soong_ui 只是 Soong 的大门,也就是说流程走到这里,我们进了 Soong 的大门,但会发现 Soong 的世界里除了一个大门(out/soong_ui)目前啥也没有。而 runMake() 函数要做的事情就是从无到有将整个 Soong 系统构建起来。

5. 一步,两步,从 0 搭建一个 Soong

runMake() 函数的最后一步也是其最核心的步骤,就是调用 build.Build()

func runMake(ctx build.Context, config build.Config, _ []string, logsDir string) {
	......

	build.Build(ctx, config)
}

build.Build() 这个核心函数定义在 build/soong/ui/build/build.go, 略去所有辅助的步骤,只保留核心的步骤,大家会发现和我们最早描述的下图是一样的,这里再贴一下,方便大家对照着理解。

// Build the tree. The 'what' argument can be used to chose which components of
// the build to run, via checking various bitmasks.
func Build(ctx Context, config Config) {
	// 省略 ......
	/*
	 * 输入 `m help` 会打印帮助,帮助的内容实际上是通过执行 `build/make/help.sh` 这
	 * 个脚本产生的
	 */
	if inList("help", config.Arguments()) {
		help(ctx, config)
		return
	}

	// 省略 ......

	/*
	 * 输入 `m clean` 或者 `m clobber` 会清除构建结果,等同于 `rm -rf out/`
	 */
	if inList("clean", config.Arguments()) || inList("clobber", config.Arguments()) {
		clean(ctx, config)
		return
	}

	/* 一些 build 前的准备工作和检查 */
	// 省略 ......

	/* 判断是否要启动一些操作 */
	what := RunAll
	if config.UseBazel() {
		what = RunAllWithBazel /* Bazel 取决于环境变量 `USE_BAZEL` 是否定义 */
	}
	if config.Checkbuild() { /* 譬如执行 m checkbuild */
		what |= RunBuildTests
	}
	if config.SkipConfig() { /* 譬如执行 m --skip-make */
		ctx.Verboseln("Skipping Config as requested")
		what = what &^ RunProductConfig
	}
	if config.SkipKati() { /* 譬如执行 m --skip-make 或者 --skip-kati --soong-only */
		ctx.Verboseln("Skipping Kati as requested")
		what = what &^ RunKati
	}
	if config.SkipKatiNinja() { /* 譬如执行 m --soong-only */
		ctx.Verboseln("Skipping use of Kati ninja as requested")
		what = what &^ RunKatiNinja
	}
	if config.SkipNinja() { /* 譬如执行 m --skip-ninja */
		ctx.Verboseln("Skipping Ninja as requested")
		what = what &^ RunNinja
	}

	if config.StartGoma() {
		startGoma(ctx, config)
	}

	if config.StartRBE() {
		startRBE(ctx, config)
	}

	/*
	 * `runMakeProductConfig()` 函数的的内部调用 `dumpMakeVars()`, 这个函数会通过 ckati
	 * 调用 `build/make/core/config.mk`, 其作用也是在执行执行重要的 runSoong 之前将
	 * makefile 中定义的变量转化为环境变量,进而传递给 soong
	 * 此外 `runMakeProductConfig()` 还会在 `out/soong/` 目录下生成一些重要的记录环
	 * 境变量的文件,譬如 `out/soong/soong.variables`,这个文件是在 soong_ui 的
	 * build 过程中在 bootstrap 之前生成,生成后我们在屏幕上才会看到 banner 显示各个
	 * 重要环境变量的值。内部含有当前项目的所有特定配置信息。
	 */
	if what&RunProductConfig != 0 {
		runMakeProductConfig(ctx, config)
	}

	// Everything below here depends on product config.

	if inList("installclean", config.Arguments()) ||
		inList("install-clean", config.Arguments()) {
		installClean(ctx, config)
		ctx.Println("Deleted images and staging directories.")
		return
	}

	if inList("dataclean", config.Arguments()) ||
		inList("data-clean", config.Arguments()) {
		dataClean(ctx, config)
		ctx.Println("Deleted data files.")
		return
	}

	if what&RunSoong != 0 {
		runSoong(ctx, config)

		if config.bazelBuildMode() == generateBuildFiles {
			// Return early, if we're using Soong as solely the generator of BUILD files.
			return
		}
	}

	if what&RunKati != 0 {
		genKatiSuffix(ctx, config)
		runKatiCleanSpec(ctx, config)
		runKatiBuild(ctx, config)
		runKatiPackage(ctx, config)

		ioutil.WriteFile(config.LastKatiSuffixFile(), []byte(config.KatiSuffix()), 0666) // a+rw
	} else if what&RunKatiNinja != 0 {
		// Load last Kati Suffix if it exists
		if katiSuffix, err := ioutil.ReadFile(config.LastKatiSuffixFile()); err == nil {
			ctx.Verboseln("Loaded previous kati config:", string(katiSuffix))
			config.SetKatiSuffix(string(katiSuffix))
		}
	}

	// Write combined ninja file
	createCombinedBuildNinjaFile(ctx, config)

	distGzipFile(ctx, config, config.CombinedNinjaFile())

	if what&RunBuildTests != 0 {
		testForDanglingRules(ctx, config)
	}

	if what&RunNinja != 0 {
		if what&RunKati != 0 {
			installCleanIfNecessary(ctx, config)
		}

		runNinjaForBuild(ctx, config)
	}

	// Currently, using Bazel requires Kati and Soong to run first, so check whether to run Bazel last.
	if what&RunBazel != 0 {
		runBazel(ctx, config)
	}
}

对 Build 这个核心函数的分析来看,其实最重要的是 runSoong(), 这个函数为最终最终 runNinja() 生成了 build.ninja 文件。runSoong()这个函数定义在 build/soong/ui/build/soong.go,下面我们来重点看一下这个函数。

TBD: runSoong() 的步骤在 12 中和 10 已经发生了一些变化,特别是找不到 minibp 相关的处理了, 也不会生成 .minibootstrap/build.ninja 了,也就是说下面步骤中的第二步和第四步不在了

函数不算长,我这里不想直接贴代码,我们可以重点关注这个函数中的类似 ctx.BeginTrace(XXX, "YYY") 的地方,基本上就是在标注重点步骤。

func runSoong(ctx Context, config Config) {
	// 省略 ......
   	ctx.BeginTrace(metrics.RunSoong, "blueprint bootstrap")
	// 省略 ......
   	ctx.BeginTrace(metrics.RunSoong, "minibp")
	// 省略 ......
	ctx.BeginTrace(metrics.RunSoong, "bpglob")
	// 省略 ......
	ninja("minibootstrap", ".minibootstrap/build.ninja")
	// 省略 ......
	ninja("bootstrap", ".bootstrap/build.ninja")
}

我这里一共标注了五行比较重要的代码,可以分成两个阶段,前四行属于第一阶段,Soong 术语上称为 minibootstrap;第五行属于第二阶段,Soong 术语上称为 bootstrap。之所以叫 bootstrap,意思就是从无到有创建 Soong。

我这里一共标注了五行比较重要的代码,之间的关系具体可以参考 AOSP 源码树中 Blueprint 的文档 build/blueprint/bootstrap/doc.go 的 "The Bootstrapping Process" 章节。这里的五个步骤可以对应到 Blueprint 官方定义的 bootstrapprimary 两个阶段(顺便说一下第三个称之为 main 阶段,不在本文的介绍范围内)。其中代码中的前四行属于第一阶段,在 Soong 系统里其术语又称之为 minibootstrap;第五行属于第二阶段,Soong 术语上称为 bootstrap。注意这里稍微有些混淆,我们后面主要以 Soong 的术语为主,必要时会对应看一下 Blueprint 的术语。这里我们可以回忆一下,Blueprint 和 Soong 的关系,Blueprint 是一个公共模块, Soong 利用 Blueprint 搭建了 AOSP 的自动化构建系统。

之所以叫 bootstrap,意思就是从无到有创建 Soong。下面我们就来仔细看看 minibootstrap 和 bootstrap 这两个阶段分别做了些什么。

5.1. 第一阶段 minibootstrap(对应 Blureprint 定义的 Bootstrap stage)

整个 minibootstrap 阶段包含关键的四步

  • 第一步 ctx.BeginTrace(metrics.RunSoong, "blueprint bootstrap"):

    执行 build/blueprint/bootstrap.bash -t 这个脚本,创建 out/soong/.minibootstrap/ 目录并在这个目录下创建一系列文件,其中最重要的是 out/soong/.minibootstrap/build.ninja 这个文件。这个文件很关键,是构造下一个阶段 bootstrap 的 ninja build 文件。此时的 Soong 系统状态如下:

  • 第二步 ctx.BeginTrace(metrics.RunSoong, "minibp")

    利用 blueprint 的 microfactory 创建 minibp 这个可执行程序,minibp 的源码在 build/blueprint/bootstrap/minibp,这个应用程序也是用于构造第二阶段 bootstrap 的工具。

  • 第三步 ctx.BeginTrace(metrics.RunSoong, "bpglob")

    同上第二步,也是利用 blueprint 的 microfactory 创建另一个可执行程序 bpglob,其源码 在 build/blueprint/bootstrap/bpglob。第二步和第三步做完后的 Soong 系统状态如下:

  • 第四步:ninja("minibootstrap", ".minibootstrap/build.ninja")

    调用 ninja,根据上面第一步生成的 out/soong/.minibootstrap/build.ninja,驱动第二步和第三步生成的 minibp/bpglob 这些程序构造第二阶段 bootstrap。最终的结果是生成了另一个 ninja 的 build 文件 out/soong/.bootstrap/build.ninja

    对照 verbose.log,输出了这么一段命令,可供参考:

    out/soong/.minibootstrap/minibp -t -l out/.module_paths/Android.bp.list -b out/soong -n out -d out/soong/.bootstrap/build.ninja.d -globFile out/soong/.minibootstrap/build-globs.ninja -o out/soong/.bootstrap/build.ninja Android.bp

5.2. 第二阶段 bootstrap

这个阶段对应 runSoong() 函数中的这一步:ninja("bootstrap", ".bootstrap/build.ninja")

调用 ninja,根据 minibootstrap 阶段生成的 out/soong/.bootstrap/build.ninja,逐步进行构造。

整个 bootstrap 阶段中首先会在 bin 目录下也生成了本阶段需要的一些工具程序:

$ ls ./out/soong/.bootstrap/bin
gotestmain  gotestrunner  loadplugins  soong_build  soong_env

注意其中的 soong_build 这个程序,按照官方的定义(具体参考文档 build/blueprint/bootstrap/doc.go),它就是整个 bootstrapping 流程 (包括我们这里给大家介绍的 minibootstrap 和 bootstrap 两个阶段) 最后生成的所谓 "primary builder"。它含有针对整个 AOSP 的工程的完整的构造逻辑,在 bootstrap 的最后一步会调用 soong_build 这个程序最终创建生成了针对整个 AOSP 项目的 ninja 的 build 文件 out/soong/build.ninja

参考 verbose.log 中最后一行可以对应一下:

[117/117] out/soong/.bootstrap/bin/soong_build -t -l out/.module_paths/Android.bp.list -b out/soong -n out -d out/soong/build.ninja.d -globFile out/soong/.bootstrap/build-globs.ninja -o out/soong/build.ninja Android.bp

out/soong/.bootstrap/bin/soong_build 会逐个扫描前期记录在 out/.module_paths/Android.bp.list 中的 Android.bp 文件,所以这包含了整个 AOSP 项 目的所有模块,所以这里生成的 out/soong/build.ninja 这个文件超级巨大,谨慎打开!

6. 总结

前面啰里啰唆讲了太多,这里直接回放一下整个过程吧,直观感受一下 AOSP 是如何从无到有构建了自己的构造系统 Soong。当然这个远远不是结局,接下来等待你的将是真正漫长的 AOSP 编译过程,对应 Blueprint 的定义就是所谓的 main stage!enjoy it ;)

  • 初始状态

  • Soong 的大门 soong_ui 盖起来了

  • 进入 build.Build() 之前的样子

  • 构造第一阶段 minibootstrap 所需要的 build.ninja

  • 创建第一阶段 minibootstrap 需要的工具 minibp/bpglob

  • 构造第二阶段 bootstrap 所需要的 build.ninja

  • 第二阶段 bootstrap 的 输出,至此 Soong 系统搭建结束

马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/aosp-riscv/working-group.git
git@gitee.com:aosp-riscv/working-group.git
aosp-riscv
working-group
working-group
master

搜索帮助

Dd8185d8 1850385 E526c682 1850385