1. 介绍
kati is an experimental GNU make clone. The main goal of this tool is to speed-up incremental build of Android.
Currently, kati does not offer a faster build by itself. It instead converts your Makefile to a ninja file.
Kati 是为了提高Android 编译速度而产生的实验性的GNU make 克隆的工具。本身没有提供快速编译,而是将Makefile 文件转换为Ninja 文件,再通过Ninja 进行编译提速。
Currently, kati's main mode is --ninja mode. Instead of executing build commands by itself, kati generates build.ninja file andninja actually runs commands.
目前,Kati的主要模式是--ninja模式。 Kati自己不用执行build命令,而是生成build.ninja文件,而ninja实际上运行命令。
Kati 最开始在Go 中实现的,期初开发者认为Go 可以获取足够的性能,但是最终却是使得Kati 的Go 版本变慢了。所以后来Kati 用C++重写实现。观看现在Kati 实现的代码,其中除了C++ 实现,还要部分的Go 和sh,这应该是考虑之后的最好性能。
2. 整体架构
- 解析器(Parser)
- 评估器(Evaluator)
- 依赖构建器(Dependency builder)
- 执行器(Executor)
- Ninja生成器(Ninja generator)
Kati 由上面几部分组成。
A Makefile has some statements which consist of zero or more expressions. There are two parsers and two evaluators - one for statements and the other for expressions.
其中两个解析器和两个评估期,一个针对Makefile中的语句,一个针对Makefile中的表达式。
评估器输出构建规则(build rules)和变量表(variable table)的列表。依赖构建器从构建规则列表中创建依赖图(dependency graph)。注意这一步不使用变量表。
然后将使用执行器或Ninja生成器。无论哪种方式,Kati再次为命令行运行其评估器。该变量表再次用于此步骤。
3. 代码位置
从目前来看(Android N、O、P)代码目录在:
build/kati/
另外,Android 也再带编译好的kati 文件:
$ find prebuilts/ -name ckati
prebuilts/build-tools/linux-x86/asan/bin/ckati
prebuilts/build-tools/linux-x86/bin/ckati
prebuilts/build-tools/darwin-x86/bin/ckati
github的source code路径为:
https://github.com/google/kati
可以通过下面命令获取source code:
git clone https://github.com/google/kati.git
4. 编译
在Android项目中,这个Git库自带Android.bp文件,可以作为一个模块自动跟随项目一起编译。 也可以在项目路径中,执行mm单独编译。 编译产物主要是ckati,会被安装到项目的out/host/linux-x86/bin/ckati,作为编译过程中主机环境的一部分。
下面是Android.bp 中节选的部分规则:
cc_binary_host {
name: "ckati",
defaults: ["ckati_defaults"],
srcs: ["main.cc"],
whole_static_libs: ["libckati"],
}
cc_binary_host {
name: "ckati_stamp_dump",
defaults: ["ckati_defaults"],
srcs: ["regen_dump.cc"],
static_libs: ["libckati"],
}
在Android也预置了ckati,在项目编译的时候会直接使用这里的ckati,详细看prebuilts/build-tools/build-prebuilts.sh,下面节选部分内容:
if [ -n ${build_soong} ]; then
# ckati and makeparallel (Soong)
SOONG_OUT=${OUT_DIR}/soong
SOONG_HOST_OUT=${OUT_DIR}/soong/host/${OS}-x86
rm -rf ${SOONG_OUT}
mkdir -p ${SOONG_OUT}
cat > ${SOONG_OUT}/soong.variables << EOF
{
"Allow_missing_dependencies": true,
"HostArch":"x86_64",
"HostSecondaryArch":"x86"
}
EOF
5. 用法
在Android项目中,ckati会在编译过程中,自动被使用,无需操心。
单独使用时,在包含Makefile的目录下,执行ckati,效果与make基本相同。 执行ckati --ninja,可以根据Makefile生成build.ninja文件,并且附带env.sh和ninja.sh。 通过env.sh来配置环境,通过执行./ninja.sh来启动Ninja、使用build.ninja编译。 生成的ninja.sh文件,主要内容如下。
. ./env.sh
exec ninja -f ./build.ninja "$@"
上面也提到,目前kati 使用都是带--ninja,其他的参数可以根据code 获取到,首先当然是main 函数:
int main(int argc, char* argv[]) {
if (argc >= 2 && !strcmp(argv[1], "--realpath")) {
HandleRealpath(argc - 2, argv + 2);
return 0;
}
Init();
string orig_args;
for (int i = 0; i < argc; i++) {
if (i)
orig_args += ' ';
orig_args += argv[i];
}
g_flags.Parse(argc, argv);
...
...
Quit();
return r;
}
g_glags 的Parse 就是解析命令行,来看下g_flags:
extern Flags g_flags;
从这个看应该是Flags 类了,没错,就在这里:
void Flags::Parse(int argc, char** argv) {
subkati_args.push_back(argv[0]);
num_jobs = num_cpus = sysconf(_SC_NPROCESSORS_ONLN);
const char* num_jobs_str;
const char* writable_str;
if (const char* makeflags = getenv("MAKEFLAGS")) {
for (StringPiece tok : WordScanner(makeflags)) {
if (!HasPrefix(tok, "-") && tok.find('=') != string::npos)
cl_vars.push_back(tok);
}
}
for (int i = 1; i < argc; i++) {
const char* arg = argv[i];
bool should_propagate = true;
int pi = i;
if (!strcmp(arg, "-f")) {
makefile = argv[++i];
should_propagate = false;
} else if (!strcmp(arg, "-c")) {
is_syntax_check_only = true;
} else if (!strcmp(arg, "-i")) {
is_dry_run = true;
} else if (!strcmp(arg, "-s")) {
is_silent_mode = true;
} else if (!strcmp(arg, "-d")) {
enable_debug = true;
} else if (!strcmp(arg, "--kati_stats")) {
enable_stat_logs = true;
} else if (!strcmp(arg, "--warn")) {
enable_kati_warnings = true;
} else if (!strcmp(arg, "--ninja")) {
generate_ninja = true;
} else if (!strcmp(arg, "--empty_ninja_file")) {
generate_empty_ninja = true;
} else if (!strcmp(arg, "--gen_all_targets")) {
gen_all_targets = true;
...
...
}
if (should_propagate) {
for (; pi <= i; pi++) {
subkati_args.push_back(argv[pi]);
}
}
}
}
6. 总结
Kati 可以看成一个小工具,主要是Android 中将Makefile 文件转换为另一个中编译模式——Ninja,而Ninja 也是Android 为了编译能够更加快速。后面也出现了Blueprint 替代了现在的Makefile,都是为了编译的快速、简便。以后Android 中也有可能不在需要Makefile,也一起期待吧!
附:
由于时间有限,个人也不是负责编译模块,所以,深入研究的比较少,有时间了定会详细剖析一下source code。
参考:
GitHub - google/kati: An experimental GNU make clone
其他文章: