Android Ninja实践

如果有自己编译过AOSP的源码,可能大家都会遇到这样的一个问题:

1
ninja: error: '*******', needed by '*********', missing and no known rule to make it

在刚刚开始接触AOSP的时候,我基本上都会直奔主题,大概清楚问题后,然后解决问题、重新编译,所以一直都没有注意到一个关键字:”Ninja”,今天我们就一起来学习/实践一下这个”熟悉的陌生人”,主要介绍一下目前Android的编译系统,以及以一个小Demo为例,实践一下Ninja的使用,如果想了解更多关于Ninja在AOSP中的使用,见参考链接。

1、Android编译系统

早期的Android系统采用Android.mk的配置来编译源码,从Android 7.0开始引入Android.bp。大体梳理一下Android版本相应的发展演变过程:

  • Android 7.0引入ninja和kati
  • Android 8.0使用Android.bp来替换Android.mk,引入Soong
  • Android 9.0强制使用Android.bp

Android Build

上图就是各个文件之间的转化关系,这里涉及到Ninja, kati, Soong, bp概念,简单介绍一下:

1. Ninja

ninja是一个编译框架,会根据相应的ninja格式的配置文件进行编译,但是ninja文件一般不会手动修改,而是通过将Android.bp文件转换成ninja格文件来编译。

2. Android.bp

Android.bp的出现就是为了替换Android.mk文件。bp跟mk文件不同,它是纯粹的配置,没有分支、循环等流程控制,不能做算数逻辑运算。如果需要控制逻辑,那么只能通过Go语言编写。

3. Soong

Soong类似于之前的Makefile编译系统的核心,负责提供Android.bp语义解析,并将之转换成Ninja文件。Soong还会编译生成一个androidmk命令,用于将Android.mk文件转换为Android.bp文件,不过这个转换功能仅限于没有分支、循环等流程控制的Android.mk才有效。

4. Blueprint

Blueprint是生成、解析Android.bp的工具,是Soong的一部分。Soong负责Android编译而设计的工具,而Blueprint只是解析文件格式,Soong解析内容的具体含义。Blueprint和Soong都是由Golang写的项目,从Android 7.0,prebuilts/go/目录下新增Golang所需的运行环境,在编译时使用。

5. Kati

kati是专为Android开发的一个基于Golang和C++的工具,主要功能是把Android中的Android.mk文件转换成Ninja文件。代码路径是build/kati/,编译后的产物是ckati。

2、Ninja的介绍

从上面我们可以看到,Android最后的编译工作都会交给Ninja,那么它到底是个什么东西呢,为什么Google需要在Android引入Ninja呢?

Ninja is a small build system with a focus on speed. It differs from other build systems in two major respects: it is designed to have its input files generated by a higher-level build system, and it is designed to run builds as fast as possible.

Ninja是一个专注于速度的构建系统,和其他构建系统相比,主要有两点不同:

  • Ninja的输入文件一般都是有更高级的构建系统产生的,比如cmake;
  • Ninja设计之初就是为了更快的构建;

其实从第一点,我们就能看出来Ninja的设计哲学:相比Makefile是设计出来给人手写的,但是Ninja设计出来是给其它程序生成的。 如果说Makefile是C语言,那么Ninja就是汇编语言。 如果说Makefile是一个DSL,那么Ninja就是一种配置文件。 Makefile支持分支、循环等流程控制,而Ninja只支持一些固定形式的配置。

3、Ninja的实践

工欲善其事,必先利其器,所以我们需要先准备好相关工具:CMake,ninja,以Mac OS为例,安装步骤为:

1
2
brew install cmake
brew install ninja

正式开始,以一个最简单的Hello,Ninja为例:

  • main.cpp
1
2
3
4
5
#include <iostream>
int main() {
std::cout << "Hello, Ninja!" << std::endl;
return 0;
}
  • CMakeLists.txt
1
2
3
4
5
6
cmake_minimum_required(VERSION 3.13)
project(ninja)

set(CMAKE_CXX_STANDARD 14)

add_executable(ninja main.cpp)

OK,一个简单的Hello,Ninja的C++工程已经完成,接下来我们开始编译,使用如下命令,生成build.ninja文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[min@bogon:] ninja $ cmake -G Ninja -B build .
-- The C compiler identification is AppleClang 9.1.0.9020039
-- The CXX compiler identification is AppleClang 9.1.0.9020039
-- Check for working C compiler: /Library/Developer/CommandLineTools/usr/bin/cc
-- Check for working C compiler: /Library/Developer/CommandLineTools/usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /Library/Developer/CommandLineTools/usr/bin/c++
-- Check for working CXX compiler: /Library/Developer/CommandLineTools/usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /Users/min/Desktop/workspace/c/ninja/build

可以看到在build目录下,生成了如下文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
[min@bogon:] build $ tree  -L 2
.
├── CMakeCache.txt
├── CMakeFiles
│   ├── 3.15.5
│   ├── CMakeOutput.log
│   ├── CMakeTmp
│   ├── TargetDirectories.txt
│   ├── cmake.check_cache
│   └── ninja.dir
├── build.ninja
├── cmake_install.cmake
└── rules.ninja

其中最重要的就是这个build.ninja文件,接下来,我们使用Ninja命令,开始编译:

1
2
3
4
5
[min@bogon:] ninja $ cd build/
[min@bogon:] build $ ninja
[2/2] Linking CXX executable ninja
[min@bogon:] build $ ./ninja
Hello, Ninja!

至此,一个简单的Hello, Ninja!就已经实践完成了,具体关于AOSP中Ninja的编译,希望大家可以自己去摸索一下~

3、参考