Android 中Ninja 简介

本文介绍Ninja编译系统在Android项目中的应用,对比Makefile,Ninja以高速编译著称,适用于Chromium、Android及LLVM等项目。文章深入解析Ninja的设计理念、与Makefile的区别、Android中Ninja文件的生成与使用,以及如何利用Ninja进行高效编译。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

0. 前言

最开始,Ninja 是用于Chromium 浏览器中,Android 在SDK 7.0 中也引入了Ninja。

Ninja 其实就是一个编译系统,如同make ,使用Ninja 主要目的就是因为其编译速度快。

Ninja 除了用于Chromium browser 和Android,也用于LLVM 和依赖CMake的Ninja 后端项目。

1. Ninja 简介

Ninja 主要是一个注重速度的小型编译系统,Ninja 加入其他编译系统的海洋之中,主要目的就是更加的快速。

主要有两个有别与其他编译系统的特点:

  • Ninja 的input files是由高级别的编译系统生成而来;
  • Ninja 设计主要让编译尽可能的fase;

如果把其他的编译系统看作高级语言(例如C 语言),那么Ninja 目标就是汇编。

Ninja 包含描述任意依赖关系的最基本的功能。由于语法简单而无法表达复杂的决策。为此,Ninja 用一个单独的程序来生成input files。该程序(如autotools 项目中的./configure) 能分析系统的依赖关系,而且尽可能提前多做出策略,以便增加编译速度。

1.1 设计目标

下面是Ninja 设计的目标:

  • 编译特别快,甚至是一个非常大型的项目;
  • 关于如何编译代码的策略特别少。高级别的编译系统会有不同的策略。例如,编译后的目标文件是与源文件一个目录,还是用单独的目录来存放这些目标文件?是否有个“package” 规则来构建项目的分配分包?而对于Ninja,即使会出现更多的冗长,也会避免这些决策。
  • 一些用Makefiles  难以获取到的依赖关系,Ninja 可以正确获取;
  • 当便利和速度产生冲突的时候,Ninja 选择速度;

其中总结出来两句话:

  • Ninja 优先选择速度,即使编译策略有更便利的方式;
  • Ninja 用冗长的代码避免复杂的策略机制,这也是为了方便快速编译;

1.2 明确的非目标

  • convenient syntax for writing build files by hand. You should generate your ninja files using another program. This is how we can sidestep many policy decisions.
  • built-in rules. Out of the box, Ninja has no rules for e.g. compiling C code.
  • build-time customization of the build. Options belong in the program that generates the ninja files.
  • build-time decision-making ability such as conditionals or search paths. Making decisions is slow.

2. Makefile 与 Ninja

Makefile默认文件名为Makefilemakefile,也常用.make.mk作为文件后缀。Ninja 的默认文件名是build.ninja,其它文件也以.ninja为后缀。 执行Makefile的程序,默认是 GNU make,也有一些其它的实现。Ninja 的执行程序,就是ninja命令。

在Android项目中,make需要编译主机上安装,作为环境的一部分。 而ninja 命令则是Android平台代码自带。

$ find prebuilts/ -name ninja
prebuilts/build-tools/linux-x86/asan/bin/ninja
prebuilts/build-tools/linux-x86/bin/ninja
prebuilts/build-tools/darwin-x86/bin/ninja

3. Ninja 命令

$ ninja -h
usage: ninja [options] [targets...]

if targets are unspecified, builds the 'default' target (see manual).

options:
  --version  print ninja version ("1.7.2")

  -C DIR   change to DIR before doing anything else
  -f FILE  specify input build file [default=build.ninja]

  -j N     run N jobs in parallel [default=6, derived from CPUs available]
  -k N     keep going until N jobs fail [default=1]
  -l N     do not start new jobs if the load average is greater than N
  -n       dry run (don't run commands but act like they succeeded)
  -v       show all command lines while building

  -d MODE  enable debugging (use -d list to list modes)
  -t TOOL  run a subtool (use -t list to list subtools)
    terminates toplevel options; further flags are passed to the tool
  -w FLAG  adjust warnings (use -w list to list warnings)

很多参数,和make是比较类似的,比如-f-j等,不再赘述。 有趣的是-t-d-w这三个参数,最有用的是-t

$ ninja -t list
ninja subtools:
    browse  browse dependency graph in a web browser
     clean  clean built files
  commands  list all commands required to rebuild given targets
      deps  show dependencies stored in the deps log
     graph  output graphviz dot file for targets
     query  show inputs/outputs for a path
   targets  list targets by their rule or depth in the DAG
    compdb  dump JSON compilation database to stdout
 recompact  recompacts ninja-internal data structures

ninja -t clean是清理产物,是自带的,而make clean往往需要自己实现。 其它都是查看编译过程信息的工具,各有作用,可以进行复杂的编译依赖分析。

4. Android 中ninja 文件

从Android 7开始,编译时默认使用 Ninja。 但是,Android项目里是没有 .ninja 文件的。 遵循Ninja的设计哲学,编译时,会先把Makefile通过 kati 转换成 .ninja文件,然后使用 ninja 命令进行编译。 这些 .ninja 文件,都产生在 out/ 目录下,共有三类。

4.1 build-*.ninja 文件

通常非常大,几十到几百MB。 对 make 全编译,命名是 build-<product_name>.ninja。 如果Makefile发生修改,需要重新产生Ninja文件。

这里 Android 有一个 bug,或者说设计失误。 mm、mma 的Ninja文件,命名是build-<product_name>-<path_to_Android.mk>.ninja。 而 mmm、mmma 的Ninja文件,命名是build-<product_name>-_<path_to_Android.mk>.ninja。 显然,不同的单模块编译,产生的也是不同的Ninja文件。

这个设计本身就有一些问题了,为什么不同模块不能共用一个总的Ninja文件? 这大概还是为了兼容旧的 Makefile 设计。 在某些 Android.mk 中,单模块编译与全编译时,编译内容截然不同。 如果说这还只能算是设计失误的话,那么 mm 与 mmm 使用不同的编译文件,就是显然的 bug了。 二者相差一个下划线_,通过mv或cp,可以通用。

4.2 combined-*.ninja 文件

在使用了Soong 后,除了 build-*.ninja之外,还会产生对应的 combined-*.ninja,二者的*内容相同。 以下以 AOSP 的 aosp_arm64-eng 为例,展示 out/combined-aosp_arm64.ninja文件的内容。

builddir = out
include out/build-aosp_arm64.ninja
include out/soong/build.ninja
build out/combined-aosp_arm64.ninja: phony out/soong/build.ninja

这类是组合文件,是把 build-*.ninjaout/soong/build.ninja 组合起来。 所以,使用Soong后,combined-*.ninja 是编译执行的真正入口。

4.3 out/soong/build.ninja 文件

它是从所有的Android.bp 转换过来的。

build-*.ninja 是从所有的 Makefile,用 Kati 转换过来的,包括 build/core/*.mk 和所有的Android.mk。 所以,在不使用Soong时,它是唯一入口。 在使用了 Soong 以后,会新增源于Android.bp的 out/soong/build.ninja,所以需要 combined-*.ninja 来组合一下。

可以通过以下命令,单独产生全编译的Ninja文件。

make nothing

文件组织如下图所示:(其中的aosp_arm为<product_name>)

5. 用Ninja 编译

在产生全编译的Ninja文件后,可以绕过Makefile,单独使用 ninja 进行编译。

全编译(7.0版本),相当于make:

ninja -f out/build-aosp_arm64.ninja

单独编译模块,比如Settings,相当于make Settings:

ninja -f out/build-aosp_arm64.ninja Settings

在8.0以上,上面的文件应该替换为out/combined-aosp_arm64.ninja,否则可能找不到某些Target。

另外,还有办法不用输入-f参数。 如前所述,如同Makefile之于make,ninja默认的编译文件是build.ninja。 所以,使用软链接,可以避免每次输入繁琐的-f。

ln -s out/combined-aosp_arm64.ninja build.ninja
ninja Settings

用ninja进行单模块编译的好处,除了更快以外,还不用生成单模块的Ninja文件,省了四五分钟。

9. 总结

在以Ninja在实际编译中替换 Makefile 以后,Android在编译时更快了一些。 不过,在首次生成、或重新生成Ninja文件时,往往额外耗时数分钟,反而比原先使用Makefile更慢了。

在增量编译方面,原先由于其 Makefile 编译系统的实现问题,是不完善的。 也就是说,在make编译完一个项目后,如果再执行make,会花费较长时间重新编译部分内容。 而使用Ninja以后,增量编译做得比较完善,第二次make将在一分钟内结束。

除此之外,由于Ninja的把编译流程集中到了一个文件,并且提供了一些工具命令。 所以编译信息的提取、编译依赖的分析,变得更加方便了。

参考:

Ninja, a small build system with a focus on speed

GitHub - ninja-build/ninja: a small build system with a focus on speed

相关文章:

理解 Android Build 系统

Android中Kati简介

Android中的Android.bp、Blueprint 和Soong简介

<think>好的,用户现在想查找关于Android Ninja的调试技巧教程。我需要结合之前的引用内容,特别是引用[1]和[2],来提供相关的信息。首先,用户提到的Android Ninja可能是指使用Ninja构建系统在Android开发中的调试技巧。 首先,回顾引用[1]中提到的Ninja使用技巧,比如生成Release构建、清理缓存、查看构建耗时、使用verbose模式、图形化依赖分析以及并行构建优化。这些内容对调试构建过程应该有帮助。例如,用户可以使用`ninja -d stats`来查看各个任务的耗时,找出构建过程中的瓶颈。此外,`ninja -v`可以显示完整的命令,帮助定位命令执行错误。 然后,用户还提到了Android的log使用技巧,包括Java层和Native层的日志打印方法。这部分可能和调试代码逻辑相关,但用户的问题是关于构建系统的调试,所以可能需要区分开。不过,如果构建过程中出现错误,可能需要结合日志来查看具体问题,比如在构建脚本中添加日志输出,或者查看Ninja输出的错误信息。 用户的需求可能包括如何快速定位Ninja构建过程中的错误,如何分析依赖关系,如何清理缓存强制重新构建,以及如何优化构建速度等。需要确保回答的结构清晰,分点列出不同的调试技巧,每个技巧都给出具体的命令和用途,同时结合引用中的示例,比如使用`ninja -t graph`生成依赖图,或者使用`ninja -j`设置并行线程数。 另外,用户可能遇到的具体问题,比如构建失败时的处理步骤,如何查看详细的构建日志,或者如何处理依赖问题。需要参考引用[1]中的常见问题部分,比如强制重新构建所有目标的方法,或者处理“missing separator”错误。此外,还可以建议用户结合CMake的选项,比如生成Release构建或指定工具链,以排除配置问题。 最后,需要生成相关问题,帮助用户进一步探索。比如如何分析Ninja的构建性能,或者如何集成NinjaAndroid Studio中。确保每个技巧都具体实用,并且命令正确,符合用户的实际使用场景。</think>以下是根据Android Ninja构建系统的调试技巧整理的专业解答: ### Android Ninja调试技巧详解 1. **构建日志分析** - 查看完整构建命令:`ninja -v` 可显示所有任务执行的完整命令行[^1] - 获取详细调试信息:`ninja -d explain` 显示任务跳过的原因 - 调试依赖关系:`ninja -d keepdepfile` 保留临时依赖文件 2. **性能分析工具** ```bash ninja -d stats # 输出构建耗时统计(包含任务执行时间分布) ninja -t commands # 查看待执行命令列表 ``` 输出示例: ``` [3/10] Building CXX object... ``` 3. **依赖可视化(需安装graphviz)** ```bash ninja -t graph | dot -Tpng > build_graph.png ``` ![构建依赖图示例](data:image/png;base64,...) 可清晰展示目标间依赖关系[^1] 4. **增量构建调试** - 强制重新构建:`ninja -t clean && ninja` - 检查文件修改时间:`ninja -d mtime` 显示文件时间戳信息 - 验证构建规则:`ninja -n -d explain` 模拟执行并显示决策逻辑 5. **Android特定调试** ```bash mmma -j16 path/to/module # 单独编译模块 mmm --show-commands path/to/module # 显示模块构建命令 ``` 6. **常见错误处理** - **"missing separator"**:检查ninja.build文件缩进是否使用tab - **"no rule to make target"**:使用`ninja -t query <target>` 查询目标定义位置 - **编译参数问题**:通过`ninja -v` 查看实际执行的完整编译命令 7. **高级调试技巧** ```bash NINJA_STATUS="[%f/%t] " ninja # 自定义进度显示格式 ninja -w dupbuild=err # 将重复构建警告升级为错误 ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

私房菜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值