Gradle 工作原理第 2 部分 - 深入探究守护进程

目录

引言

《Gradle 工作原理》系列前文回顾

  1. Gradle 工作原理第 1 部分 - 启动

这是《Gradle 工作原理》系列的第二篇博客。在本篇博客中,我们将解释 Gradle 守护进程 JVM 内部发生了什么。

为什么我们需要 Gradle 守护进程? #

上一篇博客中,我们提到 Gradle 启动了一个 Gradle 守护进程 JVM(“守护进程”)来运行构建。用户指南解释了为什么我们需要守护进程。

守护进程是一个长时间运行的后台进程,可以减少运行构建所需的时间。守护进程通过以下方式减少构建时间:

  • 在构建之间缓存项目信息

  • 在后台运行,因此每个 Gradle 构建不必等待 JVM 启动

  • 受益于 JVM 中的持续运行时优化

  • 监视文件系统以在运行构建之前精确计算需要重建的内容

Gradle 守护进程在 Gradle 3.0 中引入,并经过多年发展变得成熟。它默认启用,我们不建议在任何情况下禁用它。

守护进程中发生了什么? #

Gradle 客户端 JVM(“客户端”)连接到兼容的空闲守护进程后,它将必要的构建信息(命令行参数、项目目录、环境变量等)发送到守护进程。然后守护进程开始运行构建并将构建输出(日志、标准输出/标准错误等)发送回客户端。通信通过本地套接字连接进行。

但是守护进程内部究竟发生了什么?用户指南解释了 Gradle 构建有三个阶段:初始化配置执行

初始化阶段:构建对象的创建 #

现在守护进程了解了构建的所有信息,它开始为构建创建内部表示。因为 Gradle 运行在 JVM 上,所以这些表示是 Java 对象。

例如,整个 Gradle 构建调用由一个 Gradle 实例表示。配置项目层次结构所需的配置由一个 Settings 实例表示。我们尝试构建的每个项目都有一个 Project 实例对应。

GradleSettingsProject 也是 init、settings 和构建脚本的默认委托。这意味着这些对象稍后可以在构建脚本中进行交互。例如,当我们在构建脚本中说 println(name) 时,我们实际上是在 Project 实例上调用 Project.getName() 方法

配置阶段:构建脚本执行 #

创建必要的 JVM 对象后,Gradle 将在守护进程中加载并执行构建脚本。构建脚本通常在项目目录中命名为 X.gradle (Groovy DSL) 或 X.gradle.kts (Kotlin DSL)。Groovy 和 Kotlin 都是 JVM 语言,这意味着它们可以在 JVM(即守护进程 JVM)中无缝运行。

例如,以下 Groovy 构建脚本创建一个 Groovy Closure 实例,并将该 Closure 实例传递给在先前的初始化阶段创建的 Project 实例上的 Project.repositories(Closure) 方法

repositories {
    mavenCentral()
}

如果您在此阶段未能完全理解构建脚本的执行,请不要担心。我们将在本系列的下一篇博客中解释构建脚本执行的详细信息。目前,我们可以简单地将 Gradle 理解为一个解释器,它逐行、从上到下执行构建脚本。

构建脚本在守护进程 JVM 内部填充构建的数据结构。例如,以下构建脚本片段将一个 hello 任务注册到 Gradle 的任务容器数据结构(即 TaskContainer)中,这意味着在需要时将创建一个 Task 实例。这个过程通常被称为“配置”,即配置数据结构;这就是为什么这个阶段被称为“配置阶段”。

tasks.register("hello") {
    doLast {
        println("Hello world!")
    }
}

构建脚本执行完成后,构建数据结构将配置好构建所需的数据。现在我们已准备好进入下一个阶段:选择一些任务并执行它们。

执行阶段:执行选定的任务 #

配置阶段之后,Gradle 将构建所需的所有数据存储在守护进程 JVM 中。然后,它根据传递给 gradle 命令的参数确定要执行的任务子集,并执行每个选定的任务。

每个 Task 都有一个由要执行的代码块组成的动作列表。例如,如果你想知道 Test 任务做了什么,只需在源代码中搜索 @TaskAction,你会发现

class Test {
    ...

    @TaskAction
    public void executeTests() {
        ...
    }
}

当我们说“一个任务被执行”时,我们的意思是“其动作中的代码在守护进程 JVM 中被执行”。任务动作总是在守护进程 JVM 中执行,但这些动作可以决定分叉一些新的 JVM 并在分叉的 JVM 中运行一些代码。

例如,Gradle Worker API 提供了一种将任务动作的执行分解成多个部分,然后在子进程中执行它们的方法。

另一个自定义任务操作分叉额外 JVM 的例子是 Test 任务Test 任务操作在守护进程 JVM 中执行,但在执行期间,它会分叉几个 JVM 并在分叉的 JVM 中运行测试代码,以避免测试代码干扰守护进程 JVM。

在构建结束时,守护进程会做一些额外的事情,例如执行回调、报告错误(如果有)、发布构建扫描等。之后,Gradle 客户端 JVM 断开与守护进程的连接并退出。守护进程现在已准备好进行下一次构建调用。

下一步 #

在本系列的下一篇博客中,我们将解释构建脚本执行的幕后发生的事情。

讨论