Android中的Kati

kati是Google专门为了Android而开发的一个小项目,基于Golang和C++。 目的是为了把Android中的Makefile,转换成Ninja文件。

代码位置

在Android 7.0以上的平台项目中,kati的位置是build/kati/。 另外,平台代码也自带编译好的ckati

$ 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上的位置是google/kati

git clone https://github.com/google/kati.git

编译

在Git库中,可以用make来直接编译。 编译完成后,目录下会出现ckati这个可执行文件。 产生ckati后,通过执行./m2n,可以把Makefile转换成build.ninja文件。 接下来,可以通过执行ninja来再次编译。

在Android项目中,这个Git库自带Android.bp文件,可以作为一个模块自动跟随项目一起编译。 也可以在项目路径中,执行mm单独编译。 编译产物主要是ckati,会被安装到项目的out/host/linux-x86/bin/ckati,作为编译过程中主机环境的一部分。 (这里是使用Linux来编译。如果是Windows或Darwin,产物位置会略有不同。)

使用

在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 "$@"

除了--ninja以外,ckati支持很多其它参数。 比如,和make一样,可以通过-f指定Makefile位置,通过-j指定线程数。 另外,在kati项目的m2n脚本中,就可以看到以下的复杂用法。

ckati --ninja --ignore_optional_include=out/%.P --ignore_dirty=out/% --use_find_emulator --detect_android_echo --detect_depfiles --gen_all_targets

然而,这些参数的具体含义,只能望文生义。 ckati的文档十分匮乏,不仅没有像样的网站,连帮助命令都没有。 不过,从其源文件find.cc中,还是可以看出一些端倪。

  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, "--gen_all_targets")) {
      gen_all_targets = true;
    } else if (!strcmp(arg, "--regen")) {
      // TODO: Make this default.
      regen = true;
    } else if (!strcmp(arg, "--regen_debug")) {
      regen_debug = true;
    } else if (!strcmp(arg, "--regen_ignoring_kati_binary")) {
      regen_ignoring_kati_binary = true;
    } else if (!strcmp(arg, "--dump_kati_stamp")) {
      dump_kati_stamp = true;
      regen_debug = true;
    } else if (!strcmp(arg, "--detect_android_echo")) {
      detect_android_echo = true;
    } else if (!strcmp(arg, "--detect_depfiles")) {
      detect_depfiles = true;
    } else if (!strcmp(arg, "--color_warnings")) {
      color_warnings = true;
    } else if (!strcmp(arg, "--werror_find_emulator")) {
      werror_find_emulator = true;
    } else if (!strcmp(arg, "--werror_overriding_commands")) {
      werror_overriding_commands = true;
    } else if (ParseCommandLineOptionWithArg(
        "-j", argv, &i, &num_jobs_str)) {
      num_jobs = strtol(num_jobs_str, NULL, 10);
      if (num_jobs <= 0) {
        ERROR("Invalid -j flag: %s", num_jobs_str);
      }
    } else if (ParseCommandLineOptionWithArg(
        "--remote_num_jobs", argv, &i, &num_jobs_str)) {
      remote_num_jobs = strtol(num_jobs_str, NULL, 10);
      if (remote_num_jobs <= 0) {
        ERROR("Invalid -j flag: %s", num_jobs_str);
      }
    } else if (ParseCommandLineOptionWithArg(
        "--ninja_suffix", argv, &i, &ninja_suffix)) {
    } else if (ParseCommandLineOptionWithArg(
        "--ninja_dir", argv, &i, &ninja_dir)) {
    } else if (!strcmp(arg, "--use_find_emulator")) {
      use_find_emulator = true;
    } else if (ParseCommandLineOptionWithArg(
        "--goma_dir", argv, &i, &goma_dir)) {
    } else if (ParseCommandLineOptionWithArg(
        "--ignore_optional_include",
        argv, &i, &ignore_optional_include_pattern)) {
    } else if (ParseCommandLineOptionWithArg(
        "--ignore_dirty",
        argv, &i, &ignore_dirty_pattern)) {
    } else if (ParseCommandLineOptionWithArg(
        "--no_ignore_dirty",
        argv, &i, &no_ignore_dirty_pattern)) {
    } else if (arg[0] == '-') {
      ERROR("Unknown flag: %s", arg);
    } else {
      if (strchr(arg, '=')) {
        cl_vars.push_back(arg);
      } else {
        should_propagate = false;
        targets.push_back(Intern(arg));
      }
    }

总结

kati是一个基于Makefile来生成ninja.build的小项目。 它是Google专为Android而开发,用来修正Android项目创建之初的错误——使用Makefile来做一个超复杂的编译构建系统。

在编译过程中,kati负责把既有的Makefile、Android.mk,转换成Ninja文件。 在Android 7.0中,它独挑大梁。 在Android 8.0以后,它与Soong一起,成为Ninja文件的两大来源。 也许,在几个大版本后,它会与原先的Makefile、Android.mk一起,退出Android平台编译系统的大舞台。

在单独使用时,它对普通的小项目还能勉强生效。 面对复杂的、多嵌套的Makefile时,它往往无法支持,会出现各种各样的问题。 当然,也可以理解为,它只为Android而设计。

总之,不推荐在Android以外的任何项目中使用kati


相关笔记