脚本基本知识

本章向您介绍编写Gradle构建脚本的基本知识。要获得快速的实践介绍,请尝试 创建新的Gradle构建指南

项目与任务

Gradle的一切都基于两个基本概念:项目和任务。

每个 Gradle 构建都由一个或多个子项目组成。项目代表什么取决于你想让Gradle做什么。例如,项目可能代表JAR库或web应用程序。它可能代表由其他项目生成的jar组装而成的发行版ZIP。项目不一定代表要构建的东西。它可能表示要做的事情,例如将应用程序部署到登台或生产环境。如果这看起来有点模糊,不要担心。Gradle的按惯例构建支持为项目添加了一个更具体的定义。

每个项目由一个或多个任务组成。任务表示构建执行的某个原子工作片段。这可能是编译一些类、创建一个JAR、生成 Javadoc 或将一些存档发布到存储库。

现在,我们将着眼于在一个项目的构建中定义一些简单的任务。后面的章节将介绍如何处理多个项目,以及如何处理项目和任务。

Hello world (世界你好)

您可以使用 gradle 命令运行Gradle构建,gradle 命令在当前目录中查找一个名为 build.gradle 的文件,我们将 build.gradle 称为构建脚本,尽管严格地说它是一个构建配置脚本,我们稍后将看到构建脚本中定义了一个项目及其任务。

为此,可以根据下面内容创建名为 build.gradle 的构建脚本。

示例1. 您的第一个构建脚本

build.gradle

  1. task hello {
  2. doLast {
  3. println 'Hello world!'
  4. }
  5. }

build.gradle.kts

  1. task("hello") {
  2. doLast {
  3. println("Hello world!")
  4. }
  5. }

在命令行shell中(或 cmd),进入到 build.gradle 的目录并使用 gradle -q hello 执行构建脚本:

-q 是做什么的?
本用户指南中的大多数示例都使用 -q 命令行选项运行。这会关闭 Gradle 的日志消息,以便只显示任务的输出。这使得本用户指南中的示例输出更加清晰。如果你需要看详细的日志信息,去掉本参数即可。有关影响Gradle 输出的命令行选项的更多细节,请参见 日志

示例2. 构建脚本的执行

输出结果来自命令:gradle -q hello

  1. > gradle -q hello
  2. Hello world!

这是怎么回事?这个构建脚本定义了一个名为 hello 的任务,并向其添加了一个操作。当您运行 gradle hello 时,gradle 执行 hello 任务,该任务反过来执行您提供的操作。该操作只是一个包含要执行的代码的块。

如果您认为这看起来与 Ant 的 target 相似,那么你是正确的。Gradle任务就相当于 Ant 的 target,但是却要强大得多。我们使用了与 Ant 不同的术语,因为我们认为单词 task 比单词 target 更具表现力。不幸的是,这引入了与 Ant 冲突的术语,因为在 Ant 中会把它的命令 (如javac或copy)叫做任务。所以当本文说到任务时,总是指的都是是 Gradle 任务,它相当于 Ant target 目标。如果我们提到 Ant 任务 (Ant命令),将会明确地说 Ant 任务

构建脚本也是可执行代码

Gradle 的构建脚本提供了 Groovy 和 Kotlin 的全部功能支持。作为开胃菜,看看这个:

示例3. 在 Gradle 的任务中使用 Groovy 或 Kotlin

build.gradle

  1. task upper {
  2. doLast {
  3. String someString = 'mY_nAmE'
  4. println "Original: $someString"
  5. println "Upper case: ${someString.toUpperCase()}"
  6. }
  7. }

build.gradle.kts

  1. task("upper") {
  2. doLast {
  3. val someString = "mY_nAmE"
  4. println("Original: $someString")
  5. println("Upper case: ${someString.toUpperCase()}")
  6. }
  7. }

输出结果来自命令:gradle -q upper

  1. > gradle -q upper
  2. Original: mY_nAmE
  3. Upper case: MY_NAME

或者

build.gradle

  1. task count {
  2. doLast {
  3. 4.times { print "$it " }
  4. }
  5. }

build.gradle.kts

  1. task("count") {
  2. doLast {
  3. repeat(4) { print("$it ") }
  4. }
  5. }

输出结果来自命令:gradle -q count

  1. > gradle -q count
  2. 0 1 2 3

任务依赖

正如您可能已经猜到的,您可以声明依赖于其他任务的任务。

build.gradle

  1. task hello {
  2. doLast {
  3. println 'Hello world!'
  4. }
  5. }
  6. task intro {
  7. dependsOn hello
  8. doLast {
  9. println "I'm Gradle"
  10. }
  11. }

build.gradle.kts

  1. task("hello") {
  2. doLast {
  3. println("Hello world!")
  4. }
  5. }
  6. task("intro") {
  7. dependsOn("hello")
  8. doLast {
  9. println("I'm Gradle")
  10. }
  11. }

输出结果来自命令:gradle -q intro

  1. > gradle -q intro
  2. Hello world!
  3. I'm Gradle

要添加依赖任务的时候,不要求当前存在的任务,也可以是后面添加的任务。

示例6. 惰性依赖 —— 另一个任务不存在

build.gradle

  1. task taskX {
  2. dependsOn 'taskY'
  3. doLast {
  4. println 'taskX'
  5. }
  6. }
  7. task taskY {
  8. doLast {
  9. println 'taskY'
  10. }
  11. }

build.gradle.kts

  1. task("taskX") {
  2. dependsOn("taskY")
  3. doLast {
  4. println("taskX")
  5. }
  6. }
  7. task("taskY") {
  8. doLast {
  9. println("taskY")
  10. }
  11. }

输出结果来自命令:gradle -q taskX

  1. > gradle -q taskX
  2. taskY
  3. taskX

taskX 依赖 taskY 的时候,taskY 还没有声明定义。这个在多项目构建中非常重要。关于任务依赖更多详细信息请参考 给任务添加依赖

不过需要注意,在引用尚未定义的任务时,不能使用 快捷表示法

动态任务

Groovy 或 Kotlin 的强大功能不仅可以用于定义任务的功能。例如,您还可以使用它来动态创建任务。

示例7. 任务的动态创建

build.gradle

  1. 4.times { counter ->
  2. task "task$counter" {
  3. doLast {
  4. println "I'm task number $counter"
  5. }
  6. }
  7. }

build.gradle.kts

  1. repeat(4) { counter ->
  2. task("task$counter") {
  3. doLast {
  4. println("I'm task number $counter")
  5. }
  6. }
  7. }

输出结果来自命令:gradle -q task1

  1. > gradle -q task1
  2. I'm task number 1

操作任务

一旦创建了任务,就可以通过API访问它们。例如,您可以使用它在运行时动态地向任务添加依赖项。而在 Ant 中不支持这样的操作。

示例8. 通过 API 访问任务 —— 添加依赖项

build.gradle

  1. 4.times { counter ->
  2. task "task$counter" {
  3. doLast {
  4. println "I'm task number $counter"
  5. }
  6. }
  7. }
  8. task0.dependsOn task2, task3

build.gradle.kts

  1. repeat(4) { counter ->
  2. task("task$counter") {
  3. doLast {
  4. println("I'm task number $counter")
  5. }
  6. }
  7. }
  8. tasks["task0"].dependsOn("task2", "task3")

输出结果来自命令:gradle -q task0

  1. > gradle -q task0
  2. I'm task number 2
  3. I'm task number 3
  4. I'm task number 0

你还可以向现有任务添加行为。

示例9. 通过 API 添加行为访问任务

build.gradle

  1. task hello {
  2. doLast {
  3. println 'Hello Earth'
  4. }
  5. }
  6. hello.doFirst {
  7. println 'Hello Venus'
  8. }
  9. hello.doLast {
  10. println 'Hello Mars'
  11. }
  12. hello {
  13. doLast {
  14. println 'Hello Jupiter'
  15. }
  16. }

build.gradle.kts

  1. val hello = task("hello") {
  2. doLast {
  3. println("Hello Earth")
  4. }
  5. }
  6. hello.doFirst {
  7. println("Hello Venus")
  8. }
  9. hello.doLast {
  10. println("Hello Mars")
  11. }
  12. hello.apply {
  13. doLast {
  14. println("Hello Jupiter")
  15. }
  16. }

输出结果来自命令:gradle -q hello

  1. > gradle -q hello
  2. Hello Venus
  3. Hello Earth
  4. Hello Mars
  5. Hello Jupiter

doFirstdoLast 可以调用多次。它们将一个操作添加到任务操作列表的开头或结尾。当任务执行时,将按顺序执行操作列表中的操作。

快捷符号

访问现有任务有一种快捷的表示法:Groovy DSL。每个任务都可以作为构建脚本的属性使用:

示例10. 将任务作为构建脚本的属性访问

build.gradle

  1. task hello {
  2. doLast {
  3. println 'Hello world!'
  4. }
  5. }
  6. hello.doLast {
  7. println "Greetings from the $hello.name task."
  8. }

输出结果来自命令:gradle -q hello

  1. > gradle -q hello
  2. Hello world!
  3. Greetings from the hello task.

这大大提高了代码的可读性,特别是在使用插件提供的任务时,比如 compile 任务。

任务额外属性

您可以将自己的属性添加到任务中。要添加名为 myProperty 的属性需要给 ext.myProperty 初始值。然后就可以像读取和设置预定义的任务属性一样读取和设置属性。

示例11. 向任务添加额外属性

build.gradle

  1. task myTask {
  2. ext.myProperty = "myValue"
  3. }
  4. task printTaskProperties {
  5. doLast {
  6. println myTask.myProperty
  7. }
  8. }

build.gradle.kts

  1. task("myTask") {
  2. extra["myProperty"] = "myValue"
  3. }
  4. task("printTaskProperties") {
  5. doLast {
  6. println(tasks["myTask"].extra["myProperty"])
  7. }
  8. }

输出结果来自命令:gradle -q printTaskProperties

  1. > gradle -q printTaskProperties
  2. myValue

额外的属性并不仅限于任务使用。您可以在 额外属性 中阅读更多关于它们的信息。

使用Ant任务

Gradle 中内嵌了 Ant 完整的子系统 (Ant tasks are first-class citizens in Gradle)。Gradle 可以在 Groovy 的基础上通过简单地依赖配置方便的集成 Ant 任务。Groovy 附带了出色的 AntBuilder 。使用Gradle 中的 Ant 任务与使用 build.xml 文件中的 Ant 任务一样方便和强大。Kotlin也可以使用。从下面的例子中,您可以学习如何执行 Ant 任务以及如何访问 Ant 属性:

示例12. 使用 AntBuilder 执行 ant.loadfile target

build.gradle

  1. task loadfile {
  2. doLast {
  3. def files = file('./antLoadfileResources').listFiles().sort()
  4. files.each { File file ->
  5. if (file.isFile()) {
  6. ant.loadfile(srcFile: file, property: file.name)
  7. println " *** $file.name ***"
  8. println "${ant.properties[file.name]}"
  9. }
  10. }
  11. }
  12. }

build.gradle.kts

  1. task("loadfile") {
  2. doLast {
  3. val files = file("./antLoadfileResources").listFiles().sorted()
  4. files.forEach { file ->
  5. if (file.isFile) {
  6. ant.withGroovyBuilder {
  7. "loadfile"("srcFile" to file, "property" to file.name)
  8. }
  9. println(" *** ${file.name} ***")
  10. println("${ant.properties[file.name]}")
  11. }
  12. }
  13. }
  14. }

输出结果来自命令:gradle -q loadfile

  1. > gradle -q loadfile
  2. *** agile.manifesto.txt ***
  3. Individuals and interactions over processes and tools
  4. Working software over comprehensive documentation
  5. Customer collaboration over contract negotiation
  6. Responding to change over following a plan
  7. *** gradle.manifesto.txt ***
  8. Make the impossible possible, make the possible easy and make the easy elegant.
  9. (inspired by Moshe Feldenkrais)

在构建脚本中,您可以使用 Ant 做更多的事情。你可以在 Ant文档 中找到更多信息。

方法提取

Gradle 在如何组织构建逻辑方面具有伸缩性。对于上述示例,组织构建逻辑的最需要的优化是提取方法。

示例13. 方法提取组织构建逻辑

build.gradle

  1. task checksum {
  2. doLast {
  3. fileList('./antLoadfileResources').each { File file ->
  4. ant.checksum(file: file, property: "cs_$file.name")
  5. println "$file.name Checksum: ${ant.properties["cs_$file.name"]}"
  6. }
  7. }
  8. }
  9. task loadfile {
  10. doLast {
  11. fileList('./antLoadfileResources').each { File file ->
  12. ant.loadfile(srcFile: file, property: file.name)
  13. println "I'm fond of $file.name"
  14. }
  15. }
  16. }
  17. File[] fileList(String dir) {
  18. file(dir).listFiles({file -> file.isFile() } as FileFilter).sort()
  19. }

build.gradle.kts

  1. task("checksum") {
  2. doLast {
  3. fileList("./antLoadfileResources").forEach { file ->
  4. ant.withGroovyBuilder {
  5. "checksum"("file" to file, "property" to "cs_${file.name}")
  6. }
  7. println("$file.name Checksum: ${ant.properties["cs_${file.name}"]}")
  8. }
  9. }
  10. }
  11. task("loadfile") {
  12. doLast {
  13. fileList("./antLoadfileResources").forEach { file ->
  14. ant.withGroovyBuilder {
  15. "loadfile"("srcFile" to file, "property" to file.name)
  16. }
  17. println("I'm fond of ${file.name}")
  18. }
  19. }
  20. }
  21. fun fileList(dir: String): List<File> =
  22. file(dir).listFiles { file: File -> file.isFile }.sorted()

输出结果来自命令:gradle -q loadfile

  1. > gradle -q loadfile
  2. I'm fond of agile.manifesto.txt
  3. I'm fond of gradle.manifesto.txt

稍后您将看到这些方法可以在多项目构建中的子项目之间共享。如果您的构建逻辑变得更加复杂,Gradle将为您提供其他非常方便的方式来组织它。我们用了整整一章来讨论这个问题。参见 组织Gradle项目

默认任务

Gradle允许您定义一个或多个默认任务,如果没有指定其他任务,将执行这些任务。

示例14. 定义默认任务

build.gradle

  1. defaultTasks 'clean', 'run'
  2. task clean {
  3. doLast {
  4. println 'Default Cleaning!'
  5. }
  6. }
  7. task run {
  8. doLast {
  9. println 'Default Running!'
  10. }
  11. }
  12. task other {
  13. doLast {
  14. println "I'm not a default task!"
  15. }
  16. }

build.gradle.kts

  1. defaultTasks("clean", "run")
  2. task("clean") {
  3. doLast {
  4. println("Default Cleaning!")
  5. }
  6. }
  7. task("run") {
  8. doLast {
  9. println("Default Running!")
  10. }
  11. }
  12. task("other") {
  13. doLast {
  14. println("I'm not a default task!")
  15. }
  16. }

输出结果来自命令:gradle -q

  1. > gradle -q
  2. Default Cleaning!
  3. Default Running!

这相当于运行 gradle clean run 。在多项目构建中,每个子项目都可以有自己特定的默认任务。如果子项目没有指定默认任务,则使用父项目的默认任务(如果定义了)。

使用DAG配置

Gradle有一个配置阶段和一个执行阶段(详细查看 构建周期)。在配置阶段之后,Gradle 会知道应该执行哪些任务。Gradle 提供了一个钩子来利用这些信息。用例之一是检查发布任务是否在要执行的任务中。根据这一点,您可以为某些变量分配不同的值。

在下面的示例中,distributionrelease 任务的执行将导致 version 变量有不同的值。

15例. 构建的不同结果取决于所选择的任务

build.gradle

  1. task distribution {
  2. doLast {
  3. println "We build the zip with version=$version"
  4. }
  5. }
  6. task release {
  7. dependsOn 'distribution'
  8. doLast {
  9. println 'We release now'
  10. }
  11. }
  12. gradle.taskGraph.whenReady { taskGraph ->
  13. if (taskGraph.hasTask(":release")) {
  14. version = '1.0'
  15. } else {
  16. version = '1.0-SNAPSHOT'
  17. }
  18. }

build.gradle.kts

  1. task("distribution") {
  2. doLast {
  3. println("We build the zip with version=$version")
  4. }
  5. }
  6. task("release") {
  7. dependsOn("distribution")
  8. doLast {
  9. println("We release now")
  10. }
  11. }
  12. gradle.taskGraph.whenReady {
  13. version =
  14. if (hasTask(":release")) "1.0"
  15. else "1.0-SNAPSHOT"
  16. }

输出结果来自命令:gradle -q distribution

  1. > gradle -q distribution
  2. We build the zip with version=1.0-SNAPSHOT

输出结果来自命令:gradle -q release

  1. > gradle -q release
  2. We build the zip with version=1.0
  3. We release now

重要的是 whenReady 会在执行 release 任务之前影响发布任务。即使 release 任务不是主要任务。

构建脚本的外部依赖项

如果构建脚本需要使用外部库,可以将它们添加到构建脚本中的脚本 classpath 中。您可以使用 buildscript() 传入一个声明构建脚本classpath 的代码块。

示例16. 为构建脚本声明外部依赖关系

build.gradle

  1. buildscript {
  2. repositories {
  3. mavenCentral()
  4. }
  5. dependencies {
  6. classpath group: 'commons-codec', name: 'commons-codec', version: '1.2'
  7. }
  8. }

build.gradle.kts

  1. buildscript {
  2. repositories {
  3. mavenCentral()
  4. }
  5. dependencies {
  6. "classpath"(group = "commons-codec", name = "commons-codec", version = "1.2")
  7. }
  8. }

上面的代码块传递给 buildscript() 库和依赖的代码块来配置 ScriptHandler 实例。通过 classpath 配置添加依赖项。这与声明 Java 编译类路径的方式相同。除了项目依赖项之外,您可以使用任何依赖项类型。

添加了构建脚本依赖之后,就可以在构建脚本中直接使用依赖中的类来完成特定的工作了。下面的示例通过给构建脚本添加 Base64 的依赖来,完成对内容的 Base64 加密。

输出结果来自命令:gradle -q encode

  1. > gradle -q encode
  2. aGVsbG8gd29ybGQK

对于多项目构建,用根项目的 buildscript() 方法声明的依赖可用于其所有子项目中的构建脚本。

构建脚本的依赖项可以是 Gradle 插件。有关Gradle插件的更多信息,请咨询 使用Gradle插件

每一个项目都会自带一个类型为 BuildEnvironmentReportTask 名称为 buildEnvironment 的任务。可以通过执行它来查看构建脚本的依赖项情况。

接下来去哪里?

在这一章中,我们首先看了任务。但这并不是任务的结束。如果您想了解更多细节,请查看 更多关于任务 的信息。

或者,继续学习 Java构建教程依赖关系管理