一、android build system编译系统概述
二、envsetup.sh脚本(Android10)
三、soong_ui.bash脚本
四、soong_ui的main.go
五、kati中调用main.mk
六、make目录中的main.mk
七、AndroidProducts.mk
八、module-info.json
九、子模块的执行
从代码看,Google是想整一个UI界面来着,但是还没搞出来。
所以直接使用soong_ui
命令编译时,必需要带下面这三个参数中的一个:
--dumpvar-mode
--dumpvars-mode
--make-mode
没带这个参数就会报错The 'soong' native UI is not yet available.
代码逻辑如下:
func main() {
...
if len(os.Args) < 2 || !(inList("--make-mode", os.Args) ||
os.Args[1] == "--dumpvars-mode" ||
os.Args[1] == "--dumpvar-mode") {
log.Fatalln("The `soong` native UI is not yet available.")
}
...
}
同时存在时按如下优先顺序,选择性执行其中一个参数
--dumpvar-mode
--dumpvars-mode
--make-mode
源码如下,你看了就知道为啥了(if语句):
func main() {
...
if os.Args[1] == "--dumpvar-mode" {
dumpVar(buildCtx, config, os.Args[2:])
} else if os.Args[1] == "--dumpvars-mode" {
dumpVars(buildCtx, config, os.Args[2:])
} else {
//开始build了
...
}
}
--dumpvar-mode
参数--dumpvar-mode
参数后面需要跟着你想要dump的make变量名(一个)
[--abs]
子参数,用来显示绝对路径
用法如下:
usage: ./out/soong_ui --dumpvar-mode [--abs] <VAR>
In dumpvar mode, print the value of the legacy make variable VAR to stdout
'report_config' is a special case that prints the human-readable config banner from the beginning of the build.
调用流程:
/build/soong/cmd/soong_ui/main.go中的main函数 --->
dumpVar(buildCtx, config, os.Args[2:]) --->
build.DumpMakeVars(ctx, config, nil, []string{varName}) --->
dumpMakeVars(ctx, config, goals, makeVars, false) --->
cmd := Command(ctx, config, "dumpvars",
config.PrebuiltBuildTool("ckati"),
"-f", "build/make/core/config.mk",
"--color_warnings",
"--kati_stats",
"dump-many-vars",
"MAKECMDGOALS="+strings.Join(goals, " "))
cmd.StartOrFatal()
最后调用到了ckati
执行-f build/make/core/config.mk
,走到了Makefile中
--dumpvars-mode
参数[--vars="VAR VAR ..."]
子参数,等号后面跟着多个需要dump的make变量名
[--abs]
子参数,用来显示绝对路径
用法如下:
usage: ./out/soong_ui --dumpvars-mode [--vars="VAR VAR ..."]
In dumpvars mode, dump the values of one or more legacy make variables, in shell syntax.
The resulting output may be sourced directly into a shell to et corresponding shell variables.
'report_config' is a special case that prints the human-readable config banner from the beginning of the build.
调用流程(略),参考--dumpvar-mode
参数
--make-mode
参数--make-mode
参数,告诉soong_ui
,你是正儿八经要开始编译的参数。
除了这个没啥特别的,但是正式编译时必须带上。(原因见上面的分析)
showcommands参数是之前make编译架构中支持的命令,新的soong
编译架构中不支持了。
想要获得更详细的编译log,可以到out
目录下的verbose.log.gz
文件中获取
除此之外还可以看soong.log
,build.trace
,verbose.log
,error.log
等log
func main() {
...
logsDir := config.OutDir()
if config.Dist() {
logsDir = filepath.Join(config.DistDir(), "logs")
}
os.MkdirAll(logsDir, 0777)
log.SetOutput(filepath.Join(logsDir, "soong.log"))
trace.SetOutput(filepath.Join(logsDir, "build.trace"))
stat.AddOutput(status.NewVerboseLog(log, filepath.Join(logsDir, "verbose.log")))
stat.AddOutput(status.NewErrorLog(log, filepath.Join(logsDir, "error.log")))
...
if config.IsVerbose() {
writer.Print("! The argument `showcommands` is no longer supported.")
writer.Print("! Instead, the verbose log is always written to a compressed file in the output dir:")
writer.Print("!")
writer.Print(fmt.Sprintf("! gzip -cd %s/verbose.log.gz | less -R", logsDir))
writer.Print("!")
writer.Print("! Older versions are saved in verbose.log.#.gz files")
writer.Print("")
time.Sleep(5 * time.Second)
}
...
}
此参数用来跳过make
或者说kati
编译过程
build/soong/ui/build/build.go
文件的Build
函数定义了主体构建流程
如果使用了该参数,config.SkipMake()
返回值就会为True
即,只编译BuildSoong
和BuildNinja
const (
BuildNone = iota
BuildProductConfig = 1 << iota
BuildSoong = 1 << iota
BuildKati = 1 << iota
BuildNinja = 1 << iota
RunBuildTests = 1 << iota
BuildAll = BuildProductConfig | BuildSoong | BuildKati | BuildNinja
)
func Build(ctx Context, config Config, what int) {
...
if config.SkipMake() {
ctx.Verboseln("Skipping Make/Kati as requested")
what = what & (BuildSoong | BuildNinja)
}
...
if what&BuildSoong != 0 {
// Run Soong
...
}
if what&BuildKati != 0 {
// Run ckati
...
} else {
...
}
if what&RunBuildTests != 0 {
...
}
if what&BuildNinja != 0 {
...
// Run ninja
...
}
}
执行 Build,并将 MAKECMDGOALS 变量定义的输出文件拷贝到 /out/dist 目录。
/build/soong/cmd/soong_ui/main.go
中的main
函数中
checkbuild参数定义了的话,config.Checkbuild()
为True
RunBuildTests会编译到(备注:默认build.BuildAll中没有RunBuildTests部分)
toBuild := build.BuildAll
if config.Checkbuild() {
toBuild |= build.RunBuildTests
}
build.Build(buildCtx, config, toBuild)
make祖传艺能,soong/kati
支持了
make DEFILE_KEY_XXX=value
help和clean参数,在/build/soong/ui/build/build.go
中的Build
函数中被解析
代码如下:
// Build the tree. The 'what' argument can be used to chose which components of
// the build to run.
func Build(ctx Context, config Config, what int) {
...
if inList("help", config.Arguments()) {
help(ctx, config, what)
return
} else if inList("clean", config.Arguments()) || inList("clobber", config.Arguments()) {
clean(ctx, config, what)
return
}
...
}
分别调用help函数和clean函数
/build/soong/ui/build/build.go
的help
函数
这里是调用了build/make/help.sh
脚本
func help(ctx Context, config Config, what int) {
cmd := Command(ctx, config, "help.sh", "build/make/help.sh")
cmd.Sandbox = dumpvarsSandbox
cmd.RunAndPrintOrFatal()
}
/build/soong/ui/build/cleanbuild.go
中的clean
函数
// Remove everything under the out directory. Don't remove the out directory
// itself in case it's a symlink.
func clean(ctx Context, config Config, what int) {
removeGlobs(ctx, filepath.Join(config.OutDir(), "*"))
ctx.Println("Entire build directory removed.")
}
这里列出来一点
详细的见make help
打出来的信息
make all 编译所有内容
make snod 从已经编译出的包中快速重建system image
make vnod 从已经编译出的包中快速重建vendor image
make pnod 从已经编译出的包中快速重建product image
make psnod 从已经编译出的包中快速重建product_services image
make onod 从已经编译出的包中快速重建odm image
make sdk 编译出 Android 的 SDK
make bootimage 生成 boot.img
make systemimage 生成 system.img
make recoveryimage 生成 recovery.img
make userdataimage 生成 userdata.img
make cacheimage 生成 cache.img
soong_ui参数解析函数为/build/soong/ui/build/config.go
中的parseArgs
函数
当然需要结合着/build/soong/cmd/soong_ui/main.go
中的main
函数和其他代码一起来看
从/build/soong/cmd/soong_ui/main.go
中的main
函数到/build/soong/cmd/soong_ui/main.go
中的main
函数的调用逻辑如下:
main函数 -->
build.NewConfig -->
NewConfig函数 -->
ret.parseArgs(ctx, args) -->
parseArgs函数
parseArgs
函数源码如下:
func (c *configImpl) parseArgs(ctx Context, args []string) {
for i := 0; i < len(args); i++ {
arg := strings.TrimSpace(args[i])
if arg == "--make-mode" {
} else if arg == "showcommands" {
c.verbose = true
} else if arg == "--skip-make" {
c.skipMake = true
} else if len(arg) > 0 && arg[0] == '-' {
parseArgNum := func(def int) int {
if len(arg) > 2 {
p, err := strconv.ParseUint(arg[2:], 10, 31)
if err != nil {
ctx.Fatalf("Failed to parse %q: %v", arg, err)
}
return int(p)
} else if i+1 < len(args) {
p, err := strconv.ParseUint(args[i+1], 10, 31)
if err == nil {
i++
return int(p)
}
}
return def
}
if len(arg) > 1 && arg[1] == 'j' {
c.parallel = parseArgNum(c.parallel)
} else if len(arg) > 1 && arg[1] == 'k' {
c.keepGoing = parseArgNum(0)
} else {
ctx.Fatalln("Unknown option:", arg)
}
} else if k, v, ok := decodeKeyValue(arg); ok && len(k) > 0 {
c.environ.Set(k, v)
} else if arg == "dist" {
c.dist = true
} else {
if arg == "checkbuild" {
c.checkbuild = true
}
c.arguments = append(c.arguments, arg)
}
}
}
main
函数源码如下:
func main() {
var stdio terminal.StdioInterface
stdio = terminal.StdioImpl{}
// dumpvar uses stdout, everything else should be in stderr
if os.Args[1] == "--dumpvar-mode" || os.Args[1] == "--dumpvars-mode" {
stdio = terminal.NewCustomStdio(os.Stdin, os.Stderr, os.Stderr)
}
writer := terminal.NewWriter(stdio)
defer writer.Finish()
log := logger.New(writer)
defer log.Cleanup()
if len(os.Args) < 2 || !(inList("--make-mode", os.Args) ||
os.Args[1] == "--dumpvars-mode" ||
os.Args[1] == "--dumpvar-mode") {
log.Fatalln("The `soong` native UI is not yet available.")
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
trace := tracer.New(log)
defer trace.Close()
met := metrics.New()
stat := &status.Status{}
defer stat.Finish()
stat.AddOutput(terminal.NewStatusOutput(writer, os.Getenv("NINJA_STATUS"),
build.OsEnvironment().IsEnvTrue("ANDROID_QUIET_BUILD")))
stat.AddOutput(trace.StatusTracer())
build.SetupSignals(log, cancel, func() {
trace.Close()
log.Cleanup()
stat.Finish()
})
buildCtx := build.Context{ContextImpl: &build.ContextImpl{
Context: ctx,
Logger: log,
Metrics: met,
Tracer: trace,
Writer: writer,
Status: stat,
}}
var config build.Config
if os.Args[1] == "--dumpvars-mode" || os.Args[1] == "--dumpvar-mode" {
config = build.NewConfig(buildCtx)
} else {
config = build.NewConfig(buildCtx, os.Args[1:]...)
}
build.SetupOutDir(buildCtx, config)
logsDir := config.OutDir()
if config.Dist() {
logsDir = filepath.Join(config.DistDir(), "logs")
}
os.MkdirAll(logsDir, 0777)
log.SetOutput(filepath.Join(logsDir, "soong.log"))
trace.SetOutput(filepath.Join(logsDir, "build.trace"))
stat.AddOutput(status.NewVerboseLog(log, filepath.Join(logsDir, "verbose.log")))
stat.AddOutput(status.NewErrorLog(log, filepath.Join(logsDir, "error.log")))
defer met.Dump(filepath.Join(logsDir, "build_metrics"))
if start, ok := os.LookupEnv("TRACE_BEGIN_SOONG"); ok {
if !strings.HasSuffix(start, "N") {
if start_time, err := strconv.ParseUint(start, 10, 64); err == nil {
log.Verbosef("Took %dms to start up.",
time.Since(time.Unix(0, int64(start_time))).Nanoseconds()/time.Millisecond.Nanoseconds())
buildCtx.CompleteTrace(metrics.RunSetupTool, "startup", start_time, uint64(time.Now().UnixNano()))
}
}
if executable, err := os.Executable(); err == nil {
trace.ImportMicrofactoryLog(filepath.Join(filepath.Dir(executable), "."+filepath.Base(executable)+".trace"))
}
}
// Fix up the source tree due to a repo bug where it doesn't remove
// linkfiles that have been removed
fixBadDanglingLink(buildCtx, "hardware/qcom/sdm710/Android.bp")
fixBadDanglingLink(buildCtx, "hardware/qcom/sdm710/Android.mk")
f := build.NewSourceFinder(buildCtx, config)
defer f.Shutdown()
build.FindSources(buildCtx, config, f)
if os.Args[1] == "--dumpvar-mode" {
dumpVar(buildCtx, config, os.Args[2:])
} else if os.Args[1] == "--dumpvars-mode" {
dumpVars(buildCtx, config, os.Args[2:])
} else {
if config.IsVerbose() {
writer.Print("! The argument `showcommands` is no longer supported.")
writer.Print("! Instead, the verbose log is always written to a compressed file in the output dir:")
writer.Print("!")
writer.Print(fmt.Sprintf("! gzip -cd %s/verbose.log.gz | less -R", logsDir))
writer.Print("!")
writer.Print("! Older versions are saved in verbose.log.#.gz files")
writer.Print("")
time.Sleep(5 * time.Second)
}
toBuild := build.BuildAll
if config.Checkbuild() {
toBuild |= build.RunBuildTests
}
build.Build(buildCtx, config, toBuild)
}
}