Kati详解-Android10.0编译系统(五)

2023-11-11

[Android取经之路] 的源码都基于Android-Q(10.0) 进行分析

[Android取经之路] 系列文章:

《系统启动篇》

Android系统架构
Android是怎么启动的
Android 10.0系统启动之init进程
Android10.0系统启动之Zygote进程
Android 10.0 系统启动之SystemServer进程
Android 10.0 系统服务之ActivityMnagerService
Android10.0系统启动之Launcher(桌面)启动流程
Android10.0应用进程创建过程以及Zygote的fork流程
Android 10.0 PackageManagerService(一)工作原理及启动流程
Android 10.0 PackageManagerService(二)权限扫描
Android 10.0 PackageManagerService(三)APK扫描
Android 10.0 PackageManagerService(四)APK安装流程
《日志系统篇》

Android10.0 日志系统分析(一)-logd、logcat 指令说明、分类和属性
Android10.0 日志系统分析(二)-logd、logcat架构分析及日志系统初始化
Android10.0 日志系统分析(三)-logd、logcat读写日志源码分析
Android10.0 日志系统分析(四)-selinux、kernel日志在logd中的实现​
《Binder通信原理》:

Android10.0 Binder通信原理(一)Binder、HwBinder、VndBinder概要
Android10.0 Binder通信原理(二)-Binder入门篇
Android10.0 Binder通信原理(三)-ServiceManager篇
Android10.0 Binder通信原理(四)-Native-C\C++实例分析
Android10.0 Binder通信原理(五)-Binder驱动分析
Android10.0 Binder通信原理(六)-Binder数据如何完成定向打击
Android10.0 Binder通信原理(七)-Framework binder示例
Android10.0 Binder通信原理(八)-Framework层分析
Android10.0 Binder通信原理(九)-AIDL Binder示例
Android10.0 Binder通信原理(十)-AIDL原理分析-Proxy-Stub设计模式
Android10.0 Binder通信原理(十一)-Binder总结

《HwBinder通信原理》

HwBinder入门篇-Android10.0 HwBinder通信原理(一)
 HIDL详解-Android10.0 HwBinder通信原理(二)
HIDL示例-C++服务创建Client验证-Android10.0 HwBinder通信原理(三)
HIDL示例-JAVA服务创建-Client验证-Android10.0 HwBinder通信原理(四)
HwServiceManager篇-Android10.0 HwBinder通信原理(五)
Native层HIDL服务的注册原理-Android10.0 HwBinder通信原理(六)
Native层HIDL服务的获取原理-Android10.0 HwBinder通信原理(七)
JAVA层HIDL服务的注册原理-Android10.0 HwBinder通信原理(八)
JAVA层HIDL服务的获取原理-Android10.0 HwBinder通信原理(九)
HwBinder驱动篇-Android10.0 HwBinder通信原理(十)
HwBinder原理总结-Android10.0 HwBinder通信原理(十一)
《编译原理》

编译系统入门篇-Android10.0编译系统(一)
编译环境初始化-Android10.0编译系统(二)
make编译过程-Android10.0编译系统(三)
Image打包流程-Android10.0编译系统(四
Kati详解-Android10.0编译系统(五)
Blueprint简介-Android10.0编译系统(六)
Blueprint代码详细分析-Android10.0编译系统(七)
Android.bp 语法浅析-Android10.0编译系统(八)
Ninja简介-Android10.0编译系统(九)
Ninja提升编译速度的方法-Android10.0编译系统(十)
Android10.0编译系统(十一)

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

    在最新的Android R(11)中,Google已经移除了/build/kati目录,只保留了一个预先编译出来的可执行文件:prebuilts/build-tools/linux-x86/bin/ckati,这意味着Google在逐渐从编译系统中移除kati,预计1-2个Android大版本,*.mk文件全部都切换成*.bp文件后,kati将会正式退出Android历史舞台。

 

2 kati 、ckati区别
    kati是go语言写的,而ckati是c++写的。kati官方文档对它的描述是:kati is an experimental GNU make clone。也就是说,kati是对等make命令的。只不过kati并不执行具体的编译工作,而是生成ninja文件。

    这里有个疑惑?为什么有两个版本的kati:kati/ckati?

kati刚开始是使用Golang编写的,但是后来验证下来发现编译速度不行,于是改成C++编写,所以现在存在两个版本:kati、ckati。我们在Android10.0编译过程中,是通过ckati来把makefile文件转换成ninja文件的。

    关于Go版本kati编译速度问题,可以通过kati自带文档:build/kati/INTERNALS.md来查看:

  Go版本比C++版本有更多的不必要的字符串分配。至于Go本身,似乎GC是主要的展示器。例如,Android的构建系统定义了大约一百万个变量,缓冲区将永远不会被释放。,这种分配格局对于非代际GC(non-generational)是不利的。

    因此采用C++编译会减少缓冲区分配问题,提高编译速度,因此我们现在主要还是使用ckati进行mk文件的转换。

 
3 Kati整体架构
    Kati由以下组件组成:

解析器(Parser)

评估器(Evaluator)

依赖构建器(Dependency builder)

执行器(Executor)

Ninja生成器(Ninja generator)

    Makefile有一些由零个或多个表达式组成的语句。有两个解析器和两个评估器, 一个用于Makefile的语句,另一个用于Makefile的表达式。

    GNU make的大部分用户可能不太关心评估器。但是,GNU make的评估器非常强大,并且是图灵完整的。对于Android的空构建,大部分时间都花在这个阶段。其他任务,例如构建依赖关系图和调用构建目标的stat函数,并不是瓶颈。这将是一个非常具体的Android特性。Android的构建系统使用了大量的GNU make黑魔法。

    评估器输出构建规则(build rules)和变量表(variable table)的列表。依赖构建器从构建规则列表中创建依赖图(dependency graph)。注意这一步不使用变量表。

    然后将使用执行器或Ninja生成器。无论哪种方式,Kati再次为命令行运行其评估器。该变量表再次用于此步骤。

 

4 kati是如何生成的
4.1 代码位置
    Android10.0中kati的代码位置:build/kati,AOSP中自带编译好的ckati。

prebuilts/build-tools/linux-x86/asan/bin/ckati
prebuilts/build-tools/linux-x86/bin/ckati
prebuilts/build-tools/darwin-x86/bin/ckati

kati也是它也是一个独立发布的项目,在GitHub上的位置是google/kati。

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

4.2 Kati的使用方法
    在Android的编译过程中,ckati会自动被使用,无须开发者担心。

    单独使用时,在包含Makefile的目录下,执行ckati,效果与make基本相同。执行ckati --ninja,可以根据Makefile生成build.ninja文件,并且附带env-aosp_arm.sh和ninja-aosp_arm.sh 。通过env-aosp_arm.sh来配置环境,通过执行./ninja-aosp_arm.sh来启动Ninja、使用build.ninja编译。

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

${kati} --ninja ${ninja_suffix_flag} --ignore_optional_include=out/%.P --ignore_dirty=out/% --use_find_emulator --detect_android_echo --detect_depfiles --gen_all_targets ${goma_flag} ${extra_flags} ${targets}
 

4.3 生成kati
    Android10.0编译时都是使用编译好的ckati(prebuilts/build-tools/linux-x86/bin/ckati)进行makefile的转换,不会再编译一下ckati,但是我们可以看看ckati是如何被编译出来的。

    ckati的编译方法:

cd  .../build/kati
make ckati
    会在build/kati的目录中生成一个二进制文件ckati

 
4.4 KATI生成过程
    在build/kati 中有个Makefile,执行make时,会编译其中的内容。

[build/kati/Makefile]
all: ckati ckati_tests
 
include Makefile.kati
include Makefile.ckati
 
test: all ckati_tests
  go test --ckati --ninja
 
clean: ckati_clean
 
.PHONY: test clean ckati_tests

 Makefile中有两个目标:ckat和ckati_tests,其中ckati就是我们要编译出来的内容,它对应的Makefile为 Makefile.ckati。

 

4.4.1 Makefile.ckati
    从Makefile.ckati中可以看出,ckati通过C++进行编译,而且依赖于KATI_CXX_OBJS和KATI_CXX_GENERATED_OBJS。

# Makefile.ckati
# Rule to build ckati into KATI_BIN_PATH
$(KATI_BIN_PATH)/ckati: $(KATI_CXX_OBJS) $(KATI_CXX_GENERATED_OBJS)
       @mkdir -p $(dir $@)
       $(KATI_LD) -std=c++11 $(KATI_CXXFLAGS) -o $@ $^ $(KATI_LIBS)
 
# Rule to build normal source files into object files in KATI_INTERMEDIATES_PATH
$(KATI_CXX_OBJS) $(KATI_CXX_TEST_OBJS): $(KATI_INTERMEDIATES_PATH)/%.o: $(KATI_SRC_PATH)/%.cc
       @mkdir -p $(dir $@)
       $(KATI_CXX) -c -std=c++11 $(KATI_CXXFLAGS) -o $@ $<
 
# Rule to build generated source files into object files in KATI_INTERMEDIATES_PATH
$(KATI_CXX_GENERATED_OBJS): $(KATI_INTERMEDIATES_PATH)/%.o: $(KATI_INTERMEDIATES_PATH)/%.cc
       @mkdir -p $(dir $@)
       $(KATI_CXX) -c -std=c++11 $(KATI_CXXFLAGS) -o $@ $<

ckati的编译log:

g++ -c -std=c++11 -g -W -Wall -MMD -MP -O -DNOLOG -march=native -o main.o main.cc
g++ -c -std=c++11 -g -W -Wall -MMD -MP -O -DNOLOG -march=native -o ninja.o ninja.cc
g++ -c -std=c++11 -g -W -Wall -MMD -MP -O -DNOLOG -march=native -o parser.o parser.cc
g++ -c -std=c++11 -g -W -Wall -MMD -MP -O -DNOLOG -march=native -o regen.o regen.cc
g++ -c -std=c++11 -g -W -Wall -MMD -MP -O -DNOLOG -march=native -o rule.o rule.cc

4.4.2 [/build/kati/main.cc]

    ckati的入口在main.cc中

    调用栈如下:

4.4.2.1 main()
main()主要步骤:

进行环境的初始化,初始化makefile解析器,包括include、define、ifndef等语法规则

解析ckati传入的参数内容,例如:"--ninja"\"--regen"等

执行编译,最终生成build-xxxx.ninja文件

退出ckati

接下来针对相关的函数,进行分析。

[/build/kati/main.cc]
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);
  FindFirstMakefie();
  if (g_flags.makefile == NULL)
    ERROR("*** No targets specified and no makefile found.");
  // This depends on command line flags.
  if (g_flags.use_find_emulator)
    InitFindEmulator();
  int r = Run(g_flags.targets, g_flags.cl_vars, orig_args);
  Quit();
  return r;
}

4.4.2.2 Flags::Parse()

解析ckati传入的参数内容,例如:"--ninja"\"--regen"等

void Flags::Parse(int argc, char** argv) {
...
  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;
    } else if (!strcmp(arg, "--regen")) {
      // TODO: Make this default.
      regen = true;
    }
...
  }
}

4.4.2.3 Run()
    根据传入的参数包含--ninja时,需要执行GenerateNinja(),Kati如果指定了--regen标志,则Kati会检查你的环境中的任何内容是否在上次运行后发生更改。如果Kati认为它不需要重新生成Ninja文件,它会很快完成。对于Android,第一次运行Kati需要接近30秒,但第二次运行只需要1秒。
 

static int Run(const vector<Symbol>& targets,
               const vector<StringPiece>& cl_vars,
               const string& orig_args) {
  double start_time = GetTime();
 
//传入参数包含--ninja 和 (--regen 或者--dump_kati_stamp)时,进入该流程
  if (g_flags.generate_ninja && (g_flags.regen || g_flags.dump_kati_stamp)) {
    ScopedTimeReporter tr("regen check time");
    if (!NeedsRegen(start_time, orig_args)) {
      fprintf(stderr, "No need to regenerate ninja file\n");
      return 0;
    }
    if (g_flags.dump_kati_stamp) {
      printf("Need to regenerate ninja file\n");
      return 0;
    }
    ClearGlobCache();
  }
  ...
//传入参数包含--ninja时,需要执行GenerateNinja()
  if (g_flags.generate_ninja) {
    ScopedTimeReporter tr("generate ninja time");
    GenerateNinja(nodes, ev.get(), orig_args, start_time);
    ev->DumpStackStats();
    return 0;
  }
  ...
  return 0;
}

4.4.2.4  GenerateNinja()
    GenerateNinja()会先初始化一个 NinjaGenerator的结构,然后解析之前的makefile,并且将node进行整理,会将所依赖的.o;.a; .so进行归类,在整理好了依赖之后,会将所的步骤写入文件build-xxxx.ninja中。
 

void GenerateNinja(const vector<NamedDepNode>& nodes,
                   Evaluator* ev,
                   const string& orig_args,
                   double start_time) {
  NinjaGenerator ng(ev, start_time);        //初始化了一个 NinjaGenerator的结构
  ng.Generate(nodes, orig_args);
}
 
  void Generate(const vector<NamedDepNode>& nodes, const string& orig_args) {
    unlink(GetNinjaStampFilename().c_str());
    PopulateNinjaNodes(nodes); //对前面include的makefile进行解析,并且将node进行整理,会将所依赖的.o;.a; .so进行归类
    GenerateNinja();  //在整理好了依赖之后,会将所的步骤写入文件build-xxxx.ninja中
    GenerateShell();
    GenerateStamp(orig_args);
  }

GenerateNinja() 会产生build-aosp_arm.ninja

GenerateShell()会产生env-aosp_arm.sh、ninja-aosp_arm.sh        

这里我们主要关注build-aosp_arm.ninja的生成过程。

void GenerateNinja() {
    ScopedTimeReporter tr("ninja gen (emit)");
    fp_ = fopen(GetNinjaFilename().c_str(), "wb");
    if (fp_ == NULL)
      PERROR("fopen(build.ninja) failed");
 
    fprintf(fp_, "# Generated by kati %s\n", kGitVersion);
    fprintf(fp_, "\n");
 
    if (!used_envs_.empty()) {
      fprintf(fp_, "# Environment variables used:\n");
      for (const auto& p : used_envs_) {
        fprintf(fp_, "# %s=%s\n", p.first.c_str(), p.second.c_str());
      }
      fprintf(fp_, "\n");
    }
 
    if (!g_flags.no_ninja_prelude) {
      if (g_flags.ninja_dir) {
        fprintf(fp_, "builddir = %s\n\n", g_flags.ninja_dir);
      }
 
      fprintf(fp_, "pool local_pool\n");
      fprintf(fp_, " depth = %d\n\n", g_flags.num_jobs);
 
      fprintf(fp_, "build _kati_always_build_: phony\n\n");
    }
 
    unique_ptr<ThreadPool> tp(NewThreadPool(g_flags.num_jobs));
    CHECK(g_flags.num_jobs);
    int num_nodes_per_task = nodes_.size() / (g_flags.num_jobs * 10) + 1;
    int num_tasks = nodes_.size() / num_nodes_per_task + 1;
    vector<ostringstream> bufs(num_tasks);
    for (int i = 0; i < num_tasks; i++) {
      tp->Submit([this, i, num_nodes_per_task, &bufs]() {
        int l =
            min(num_nodes_per_task * (i + 1), static_cast<int>(nodes_.size()));
        for (int j = num_nodes_per_task * i; j < l; j++) {
          EmitNode(nodes_[j], &bufs[i]);
        }
      });
    }
    tp->Wait();
 
    if (!g_flags.generate_empty_ninja) {
      for (const ostringstream& buf : bufs) {
        fprintf(fp_, "%s", buf.str().c_str());
      }
    }
 
    SymbolSet used_env_vars(Vars::used_env_vars());
    // PATH changes $(shell).
    used_env_vars.insert(Intern("PATH"));
    for (Symbol e : used_env_vars) {
      StringPiece val(getenv(e.c_str()));
      used_envs_.emplace(e.str(), val.as_string());
    }
 
    string default_targets;
    if (g_flags.targets.empty() || g_flags.gen_all_targets) {
      CHECK(default_target_);
      default_targets = EscapeBuildTarget(default_target_->output);
    } else {
      for (Symbol s : g_flags.targets) {
        if (!default_targets.empty())
          default_targets += ' ';
        default_targets += EscapeBuildTarget(s);
      }
    }
    if (!g_flags.generate_empty_ninja) {
      fprintf(fp_, "\n");
      fprintf(fp_, "default %s\n", default_targets.c_str());
    }
 
    fclose(fp_);
  }

Kati认为,当更改以下任一项时,需要重新生成Ninja文件:

传递给Kati的命令行标志

用于生成上一个ninja文件的Makefile的时间戳

评估Makefile时使用的环境变量

$(wildcard ...)的结果

$(shell ...)的结果

 

5 kati执行过程
    在第三节的make编译过程中,我们知道soong_ui执行编译时,会调用ckati把makefile编译成*.ninja文件,这里我们就看看具体的流程是如何执行的。

 

5.1 soong_ui build调用栈

 在之前的编译过程中,其中第三步和第四步,运行runKatiBuild()和runKatiPackage(),加载core/main.mk和packaging/main.mk,搜集所有的Android.mk文件,分别生成out/build-aosp_arm.ninja 和out/build-aosp_arm-package.ninja,这就是kati/ckati的编译过程。

    下面我们来一起看看具体的执行过程。

 

5.2 runKatiBuild()

 
[/build/soong/ui/build/kati.go]
func runKatiBuild(ctx Context, config Config) {
       ctx.BeginTrace(metrics.RunKati, "kati build")
       defer ctx.EndTrace()
 
       args := []string{
               "--writable", config.OutDir() + "/",
               "-f", "build/make/core/main.mk",
       }
 
       // PDK builds still uses a few implicit rules
       if !config.IsPdkBuild() {
               args = append(args, "--werror_implicit_rules")
       }
 
       if !config.BuildBrokenDupRules() {
               args = append(args, "--werror_overriding_commands")
       }
 
       if !config.BuildBrokenPhonyTargets() {
               args = append(args,
                       "--werror_real_to_phony",
                       "--werror_phony_looks_real",
                       "--werror_writable")
       }
 
       args = append(args, config.KatiArgs()...)
 
       args = append(args,
               "SOONG_MAKEVARS_MK="+config.SoongMakeVarsMk(),
               "SOONG_ANDROID_MK="+config.SoongAndroidMk(),
               "TARGET_DEVICE_DIR="+config.TargetDeviceDir(),
               "KATI_PACKAGE_MK_DIR="+config.KatiPackageMkDir())
 
       runKati(ctx, config, katiBuildSuffix, args, func(env *Environment) {})
}

这里的参数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")
       args = append([]string{
               "--ninja",
               "--ninja_dir=" + config.OutDir(),
               "--ninja_suffix=" + config.KatiSuffix() + extraSuffix,
               "--no_ninja_prelude",
               "--regen",
               "--ignore_optional_include=" + filepath.Join(config.OutDir(), "%.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",
       }, args...)
 
       if config.Environment().IsEnvTrue("EMPTY_NINJA_FILE") {
               args = append(args, "--empty_ninja_file")
       }
       cmd := Command(ctx, config, "ckati", executable, args...)
       cmd.Sandbox = katiSandbox
       pipe, err := cmd.StdoutPipe()
       if err != nil {
               ctx.Fatalln("Error getting output pipe for ckati:", err)
       }
       cmd.Stderr = cmd.Stdout
 
       envFunc(cmd.Environment)
 
       if _, ok := cmd.Environment.Get("BUILD_USERNAME"); !ok {
               u, err := user.Current()
               if err != nil {
                       ctx.Println("Failed to get current user")
               }
               cmd.Environment.Set("BUILD_USERNAME", u.Username)
       }
 
       if _, ok := cmd.Environment.Get("BUILD_HOSTNAME"); !ok {
               hostname, err := os.Hostname()
               if err != nil {
                       ctx.Println("Failed to read hostname")
               }
               cmd.Environment.Set("BUILD_HOSTNAME", hostname)
       }
 
       cmd.StartOrFatal()
       status.KatiReader(ctx.Status.StartTool(), pipe)
       cmd.WaitOrFatal()
}

调用Command(),根据传入的参数,生成一个cmd的结构,其中相关参数如下:

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]
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})}
executable:

prebuilts/build-tools/linux-x86/bin/ckati
Command封装方法如下:

[/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实现过程比较复杂,这里就不详细展开,有兴趣的朋友,可以把/build/kati中的C++源码详细的分析一下。

 

6.总结
    Kati的主要功能就是把Makefile转换成build-xxx.ninja文件从而来参与系统编译,随着Android逐步消除版本中的Makefile文件,Kati最终也会退出Android的历史舞台。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Kati详解-Android10.0编译系统(五) 的相关文章

  • hive数据仓库课后答案

    第一章 数据仓库的简介 一 填空题 1 数据仓库的目的是构建面向 分析 的集成化数据环境 2 Hive是基于 Hadoop 的一个数据仓库工具 3 数据仓库分为3层 即 源数据层 数据应用层 和数据仓库层 4 数据仓库层可以细分为 明细层

随机推荐

  • k8s部署SpringCloud应用

    一 准备工作 将v2目录上传到 root 目录 下载地址 链接 https pan baidu com s 1oqED4Kew5BeLFqms6U6ISw 提取码 lzx9 springcloud1 项目 用k8s部署 eureka eur
  • (JAVA练习)输入,输出二维数组

    题目 输入 输出二维数组 解答 import java util Scanner public class Erweishuzu public static void main String args 二维数组练习 Scanner sc n
  • element-ui 中dialog居中

    标题element ui 中dialog居中 el dialog display flex flex direction column margin 0 important position absolute top 50 left 50
  • 一款强大的浏览器翻译插件 - 沉浸式的翻译

    起因 前一段时间谷歌翻译宣布跑路 不再对大陆用户提供服务 听闻这一噩耗我不由得心里一惊 燕子 啊不是 谷歌没有你我可咋活呀 对于没太大工作需求 顶多遇上几个不认识单词或需要翻译网页的我来说 Chrome 自带的谷歌翻译可以说是我最常用的翻译
  • micropython源码分析之qstr

    前言 最近在研究micropython的源码编译过程 简单记录下关于qstr部分内容 本篇文章基于micropython1 18版本源码 1 19版本及之后可能会略有差异 标识符与相应对象的联系 Micropython中有很多标识符 例如l
  • 工作笔记:TrueCrypt编译记录

    工作笔记 TrueCrypt编译记录 TrueCrypt的最新版本6 2可以从官方网站上下载 我从这里下载了一个6 1的 http freedos pri ee truecrypt 在TrueCrypt官方网站上很多旧版本都没了 这里却很全
  • 关于Python中中文文本文件使用二进制方式读取后的解码UnicodeDecodeError问题

    最近老猿在进行文件操作的验证测试 发现对于中文文本文件如果使用二进制方式打开 返回的类型是bytes 如果要转换成可读的字符串信息需要进行解码 可是老猿使用decode 或decode UTF 8 解码后报错 Traceback most
  • 从零开始SpringCloud Alibaba实战(79)——Spring-Boot+AOP+统计单次请求方法的执行次数和耗时

    文章目录 前言 代码 ThreadLocal方案 前言 作为工程师 不能仅仅满足于实现了现有的功能逻辑 还必须深入认识系统 一次请求 流经了哪些方法 执行了多少次DB操作 访问了多少次文件操作 调用多少次API操作 总共有多少次IO操作 多
  • Java技术体系平台

    Java SE Java Standard Edition 标准版 支持面向桌面级应用 如Windows下的应用程序 的Java平台 提供了完整的Java核心API 此版本以前称为J2SE Java EE Java Enterprise E
  • CMSIS 到底是什么?

    CMSIS 到底是什么 先来看看ARM公司对CMSIS的定义 ARM Cortex 微控制器软件接口标准 CMSIS 是 Cortex M 处理器系列的与供应商无关的硬件抽象层 CMSIS 可实现与处理器和外设之间的一致且简单的软件接口 从
  • 【网络自定向下的学习】——TCP3次握手和4次挥手详解

    目录 前言 一 可靠数据传输 1 确认应答机制 2 超时重传机制 二 建立连接 三次握手 1 建立连接的过程 2 为什么会有三次握手 3 三次握手可以携带数据吗 4 什么是半连接队列 三 断开连接 4次挥手 1 4次挥手的过程 2 为什么连
  • 浅谈ChatGPT与企业数字化转型

    ChatGPT作为当今一个现象级的爆款概念 它的出现 会与企业数字化碰撞出怎么样的花火 很多数字化转型中的企业 咨询师 也都把目光转向ChatGPT 以及ChatGPT背后的大模型 也许 ChatGPT会给数字化转型带来新一轮的发展 助推剂
  • .net 抽奖概率计算

    公司需要做一个大转盘抽奖的活动 其实最关键的地方就是奖品的概率计算了 不过前两天做的这个计算规则挺简单 设置每个奖品的概率 所有奖品概率之和 乘以 随机值 0 1之间的double类型小数 抽中值 然后循环判断每个奖品的概率 直到大于抽中值
  • RT-Thread内核启动流程

    一般了解一份代码大多从启动部分开始 同样这里也采用这种方式 先寻找启动的源头 RT Thread 支持多种平台和多种编译器 而 rtthread startup 函数是 RT Thread 规定的统一启动入口 一般执行顺序是 系统先从启动文
  • Git rebase -i 合并多次提交

    我们在开发项目的过程中可能提交了多次代码 但有时候需要合并多次commit 实现的效果如下 如果你需要合并多个commit就通过Git log看下查你要合并commit的ID 记住最早的commit ID 如 123abc git reba
  • 什么是MVVM,vue的MVVM原理

    1 Mvvm定义MVVM是Model View ViewModel的简写 即模型 视图 视图模型 模型 指的是后端传递的数据 视图 指的是所看到的页面 视图模型 mvvm模式的核心 它是连接view和model的桥梁 它有两个方向 一是将
  • [906]git设置忽略文件.gitignore

    在仓库目录下新建一个名为 gitignore的文件 因为是点开头 没有文件名 没办法直接在windows目录下直接创建 必须通过右键Git Bash 按照linux的方式来新建 gitignore文件 gitignore文件对其所在的目录及
  • KeyError错误

    KeyError错误出现时可能是检索不到这个键名 就我自己碰到的这个问题来说 是编码的原因 前因是用了一个别人写的读取标定参数的函数 def read calib file path taken from https github com
  • 用一个数组表示股票每天的价格,数组的第i个数表示股票在第i天的价格。 如果只允许进行一次交易,也就是说只允许买一支股票并卖掉,求最大的收益。

    用一个数组表示股票每天的价格 数组的第i个数表示股票在第i天的价格 如果只允许进行一次交易 也就是说只允许买一支股票并卖掉 求最大的收益 提示 从前向后遍历数组 记录当前出现过的最低价格 作为买入价格 并计算以当天价格出售的收益 作为可能的
  • Kati详解-Android10.0编译系统(五)

    Android取经之路 的源码都基于Android Q 10 0 进行分析 Android取经之路 系列文章 系统启动篇 Android系统架构Android是怎么启动的Android 10 0系统启动之init进程Android10 0系