Gradle 工作原理第二部分 - 深入守护进程
之前在Gradle 工作原理中
这是Gradle 工作原理系列的第二篇博客。在本篇博客中,我们将解释Gradle Daemon JVM
内部发生了什么。
为什么我们需要 Gradle 守护进程?
在上一篇博客中,我们提到 Gradle 启动了一个Gradle Daemon JVM
(“守护进程”)来运行构建。用户指南解释了为什么我们需要守护进程。
守护进程是一个长生命周期的后台进程,它可以减少运行构建所需的时间。守护进程通过以下方式减少构建时间:
跨构建缓存项目信息
在后台运行,这样每次 Gradle 构建就不必等待 JVM 启动。
从 JVM 的持续运行时优化中获益。
监视文件系统,以计算在运行构建之前需要重建的内容。
Gradle 守护进程是在 Gradle 3.0 中引入的,并且多年来已经成熟。它默认启用,我们不建议在任何情况下禁用它。
守护进程中发生了什么?
在 Gradle Client JVM
(“客户端”)连接到兼容的空闲守护进程后,它会将必要的构建信息(命令行参数、项目目录、环境变量等)发送到守护进程。然后,守护进程开始运行构建并将构建输出(日志记录、stdout/stderr 等)发送回客户端。通信通过本地套接字连接进行。
但是守护进程内部到底发生了什么?用户指南 解释说 Gradle 构建中有三个阶段:初始化
、配置
和 执行
。
初始化阶段:创建构建对象
现在守护进程已经了解了构建的所有信息,它开始为构建创建内部表示。由于 Gradle 在 JVM 上运行,因此这些表示是 Java 对象。
例如,整个 Gradle 构建调用由 Gradle 实例表示。配置项目层次结构所需的配置由 Settings 实例表示。每个我们要构建的项目都有一个 Project 实例。
Gradle
、Settings
和 Project
也是 init、settings 和 build 脚本的默认委托。这意味着这些对象以后可以在构建脚本中进行交互。例如,当我们在构建脚本中说 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 Client JVM
会断开与守护进程的连接并退出。守护进程现在已准备好接受下一个构建调用。
下一步
在 本系列的下一篇博文中,我们将解释构建脚本执行的幕后情况。