Gradle 非常灵活并且强大,以致于初学者很容易对其感到恐惧。但是,理解下面 5 个核心原则,会让 Gradle 变得更易于理解。
1. Gradle 是一个通用的构建工具
Gradle 可以用于构建(build)任何软件,因为它对你要构建的东西或构建方式几乎不做任何假设。不过当前它最大的限制是,只支持兼容 Maven 和 lvy 的仓库和文件系统。
这并不意味着你需要为构建做许多工作。Gradle 可以通过插件(plugins)添加一层约定(convention)以及预构建功能(prebuild functionality)来让常见的项目类型,例如 Java 库,更容易被构建。你甚至能将自己的约定和构建功能封装成插件来发布。
2. 核心模型基于 task
task 是 Gradle 的工作单元。Gradle 的构建模型就是一个 task 的定向无环图(Directed Acyclic Graphs, DAGs)。也就是说,构建本质上是在配置一个由 task 组成的定向无环图。task 之间根据它们的依赖关系相连。一旦 task 图被创建,Gradle 就能确定该以何种顺序执行 task。
这张图显示了两个 task 图的例子,一个是抽象的,一个是具体的,task 之间的依赖关系用箭头表示:
图 1. Gradle task 图的两个例子
几乎所有的构建过程都可以通过这种方式建模为一个 task 图,这也是 Gradle 灵活的原因之一。而且这个 task 图可以由插件和你的构建脚本来定义,并通过 task 依赖机制将 task 连接起来。
一个 task 包括:
-
动作(Actions)——执行某些工作。例如复制文件或者编译源码。
-
输入(Inputs)——给动作使用或操作的值、文件和目录
-
输出(Outputs)——由动作修改或生成的文件和目录
以上内容都是可选的,使用与否取决于实际需要。一些 task,比如标准生命周期 task(standard lifecycle tasks),甚至没有任何动作。它们只是将多个任务聚合在一起,以方便使用。
你可以选择你需要的 task 来运行。为了节约时间,请选择刚好能满足需要的 task。如果想运行单元测试,就选择执行单元测试的 task——通常是
test
。如果想打包一个应用,大多数构建都提供一个assemble
task 以供使用。
最后,Gradle 的增量构建支持强大而又可靠,所以为了保持构建的运行速度,请避免运行 clean
task,除非你确实想执行清理。
3. Gradle 的多个固定构建阶段
Gradle 会在三个阶段(phases)评估(evaluates)并执行(execute)构建脚本。理解这三个阶段非常重要。
-
初始化(Initialization)
设置构建的环境,并明确哪些项目将参与其中。
-
配置(Configuration)
构造并配置构建的 task 图。然后根据用户想要运行的 task,确定需要运行哪些任务,以及运行的顺序。
-
执行(Execution)
运行配置阶段结束时选择的 task。
这些阶段组成了 Gradle 的构建生命周期(Build Lifecycle)。
与 Apache Maven 术语的比较
Gradle 的“构建阶段”与 Maven 的“阶段”不同。Maven 的“阶段”将构建执行划分成了多个部分。它们的作用类似于 Gradle 的 task 图,尽管没有那么灵活。
Maven 的构建生命周期概念与 Gradle 的生命周期 task 大致相似。
设计良好的构建脚本主要由声明式配置组成,而非命令式逻辑。容易理解的是,这些配置在配置阶段就会被评估。但许多构建也有 task 动作(例如通过 doLast {}
和 doFirst {}
添加的),它们在执行阶段被评估。理解这一点非常重要,因为配置阶段评估的代码无法感知到执行阶段发生的变化。
配置阶段的另一个重要方面是,每当构建运行时,都会对其中涉及的一切进行评估。因此要避免在配置阶段做复杂的工作。除此之外,构建扫描(build scan)可以帮助你识别这样的热点。
4. Gradle 可以使用多种方式进行扩展
如果你能用 Gradle 内建的构建逻辑来构建你的项目,那再好不过了。然而事实往往没有这么顺利。大多数构建都有一些特殊的要求,这就要求你能添加自定义构建逻辑。
Gradle 提供了多种机制来进行扩展,比如:
-
自定义 task 类型
当你想让构建做一些现有 task 不能做的工作时,你可以简单地编写自己的 task 类型。通常最好把自定义 task 类型的源文件放在 buildSrc 目录或打包的插件中。然后你就可以像使用任何 Gradle 内建的 task 类型一样,使用这个自定义 task 类型。
-
自定义 task 动作
你可以通过
Task.doFirst()
和Task.doLast()
方法将自定义构建逻辑附加在 task 之前或之后执行。 -
项目和 task 的额外属性
你可以将自定义属性添加到项目或 task 中,并在自定义动作或任何其他构建逻辑中使用。额外的属性甚至能被应用到那些不是由你明确创建的 task 上,比如由 Gradle 核心插件创建的 task。
-
自定义约定
约定是简化构建的有力方法,它可以让用户更容易理解和使用。这可以从标准项目结构和命名约定中看出,比如 Java 构建。你可以编写你自己的插件来提供约定,它们只需要为构建的相关方面配置默认值。
-
自定义模型
Gradle 允许你在构建中引入除了 task、文件、依赖配置之外的新概念。你可以在大多数语言插件中看到这一点,它们将源集(source sets)的概念添加到了构建之中。对构建过程进行适当的建模可以大大提高构建的易用性和效率。
5. 用构建脚本操作 API
Gradle 的构建脚本看起来像可执行代码,实际上它的确是。这里有一个实现细节:设计良好的构建脚本描述了构建软件需要哪些(what)步骤,而不是这些步骤应该如何(how)完成工作。那是自定义任务类型和插件的工作。
有一个普遍的误解,认为 Gradle 的强大和灵活来自于它的构建脚本是代码这一事实。这个观点完全错误。实际上那是底层模型和 API 提供的力量。正如我们在最佳实践中所建议的那样,你应该避免在你的构建脚本中放入过多的命令式逻辑。
然而,有一个领域,“将构建脚本视为可执行代码”在此领域是很有用的,即:理解构建脚本的语法如何映射到 Gradle 的 API。API 文档(由 Groovy DSL 参考和 Javadocs 组成)中列出了方法、属性并题及了闭包和动作。它们在构建脚本的上下文中有什么含义?请阅读 Groovy 构建脚本入门来获得这个问题的答案。这能帮助你有效地使用 API 文档。
由于 Gradle 运行在 JVM 上,构建脚本也可以使用标准的 Java API。Groovy 构建脚本可以额外使用 Groovy API,而 Kotlin 构建脚本可以使用 Kotlin 的。
本文于2020年6月30日由笔者翻译自 Gradle 官方文档:《What is Gradle? 》文章的 Five things you need to know about Gradle 部分。
本文使用与原文相同的知识共享署名-非商业性使用-相同方式共享 4.0 国际协议 (CC BY-NC-SA 4.0) 进行许可。