文章标题:AOSP Soong 创建过程详解
注意 按照我最新的理解来看,我们现在并不需要花费那么多的精力在 Soong 上,一是考虑到目前 Android Build System 的整体流程其实仍然是基于 make/kati 的,Soong 到目前位置还是停留在对部分单个 module 的构建描述上;还有一个就是考虑到目前 Google 的策略,其实后面 Soong 也会淡出,被 Basel 所代替。所以我们为什么还要花那么多时间在研究 soong 上呢。
以上是我目前的理解。至少从我学习的经历来看,Soong 已经不值得我再深入去看了,所以本文的内容实际上包含了部分原 aosp 10 的内容和部分我改动后基于 12 的内容,是一个混杂的描述。仅供大家参考,大家看的时候注意结合实际代码甄别。是否会继续全部更新到 12,我暂时还未决定。
本文已更新到 aosp 12
前阵子总结了一下 AOSP Build 背后涉及的相关知识,具体可以参考这篇知乎文章《AOSP Build 背后涉及的相关知识汇总》。当时总结了 AOSP 引入 Soong 后的编译过程,参考如下图所示:
这个流程图是一个非常 high-level 的描述,特别是对 Soong 是如何将输入的各个模块的 Android.bp 文件转化为 out/soong/build.ninja
只是一笔带过了。由于移植的需要,最近还是仔细又看了一下,这篇文章就来把这个过程的细节再扒拉扒拉,顺便记着备忘。
首先提醒大家一下,这篇文章关注的是下图中用红色方框圈起来的部分。即引入 Soong 后,AOSP 是如何分析 Android.bp,并将其转换为 build.ninja 文件。
大家现在应该已经知道,当前 AOSP 的编译框架还是保留了 make 和 soong 两套,后续的 roadmap 会逐渐用 soong 方式替换掉剩余的 make,这样就完全统一为 Soong 了。但至少现在 AOSP 依然存在两套构造的入口,基于 make 的和基于 Soong 的。从后面的分析我们可以看出,在目前最新版本的 AOSP 中,缺省情况下,make 流程内部已经被悄悄替换为 soong 了。
备注:Google 看上去已经没有耐心再等 soong 了,Google 又发起了一个将 Android 的 Build 系统迁移到 Bazel 的计划,详细可以参考 《Bazel 和 AOSP 介绍》。
我们先简单看一下基于 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
目前以上两个方式已经统一,所以我们重点看方式一。
我们重点来看看现在推荐的构建过程,这个只要是编译过 Android 的同学再熟悉不过的了,标准的构造操作步骤分三步:
$ source build/envsetup.sh
$ lunch [<product>-<variant>]
$ m [<options>] [<targets>] [<variable>=<value>...]
. build/envsetup.sh
。build/envsetup.sh
实际只是一个符号链接,指向 build/make/envsetup.sh
,这个脚本定义了用于编译的很多命令,包括标准构建流程中的 m
/mm
/mmm
等,所以 source 这个脚本相当于导入了这些命令函数,为第三步做准备。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
是一样的,而 mmma
和 mmm
的效果也是一样的。这个从 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 的构造过程。
严格地说,进入 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
。这个脚本主要做了以下几件事情:
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 用户的操作接口。
我们来看看 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 系统构建起来。
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 官方定义的 bootstrap 和 primary 两个阶段(顺便说一下第三个称之为 main 阶段,不在本文的介绍范围内)。其中代码中的前四行属于第一阶段,在 Soong 系统里其术语又称之为 minibootstrap;第五行属于第二阶段,Soong 术语上称为 bootstrap。注意这里稍微有些混淆,我们后面主要以 Soong 的术语为主,必要时会对应看一下 Blueprint 的术语。这里我们可以回忆一下,Blueprint 和 Soong 的关系,Blueprint 是一个公共模块, Soong 利用 Blueprint 搭建了 AOSP 的自动化构建系统。
之所以叫 bootstrap,意思就是从无到有创建 Soong。下面我们就来仔细看看 minibootstrap 和 bootstrap 这两个阶段分别做了些什么。
整个 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
这个阶段对应 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
这个文件超级巨大,谨慎打开!
前面啰里啰唆讲了太多,这里直接回放一下整个过程吧,直观感受一下 AOSP 是如何从无到有构建了自己的构造系统 Soong。当然这个远远不是结局,接下来等待你的将是真正漫长的 AOSP 编译过程,对应 Blueprint 的定义就是所谓的 main stage!enjoy it ;)
初始状态
Soong 的大门 soong_ui 盖起来了
进入 build.Build() 之前的样子
构造第一阶段 minibootstrap 所需要的 build.ninja
创建第一阶段 minibootstrap 需要的工具 minibp/bpglob
构造第二阶段 bootstrap 所需要的 build.ninja
第二阶段 bootstrap 的 输出,至此 Soong 系统搭建结束
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。