Android build子系统(01)Ninja构建系统解读

**说明:**本文将解读Ninja构建系统,这是当前Android Framework中广泛使用的构建工具。我们将从Ninja的起源和背景信息开始,逐步解读Ninja的优势和核心原理,并探讨其一般使用场景。然后介绍其在Android Framework中的应用及相关工具:kati、soong、gn等,最后介绍下如何自行构建一个Ninja编译系统,以便于对Ninja有一个完整的了解。


1 Ninja基本内容解读

1.1 什么是Ninja?

Ninja是一个小型的、专注于速度的构建系统,最初由Google的程序员Chris Manson开发,最初用于加速Chrome浏览器的构建过程。Ninja的设计哲学是简化构建过程,通过精确指定输入和输出关系,实现快速增量构建。Ninja的首次使用是在开源的Chromium浏览器项目中,该项目拥有超过30,000个源文件,Ninja能够在不到一秒的时间内开始构建过程,相较于其他构建系统有显著的速度优势。

与Make相比,Ninja舍弃了各种高级功能来实现快速的增量编译。Make具有各种高级功能,比如函数、内置规则,而Ninja则专注于速度。Ninja的构建文件是可读的,但更多场合下,是由其他构建系统的工程文件自动生成的。

Ninja被用于构建Google Chrome、部分Android系统、LLVM等项目。由于CMake支持Ninja后端,CMake可以生成Ninja构建文件,从而利用Ninja的高效构建能力。

总之,Ninja是一个快速、轻量级的构建系统,专注于增量构建,常用于大型项目。

1.2 Ninja的核心原理解读

Ninja的核心原理基于构建文件中定义的规则和依赖关系,通过构建图(依赖图)来确定需要重新构建的目标。Ninja使用简单的文件时间戳比较来实现增量构建,避免了不必要的编译过程。总结下,它的核心原理主要包含以下几个方面:

  • 依赖图: Ninja构建过程基于一个明确的依赖图,这个依赖图定义了项目中所有文件的依赖关系。每个节点代表一个文件或一个构建命令,边代表依赖关系。Ninja在构建前会构建这个依赖图,并在构建时只执行那些受影响的节点。
  • 构建文件 : Ninja使用.ninja文件作为输入,这些文件包含了构建规则和目标。这些规则定义了如何从输入文件生成输出文件。.ninja文件通常由其他工具(如GN或CMake)生成。
  • 增量构建: 只有当输入文件发生变化时,Ninja才会重新构建目标。它通过比较文件的时间戳来确定哪些文件需要更新。
  • 并行构建: Ninja能够并行执行多个构建任务,以充分利用多核处理器的能力。它会智能地调度任务,以最大化并行度并减少构建时间。
  • 避免冗余: Ninja的设计避免了不必要的工作。例如,它不会在构建过程中重新扫描依赖关系,因为这些信息已经在构建文件中明确指定。
  • 简洁性 : Ninja的构建文件(.ninja文件)是简洁的,专注于构建逻辑,不包含条件逻辑或循环。这使得构建文件易于理解和维护。
  • 可靠性: Ninja在构建过程中会捕获错误并立即停止,这样可以避免无效的构建尝试。
  • 工具链无关性: Ninja本身不关心底层的编译器或工具链,它只负责调度构建任务。这使得Ninja可以与多种编译器和工具链一起使用。
  • 跨平台: Ninja可以在Windows、Linux和macOS等多种操作系统上运行,这使得它适用于跨平台项目。
  • 性能: Ninja的性能非常出色,尤其是在大型项目中。它能够快速地开始构建过程,并在构建过程中保持高效率。

Ninja的核心原理是提供一个简单、快速、可靠的构建系统,它通过优化构建过程和利用现代硬件的优势来实现这一目标。

1.3 Ninja相比于make的优势

Ninja 和 Make 都是构建系统,用于自动化编译和构建软件项目。Ninja 是在 Make 的基础上发展起来的,它旨在解决 Make 在某些方面的局限性,特别是在大型项目中的性能问题。以下是 Ninja 相比 Make 的一些优势:

  • 速度: Ninja 的主要优势是速度快。它在设计时就注重减少磁盘 I/O 和提高构建速度。Ninja 通过预先计算构建依赖关系,并在构建文件中明确指定,从而避免了 Make 在构建过程中重复扫描源代码文件的开销。
  • 并行构建 : Ninja 能够更有效地利用多核处理器进行并行构建。它默认就会并行执行构建任务,而 Make 需要显式地通过 -j 选项来指定并行构建的作业数。
  • 依赖关系: Ninja 的依赖关系更加明确和静态。它不依赖于文件的时间戳来确定是否需要重新构建,而是使用文件内容的哈希值,这减少了在构建过程中的不确定性和不必要的构建。
  • 构建文件 : Ninja 的构建文件(.ninja 文件)通常由其他工具(如 GN 或 CMake)生成,这使得构建文件的维护和管理更加一致和简单。而 Makefile 通常需要手工编写,容易出错且难以维护。
  • 简洁性: Ninja 的构建文件更加简洁,因为它避免了 Makefile 中常见的复杂逻辑和条件判断。这使得 Ninja 文件更容易理解和修改。
  • 可靠性: Ninja 在遇到错误时会立即停止构建,而不是尝试继续执行其他任务。这有助于更快地发现和解决问题。
  • 跨平台: Ninja 支持跨平台构建,可以在 Windows、Linux 和 macOS 上运行,而 Make 起源于 Unix 系统,虽然也有跨平台的支持,但在某些平台上可能需要额外的配置。
  • 工具链无关性: Ninja 不关心底层的编译器或工具链,它只负责调度构建任务。这使得 Ninja 可以与多种编译器和工具链一起使用,而 Make 可能需要为不同的编译器或工具链编写不同的 Makefile。
  • 一致性: Ninja 通过生成的构建文件来执行构建,这使得构建过程更加一致,不受环境变化的影响。而 Makefile 可能会受到当前 shell 环境的影响。
  • 性能: Ninja 在大型项目中的性能优势尤为明显,因为它能够更快地启动构建过程,并且在增量构建时更加高效。

总的来说,Ninja 通过优化构建过程和利用现代硬件的优势,提供了一种更快速、更可靠、更易于维护的构建解决方案。

1.4 Ninja的安装

ubuntu上可以直接安装:

bash 复制代码
$sudo apt install ninja-build

1.5 Ninja的一般使用场景

以下是 Ninja 的一般使用场景:

  • 跨平台构建:Ninja 支持在 Windows、Linux 和 macOS 等多种操作系统上运行,适用于跨平台项目构建。
  • 大型项目构建:Ninja 特别适合于大型项目,如 Chromium、LLVM 等,这些项目包含成千上万个源文件,Ninja 通过并行编译显著缩短构建时间。
  • 与现代构建系统配合:Ninja 常与 CMake 或 Meson 等现代构建系统配合使用,生成高效的构建文件。
  • 持续集成/持续部署(CI/CD):在 CI/CD 系统中,Ninja 的快速构建能力有助于缩短反馈循环时间,提高构建和测试的效率。
  • 需要快速迭代的场景:在开发过程中,如果需要频繁编译,Ninja 可以提供快速的反馈循环,使得开发者可以更快地进行代码迭代。
  • 自定义构建规则:Ninja 允许开发者自定义构建规则,适用于需要特殊构建逻辑的项目。
  • 与Android NDK配合:Android NDK 默认使用 Ninja 进行原生库的构建,因此在 Android 原生应用开发中,Ninja 是一个重要的工具。
  • Bazel 构建工具:Google 的 Bazel 构建工具虽然有自己的内部构建系统,但也可以配置为使用 Ninja 提高性能。

Ninja 的核心优势在于其构建速度和并行编译能力,这使得它成为许多大型和复杂项目的理想选择。

2 Ninja在Android Framework中的应用

Ninja适用于需要快速构建的大型项目,尤其是在C/C++代码编译方面表现出色。在Android Framework的构建中,Ninja主要用于编译原生代码,同时也支持Java/Kotlin代码的编译。

随着Android系统的不断演进,从Android 7.0(Nougat)开始引入了Soong构建系统,它使用Android.bp文件来定义构建规则,并生成Ninja文件,然后由Ninja执行实际的编译和链接任务。

2.1 为什么要引入ninja?

实际上在Android 7.0(Nougat)之前,Android系统主要使用Makefile和Android.mk文件来描述构建过程。这些文件定义了如何编译和链接模块,并通过调用make命令来执行构建任务。

随着Android系统和应用程序的增长,这种构建方式变得越来越慢,尤其是在大型项目中。为了解决这个问题,Google开始引入新的构建系统来提高编译速度和效率。

2.2 过渡期工具:Kati工具

在从Make过渡到Ninja的过程中,Google开发了Kati工具,用于将Android.mk文件转换为Ninja可以理解的构建文件。这样,现有的Android.mk文件可以被重用于新的构建系统,而不需要立即迁移到新的格式。

这里给出一个简单的kati工具使用的流程,便于更好地理解Kati工具:

假设你有一个简单的 Android.mk 文件,它定义了一个模块的编译规则,如下所示:

TypeScript 复制代码
include $(CLEAR_VARS)
LOCAL_MODULE := my_module
LOCAL_SRC_FILES := my_source.c
include $(BUILD_SHARED_LIBRARY)

这个 Android.mk 文件告诉构建系统如何编译一个共享库 my_module,它由 my_source.c 源文件构建而来。使用 Kati 转换这个过程如下:

bash 复制代码
$cd path/to/your/module
$ckati --ninja

这将生成一个 build.ninja 文件,内容类似于:

TypeScript 复制代码
rule cc
  command = gcc -c $cflags -o $out $in
  description = COMPILE

build my_module.o: cc my_source.c
build my_module: link my_module.o

然后,你可以使用 Ninja 来构建这个模块:

bash 复制代码
$ninja -f build.ninja

2.3 Soong工具构建系统引入

从Android 7.0(Nougat)开始,引入了Soong构建系统,它使用Android.bp文件来定义构建规则,并生成Ninja构建文件。在Android 8.0(Oreo)中,Google进一步引入了Android.bp文件和Soong构建系统。Android.bp文件是一种更简洁、更易于维护的构建脚本格式。Soong是一个新的构建引擎,它使用Android.bp文件来生成Ninja构建文件。

这里给出一个简单的kati工具使用的流程,便于更好地理解Soong工具:

假设你有一个简单的 Android.bp 文件,它定义了一个 C/C++ 库的构建规则,如下所示:

TypeScript 复制代码
cc_library_shared {
    name: "my_library",
    srcs: ["src/my_library.c"],
    shared_libs: ["liblog"],
    export_include_dirs: ["include"],
}

这个 Android.bp 文件告诉构建系统如何编译一个共享库 my_library,它由 src/my_library.c 源文件构建而来,并包含 liblog 库。

在 Android 构建环境中,通常不需要直接调用 Soong 命令,因为构建脚本会自动化这个过程。这里为了方便理解,使用手动方式触发 Soong 的构建过程,使用以下命令:

bash 复制代码
source build/envsetup.sh
lunch XXX-target
out/soong/.bootstrap/bin/soong_build --make-mode <target-moudle>

这个命令会执行 Soong,生成 out/soong/build.ninja 文件,然后 Ninja 会使用这个文件来编译项目,使用 Ninja 来构建这个模块:

bash 复制代码
$ninja -f build.ninja

2.4 GN工具的引入

GN(Generate Ninja)是一个由Google开发的元构建系统,它用于生成Ninja构建文件,这些文件随后由Ninja构建系统使用来编译项目。GN在Android系统中的使用是逐步引入的。

GN在Android系统中的引入最开始主要是为了改善Chromium项目的构建性能。Chromium是Google Chrome浏览器的开源项目,它有着庞大的代码库。GN的设计目标是减少构建时间,尤其是在大型项目中。GN通过并行构建和优化依赖关系来提高构建速度。

从Android 8.0(Oreo)开始,GN的使用更加广泛,并且随着Android版本的更新,GN和Ninja的集成逐渐深入到Android的构建系统中。GN的主要优势如下:

  • 速度:GN生成的Ninja文件能够快速执行构建任务,尤其是在大型项目中。
  • 可读性 :GN的构建文件(.gnBUILD.gn)比传统的Makefile更容易阅读和维护。
  • 跨平台:GN支持跨平台构建,可以在不同的操作系统上使用。

总的来说,GN的引入也是为了提高Android系统和Chromium等大型项目的构建效率。

gn的安装,可以从官网下载代码编译:

bash 复制代码
$git clone https://gn.googlesource.com/gn
$cd gn
$python build/gen.py
$ninja -C out

然后把二进制文件放到你的路径里即可。

这里给出一个简单的GN工具使用的流程,便于更好地理解GN工具:

假设你有一个简单的 C++ 项目,你需要编写一个 BUILD.gn 文件来告诉 GN 如何构建它。这个文件可能会包含如下内容:

TypeScript 复制代码
# 定义一个可执行文件目标
executable("my_app") {
  sources = [
    "main.cc",
    "utils.cc",
  ]
  deps = [
    "//third_party/some_library",
  ]
}

这个 BUILD.gn 文件定义了一个名为 my_app 的可执行文件,它依赖于 main.ccutils.cc 这两个源文件,以及一个名为 some_library 的第三方库。

在 Android 构建环境中,GN 的执行通常是自动的。这里为了方便理解,手动运行 GN,在项目根目录下运行以下命令:

bash 复制代码
$gn gen out/debug --dotfile=out/debug/gn_graph.dot

这个命令会生成一个名为 out/debug 的输出目录,生成一个 Ninja 构建文件。并创建一个名为 gn_graph.dot 的文件,该文件包含了构建图的 Graphviz 表示,用于可视化构建过程。

然后可以使用 Ninja 来构建这个模块:

bash 复制代码
$ninja -f build.ninja

2.5 详细解读kati soong gn与Ninja之间的关系

Kati、Soong、GN 和 Ninja 都是构建系统组件。在 Android 系统的编译过程中,这些工具通常按照以下流程工作:

  • GN 将BUILD.gn转换为 Ninja 文件。
  • KatiAndroid.mk 转换为 Ninja 文件。
  • Soong 解析 Android.bp 文件并生成 Ninja 文件。
  • Ninja 读取生成的 Ninja 文件,并执行构建任务。

总的来说,GN Kati Soong相当于cmake的角色,而Ninja相当于make的角色。同时GN 和 Ninja 是现代构建系统的工具,而 Kati 和 Soong 是 Android 在从旧的 Make 构建系统过渡到基于 Ninja 的构建系统过程中引入的组件。

作为一个高效、轻量级的构建工具,在Android Framework的构建过程中发挥着重要作用。了解Ninja的原理和优势,可以帮助开发者更好地优化构建过程,提高开发效率。接下来用一个最简单的例子,我们来熟悉一下Ninja的编译流程。

3 构建一个最简单Ninja编译系统

构建一个最简单的 Ninja 编译系统,你需要以下2个文件:一个 C/C++ 源文件、一个 Ninja 构建文件。以下是一个简单的 "Hello, World!" 程序的例子。

3.1 源代码文件(hello.c

cpp 复制代码
// hello.c
#include <stdio.h>

int main() {
    printf("Hello, World!\n");
    return 0;
}

3.2 Ninja 构建文件(build.ninja

TypeScript 复制代码
# 定义编译器
cflags = -Wall

# 定义构建规则
rule cc
  command = gcc $cflags -c $in -o $out
  description = Compiling $out

# 定义构建目标
build hello.o: cc hello.c
build hello: link hello.o
  command = gcc -o $out $in
  description = Linking $out

3.3 运行 Ninja 构建

首先,确保你已经安装了 Ninja。然后,在包含上述两个文件的目录中打开命令行,运行以下命令:

bash 复制代码
#默认路径
$ninja

这个命令会检查 build.ninja 文件中的指令,编译 hello.c 文件,并将其链接成可执行文件 hello。构建完成后,你可以运行生成的可执行文件:

bash 复制代码
$./hello
Hello, World!
相关推荐
百锦再33 分钟前
Android Studio开发 SharedPreferences 详解
android·ide·android studio
青春给了狗44 分钟前
Android 14 修改侧滑手势动画效果
android
CYRUS STUDIO1 小时前
Android APP 热修复原理
android·app·frida·hotfix·热修复
火柴就是我2 小时前
首次使用Android Studio时,http proxy,gradle问题解决
android
limingade2 小时前
手机打电话时电脑坐席同时收听对方说话并插入IVR预录声音片段
android·智能手机·电脑·蓝牙电话·电脑打电话
浩浩测试一下2 小时前
计算机网络中的DHCP是什么呀? 详情解答
android·网络·计算机网络·安全·web安全·网络安全·安全架构
青春给了狗4 小时前
Android 14 系统统一修改app启动时图标大小和圆角
android
pengyu4 小时前
【Flutter 状态管理 - 柒】 | InheritedWidget:藏在组件树里的"魔法"✨
android·flutter·dart
居然是阿宋6 小时前
Kotlin高阶函数 vs Lambda表达式:关键区别与协作关系
android·开发语言·kotlin
凉、介6 小时前
PCI 总线学习笔记(五)
android·linux·笔记·学习·pcie·pci