在 Android 构建系统中,Soong UI 是 Soong 构建框架的入口点。通过前面的学习我们知道,在执行 make 兼容模式编译时,soong_ui 会调用 ckati 来将传统的 Android.mk 和其他 Makefile 文件转换为 Ninja 构建文件(如 build-aosp_arm.ninja 和 build-aosp_arm-package.ninja)。这些 .ninja 文件随后会被 ninja 工具调用以进行真正的构建流程。
在这一过程中,kati.go 是 Kati 在 Go 层的封装实现,它负责协调并驱动整个 ckati 的执行过程。下面我们将对 kati.go 的内容进行详细解析,并完善其术语描述,以便更清晰地理解 Kati 执行的逻辑和作用。
kati.go 是 Android 构建系统中用于启动和管理 ckati 的 Go 实现模块,位于 Soong 框架中。该模块定义了与 Kati 相关的主要函数和逻辑,包括如何启动 ckati 进程、传递参数、处理输入输出等。
它本质上是对底层 C++ 编写的 ckati 可执行程序的一层封装,使得 Soong 能够统一调度 ckati 并集成到现代的构建流程中。
源码位置:/build/soong/ui/build/kati.go
func runKatiBuild(ctx Context, config Config) {
ctx.BeginTrace(metrics.RunKati, "kati build")
defer ctx.EndTrace()
args := []string{
// Mark the output directory as writable.
"--writable", config.OutDir() + "/",
// Fail when encountering implicit rules. e.g.
// %.foo: %.bar
// cp $< $@
"--werror_implicit_rules",
// Entry point for the Kati Ninja file generation.
"-f", "build/make/core/main.mk",
}
if !config.BuildBrokenDupRules() {
// Fail when redefining / duplicating a target.
args = append(args, "--werror_overriding_commands")
}
args = append(args, config.KatiArgs()...)
args = append(args,
// Location of the Make vars .mk file generated by Soong.
"SOONG_MAKEVARS_MK="+config.SoongMakeVarsMk(),
// Location of the Android.mk file generated by Soong. This
// file contains Soong modules represented as Kati modules,
// allowing Kati modules to depend on Soong modules.
"SOONG_ANDROID_MK="+config.SoongAndroidMk(),
// Directory containing outputs for the target device.
"TARGET_DEVICE_DIR="+config.TargetDeviceDir(),
// Directory containing .mk files for packaging purposes, such as
// the dist.mk file, containing dist-for-goals data.
"KATI_PACKAGE_MK_DIR="+config.KatiPackageMkDir())
runKati(ctx, config, katiBuildSuffix, args, func(env *Environment) {})
// compress and dist the main build ninja file.
distGzipFile(ctx, config, config.KatiBuildNinjaFile())
// Cleanup steps.
cleanCopyHeaders(ctx, config)
cleanOldInstalledFiles(ctx, config)
}
该函数是 Android 构建系统中 Soong UI 模块调用 Kati 生成主构建 Ninja 文件的核心函数之一。它的主要职责是:
- 调用底层的 ckati 工具;
- 将传统的 Makefile(如 build/make/core/main.mk)解析并转换为 Ninja 构建规则文件;
- 设置必要的参数和环境变量,以确保构建逻辑正确无误;
- 最后进行一些清理和分发操作。
这里的参数 args,通过 fmt 打印后,内容为:
[
--writable out/,
-f build/make/core/main.mk,
--werror_implicit_rules,
--werror_overriding_commands,
--werror_real_to_phony,
--werror_phony_looks_real,
--werror_writable,
SOONG_MAKEVARS_MK=out/soong/make_vars-aosp_arm.mk,
SOONG_ANDROID_MK=out/soong/Android-aosp_arm.mk,
TARGET_DEVICE_DIR=build/target/board/generic,
KATI_PACKAGE_MK_DIR=out/target/product/generic/obj/CONFIG/kati_packaging
]
同时,这里指定了 makefile 的入口为 build/make/core/main.mk,编译 target 的目录为 build/target/board/generic。
func runKati(ctx Context, config Config, extraSuffix string, args []string, envFunc func(*Environment)) {
executable := config.PrebuiltBuildTool("ckati")
// cKati arguments.
args = append([]string{
// Instead of executing commands directly, generate a Ninja file.
"--ninja",
// Generate Ninja files in the output directory.
"--ninja_dir=" + config.OutDir(),
// Filename suffix of the generated Ninja file.
"--ninja_suffix=" + config.KatiSuffix() + extraSuffix,
// Remove common parts at the beginning of a Ninja file, like build_dir,
// local_pool and _kati_always_build_. Allows Kati to be run multiple
// times, with generated Ninja files combined in a single invocation
// using 'include'.
"--no_ninja_prelude",
// Support declaring phony outputs in AOSP Ninja.
"--use_ninja_phony_output",
// Support declaring symlink outputs in AOSP Ninja.
"--use_ninja_symlink_outputs",
// Regenerate the Ninja file if environment inputs have changed. e.g.
// CLI flags, .mk file timestamps, env vars, $(wildcard ..) and some
// $(shell ..) results.
"--regen",
// Skip '-include' directives starting with the specified path. Used to
// ignore generated .mk files.
"--ignore_optional_include=" + filepath.Join(config.OutDir(), "%.P"),
// Detect the use of $(shell echo ...).
"--detect_android_echo",
// Colorful ANSI-based warning and error messages.
"--color_warnings",
// Generate all targets, not just the top level requested ones.
"--gen_all_targets",
// Use the built-in emulator of GNU find for better file finding
// performance. Used with $(shell find ...).
"--use_find_emulator",
// Fail when the find emulator encounters problems.
"--werror_find_emulator",
// Do not provide any built-in rules.
"--no_builtin_rules",
// Fail when suffix rules are used.
"--werror_suffix_rules",
// Fail when a real target depends on a phony target.
"--werror_real_to_phony",
// Makes real_to_phony checks assume that any top-level or leaf
// dependencies that does *not* have a '/' in it is a phony target.
"--top_level_phony",
// Fail when a phony target contains slashes.
"--werror_phony_looks_real",
// Fail when writing to a read-only directory.
"--werror_writable",
// Print Kati's internal statistics, such as the number of variables,
// implicit/explicit/suffix rules, and so on.
"--kati_stats",
}, args...)
// Generate a minimal Ninja file.
//
// Used for build_test and multiproduct_kati, which runs Kati several
// hundred times for different configurations to test file generation logic.
// These can result in generating Ninja files reaching ~1GB or more,
// resulting in ~hundreds of GBs of writes.
//
// Since we don't care about executing the Ninja files in these test cases,
// generating the Ninja file content wastes time, so skip writing any
// information out with --empty_ninja_file.
//
// From https://github.com/google/kati/commit/87b8da7af2c8bea28b1d8ab17679453d859f96e5
if config.EmptyNinjaFile() {
args = append(args, "--empty_ninja_file")
}
// Apply 'local_pool' to to all rules that don't specify a pool.
if config.UseRemoteBuild() {
args = append(args, "--default_pool=local_pool")
}
cmd := Command(ctx, config, "ckati", executable, args...)
// Set up the nsjail sandbox.
cmd.Sandbox = katiSandbox
// Set up stdout and stderr.
pipe, err := cmd.StdoutPipe()
if err != nil {
ctx.Fatalln("Error getting output pipe for ckati:", err)
}
cmd.Stderr = cmd.Stdout
// Apply the caller's function closure to mutate the environment variables.
envFunc(cmd.Environment)
// Pass on various build environment metadata to Kati.
if _, ok := cmd.Environment.Get("BUILD_USERNAME"); !ok {
username := "unknown"
if u, err := user.Current(); err == nil {
username = u.Username
} else {
ctx.Println("Failed to get current user:", err)
}
cmd.Environment.Set("BUILD_USERNAME", username)
}
if _, ok := cmd.Environment.Get("BUILD_HOSTNAME"); !ok {
hostname, err := os.Hostname()
if err != nil {
ctx.Println("Failed to read hostname:", err)
hostname = "unknown"
}
cmd.Environment.Set("BUILD_HOSTNAME", hostname)
}
cmd.StartOrFatal()
// Set up the ToolStatus command line reader for Kati for a consistent UI
// for the user.
status.KatiReader(ctx.Status.StartTool(), pipe)
cmd.WaitOrFatal()
}
该函数是 Android 构建系统中 Soong UI 调用底层 ckati 的核心封装函数,负责将 Makefile 解析为 Ninja 构建规则,并生成对应的 .ninja 文件。它不仅构造了完整的命令行参数,还处理了环境变量、沙箱控制、日志输出等关键构建流程。 这里调用 Command() 函数,根据传入的参数,生成一个 cmd 的结构,其中相关参数如下:
1)args
[
--ninja,
--ninja_dir=out,
--ninja_suffix=-aosp_arm,
--no_ninja_prelude,
--regen,
--ignore_optional_include=out/%.P,
--detect_android_echo,
--color_warnings,
--gen_all_targets,
--use_find_emulator,
--werror_find_emulator,
--no_builtin_rules,
--werror_suffix_rules,
--warn_real_to_phony,
--warn_phony_looks_real,
--top_level_phony,
--kati_stats,
--writable out/,
-f build/make/core/main.mk,
--werror_implicit_rules,
--werror_overriding_commands,
--werror_real_to_phony,
--werror_phony_looks_real,
--werror_writable,
SOONG_MAKEVARS_MK=out/soong/make_vars-aosp_arm.mk,
SOONG_ANDROID_MK=out/soong/Android-aosp_arm.mk,
TARGET_DEVICE_DIR=build/target/board/generic,
KATI_PACKAGE_MK_DIR=out/target/product/generic/obj/CONFIG/kati_packaging
]
这里是 ckati 执行时使用的完整命令行参数,这些参数组合起来,构成了一个完整的 ckati 构建上下文,确保从传统的 Makefile 构建方式向现代 Ninja 构建方式的平滑过渡。
2)config
%!s(*build.configImpl=&{
[] false 0xc00000ecc0
out/dist
16
1
false false false false
[] [droid]
-aosp_arm
generic
build/target/board/generic
false false false false true
})
这里是当前构建配置对象(config)的内容。
3)executable
prebuilts/build-tools/linux-x86/bin/ckati
这是 Android 构建系统中预编译好的 ckati 工具路径。表明当前正在调用的是官方预编译的高性能版本,而不是从源码编译的调试版本。
源码位置:/build/soong/ui/build/exec.go
func Command(ctx Context, config Config, name string, executable string, args ...string) *Cmd {
ret := &Cmd{
Cmd: exec.CommandContext(ctx.Context, executable, args...),
Environment: config.Environment().Copy(),
Sandbox: noSandbox,
ctx: ctx,
config: config,
name: name,
}
return ret
}
根据上述的相关参数可知,最终是调用系统准备好的 prebuilts/build-tools/linux-x86/bin/ckati 参与编译,其中传入的参数有 --ninja\ --regen--detect_android_echo 等,最终编译出 build-aosp_arm.ninja。
综合分析,Kati 的主要功能就是把 Makefile 转换成 build-xxx.ninja 文件从而来参与系统编译,随着 Android 逐步消除版本中的 Makefile 文件,Kati 最终也会退出 Android 的历史舞台。