当前位置: 首页 > 工具软件 > Go UI > 使用案例 >

四、soong_ui的main.go

微生宝
2023-12-01

一、android build system编译系统概述
二、envsetup.sh脚本(Android10)
三、soong_ui.bash脚本
四、soong_ui的main.go
五、kati中调用main.mk
六、make目录中的main.mk
七、AndroidProducts.mk
八、module-info.json
九、子模块的执行


soong_ui参数

必要的参数

从代码看,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参数

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)
		}
    ...
}

–skip-make参数

此参数用来跳过make或者说kati编译过程

build/soong/ui/build/build.go文件的Build函数定义了主体构建流程

如果使用了该参数,config.SkipMake()返回值就会为True

即,只编译BuildSoongBuildNinja

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
		...
	}
}

dist参数

执行 Build,并将 MAKECMDGOALS 变量定义的输出文件拷贝到 /out/dist 目录。

checkbuild参数

/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)

命令传参进行宏定义(KeyValue组合)

make祖传艺能,soong/kati支持了

make DEFILE_KEY_XXX=value

help和clean参数

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.gohelp函数

这里是调用了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.")
}

makefile中支持的参数 or makefile中的伪目标

这里列出来一点

详细的见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)
		}
	}
}

soong_ui中的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)
	}
}
 类似资料: