第六章. 构建脚本基础

Chapter 6. Build Script Basics

6.1. 项目和任务

6.1. Projects and tasks

在整个Gradle,有两个最基础的概念:项目任务
Everything in Gradle sits on top of two basic concepts: projects and tasks.

任何一个Gradle构建都是由一个或多个项目组成。一个项目代表着什么,取决于你想通过Gradle来做什么。比如,一个项目可能代表着一个JAR库,或者是一个Web应用程序。它也可能代表从其他项目所生成的JAR包组装起来的ZIP文件。一个项目不一定是代表一个要构建的东西,它也可能代表一个要完成的东西,比如把您的应用部署到预发布或生产环境。如果现在看得不是很明白,也不用担心。Gradle的按约定来构建的支持为项目提供了更具体的定义。
Every Gradle build is made up of one or more projects. What a project represents depends on what it is that you are doing with Gradle. For example, a project might represent a library JAR or a web application. It might represent a distribution ZIP assembled from the JARs produced by other projects. A project does not necessarily represent a thing to be built. It might represent a thing to be done, such as deploying your application to staging or production environments. Don't worry if this seems a little vague for now. Gradle's build-by-convention support adds a more concrete definition for what a project is.

每一个项目都由一个或多个任务组成。一个任务表示构建执行的一些原子工作,比如编译一些类,创建一个JAR包,生成javadoc,或者是把一些档案发布到仓库中。
Each project is made up of one or more tasks. A task represents some atomic piece of work which a build performs. This might be compiling some classes, creating a JAR, generating javadoc, or publishing some archives to a repository.

现在,我们来看一下如何在一个项目的构建中定义一些简单的任务。后续的章节将更多的关注多项目的构建,以及多项目和多任务的内容。
For now, we will look at defining some simple tasks in a build with one project. Later chapters will look at working with multiple projects and more about working with projects and tasks.

6.2. Hello world

6.2. Hello world

您可以使用 gradle 命令来运行Gradle构建。 gradle命令会在当前目录下查找一个 build.gradle 文件。 [2] 我们把这个 build.gradle 文件称为是一个 构建脚本,尽管严格上来讲,它只是一个构建的配置脚本,我们将会在后面看到。这个构建脚本定义了一个项目和它的任务。
You run a Gradle build using the gradle command. The gradle command looks for a file called build.gradle in the current directory. [2] We call this build.gradle file a build script, although strictly speaking it is a build configuration script, as we will see later. The build script defines a project and its tasks.

试着创建一个叫 build.gradle的构建脚本,如下所示:
To try this out, create the following build script named build.gradle.

示例 6.1. 第一个构建脚本 - Example 6.1. The first build script

build.gradle

task hello {
    doLast {
        println 'Hello world!'
    }
}

然后在命令行shell里,进入到当前目录并运行gradle -q hello来执行这个构建脚本:
In a command-line shell, enter into the containing directory and execute the build script by running gradle -q hello:

-q 参数的作用是什么?

本用户指南中的很多例子都用了 -q参数运行,这个参数是用于抑制Gradle的日志消息,以便只显示任务的输出结果,这样会使得在本用户指南里的文档的输出更清晰一点。如果你不想要的话,你可以不加上这个参数。如果想了解更多影响Gradle的输出的命令参数,请参阅第十八章, 日志
Most of the examples in this user guide are run with the -q command-line option. This suppresses Gradle's log messages, so that only the output of the tasks is shown. This keeps the example output in this user guide a little clearer. You don't need to use this option if you don't want. See Chapter 18, Logging for more details about the command-line options which affect Gradle's output.

What does -q do?

示例 6.2. 执行构建脚本 - Example 6.2. Execution of a build script

gradle -q hello的输出结果
Output of gradle -q hello

> gradle -q hello
Hello world!

我们来看看这个脚本做了些什么。它定义了一个叫做 hello的任务,并且给它加了一个动作。当你执行gradle hello的时候,Gradle会执行这个hello任务,而这个任务又会执行您所提供的动作。这个动作只是一个包含了一些要执行的Groovy代码的闭包。
What's going on here? This build script defines a single task, called hello, and adds an action to it. When you run gradle hello, Gradle executes the hello task, which in turn executes the action you've provided. The action is simply a closure containing some Groovy code to execute.

如果你认为它看上去和Ant的目标很像,那就对了。Gradle的任务相当于Ant的目标。但是,正如你所见,它们更加强大。我们使用与Ant不同的术语,是因为我们认为任务这个词比目标的含义更丰富。不过不幸的是,这也导致了与Ant的术语冲突。Ant会调用它自己的一些命令,比如像javac 或是 copy任务。因为,当我们讨论任务时,通常指的是Gradle的任务,相当于Ant的目标。当我们讨论Ant 的任务时,我们会明确地说是 ant 任务
If you think this looks similar to Ant's targets, well, you are right. Gradle tasks are the equivalent to Ant targets. But as you will see, they are much more powerful. We have used a different terminology than Ant as we think the word task is more expressive than the word target. Unfortunately this introduces a terminology clash with Ant, as Ant calls its commands, such as javac or copy, tasks. So when we talk about tasks, we always mean Gradle tasks, which are the equivalent to Ant's targets. If we talk about Ant tasks (Ant commands), we explicitly say ant task.

6.3. 快速任务定义

6.3. A shortcut task definition

有一种简单的方法可以定义像上面我们的这类hello 任务,它看起来更简洁。
There is a shorthand way to define a task like our hello task above, which is more concise.

示例6.3. 任务定义的快捷方式 - Example 6.3. A task definition shortcut

build.gradle

task hello << {
    println 'Hello world!'
}

这里再一次使用了一个闭包的方式来定义一个 hello 任务去执行。在本用户指南中,我们还会再使用这种任务定义的风格。
Again, this defines a task called hello with a single closure to execute. We will use this task definition style throughout the user guide.

6.4. 构建脚本即代码

6.4. Build scripts are code

Gradle的构建脚本向您开放了Groovy的全部功能。作为开胃菜,可以看看下这个例子:
Gradle's build scripts expose to you the full power of Groovy. As an appetizer, have a look at this:

示例6.4. 在Gradle任务中使用Groovy - Example 6.4. Using Groovy in Gradle's tasks

build.gradle

task upper << {
    String someString = 'mY_nAmE'
    println "Original: " + someString
    println "Upper case: " + someString.toUpperCase()
}

Output of gradle -q upper的输出结果
Output of gradle -q upper

> gradle -q upper
Original: mY_nAmE
Upper case: MY_NAME

或者
or

示例6.5. 在Gradle任务中使用Groovy - Example 6.5. Using Groovy in Gradle's tasks

build.gradle

task count << {
    4.times { print "$it " }
}

Output of gradle -q count的输出结果
Output of gradle -q count

> gradle -q count
0 1 2 3

6.5. 任务依赖

6.5. Task dependencies

你应该也已经猜到了,我们可以声明任务之间的依赖关系。
As you probably have guessed, you can declare dependencies between your tasks.

示例6.6. 声明任务之间的依赖关系 - Example 6.6. Declaration of dependencies between tasks

build.gradle

task hello << {
    println 'Hello world!'
}
task intro(dependsOn: hello) << {
    println "I'm Gradle"
}

gradle -q intro的输出结果
Output of gradle -q intro

> gradle -q intro
Hello world!
I'm Gradle

在添加依赖时,对应的任务不一定要存在。
To add a dependency, the corresponding task does not need to exist.

示例6.7. 延迟依赖——另一个任务(暂)不存在 - Example 6.7. Lazy dependsOn - the other task does not exist (yet)

build.gradle

task taskX(dependsOn: 'taskY') << {
    println 'taskX'
}
task taskY << {
    println 'taskY'
}

gradle -q taskX的输出结果
Output of gradle -q taskX

> gradle -q taskX
taskY
taskX

taskX 依赖 taskY 是在 taskY 之前定义的。这点在多项目构建中非常有用。有关任务依赖的更多信息,可以参阅 第15.4节,“给任务添加依赖”
The dependency of taskX to taskY is declared before taskY is defined. This is very important for multi-project builds. Task dependencies are discussed in more detail in Section 15.4, “Adding dependencies to a task”.

请注意,当一个任务还没有被定义的时候,不能使用快捷符号(见 第6.8节,“快捷标记”)。
Please notice that you can't use shortcut notation (see Section 6.8, “Shortcut notations”) when referring to a task that is not yet defined.

6.6. 动态任务

6.6. Dynamic tasks

Groovy的强大之处,不仅仅是用在定义一个任务做什么的时候。例如,你也可以使用它来动态地创建一些任务。
The power of Groovy can be used for more than defining what a task does. For example, you can also use it to dynamically create tasks.

示例6.8. 任务的动态创建 - Example 6.8. Dynamic creation of a task

build.gradle

4.times { counter ->
    task "task$counter" << {
        println "I'm task number $counter"
    }
}

gradle -q task1的输出结果
Output of gradle -q task1

> gradle -q task1
I'm task number 1

6.7. 操纵现有任务

6.7. Manipulating existing tasks

与Ant不同,一旦任务被创建了,就可以通过一个 API去访问它。例如,你可以去给它们添加其他的依赖。
Once tasks are created they can be accessed via an API. This is different to Ant. For example you can create additional dependencies.

示例6.9. 通过API访问任务——添加依赖 - Example 6.9. Accessing a task via API - adding a dependency

build.gradle

4.times { counter ->
    task "task$counter" << {
        println "I'm task number $counter"
    }
}
task0.dependsOn task2, task3

gradle -q task0的输出结果
Output of gradle -q task0

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

或者,你也可以对已经存在的任务添加行为。
Or you can add behavior to an existing task.

示例6.10. 通过API访问任务——添加行为 - Example 6.10. Accessing a task via API - adding behaviour

build.gradle

task hello << {
    println 'Hello Earth'
}
hello.doFirst {
    println 'Hello Venus'
}
hello.doLast {
    println 'Hello Mars'
}
hello << {
    println 'Hello Jupiter'
}

gradle -q hello的输出结果
Output of gradle -q hello

> gradle -q hello
Hello Venus
Hello Earth
Hello Mars
Hello Jupiter

doFirstdoLast 可以被多次调用,它们分别是向任务的动作列表的开头或结尾添加一个动作。当任务执行的时候,动作列表的这些动作会依次执行。<< 操作只是 doLast的别名。
The calls doFirst and doLast can be executed multiple times. They add an action to the beginning or the end of the task's actions list. When the task executes, the actions in the action list are executed in order. The << operator is simply an alias for doLast.

6.8. 快捷符号

6.8. Shortcut notations

你可能在前面的例子中也注意到了,一个已存在任务会有一个便捷的符号用于访问。 每一个任务都可作为这个构建脚本中的一个属性。
As you might have noticed in the previous examples, there is a convenient notation for accessing an existing task. Each task is available as a property of the build script:

示例6.11. 以构建脚本的属性的方式访问任务 - Example 6.11. Accessing task as a property of the build script

build.gradle

task hello << {
    println 'Hello world!'
}
hello.doLast {
    println "Greetings from the $hello.name task."
}

gradle -q hello的输出结果
Output of gradle -q hello

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

这使得代码很易于阅读,尤其是当使用插件所提供的任务时(例如 compile)。
This enables very readable code, especially when using the out of the box tasks provided by the plugins (e.g. compile).

6.9. 额外的任务属性

6.9. Extra task properties

您可以把您自己的属性添加到一个任务中。譬如如果要添加一个 myProperty属性,可以为 ext.myProperty设置一个初始值,然后您就可以像使用预定义的任务属性一样对它进行读取或设置。
You can add your own properties to a task. To add a property named myProperty, set ext.myProperty to an initial value. From that point on, the property can be read and set like a predefined task property.

示例6.12. 为任务添加额外的属性 - Example 6.12. Adding extra properties to a task

build.gradle

task myTask {
    ext.myProperty = "myValue"
}

task printTaskProperties << {
    println myTask.myProperty
}

gradle -q printTaskProperties的输出结果
Output of gradle -q printTaskProperties

> gradle -q printTaskProperties
myValue

额外属性并不局限于任务,你可以在 第13.4.2节,“额外属性”中了解更多关于它们的内容。

6.10. 使用 Ant 任务

6.10. Using Ant Tasks

Ant任务是Gradle的一级公民。Gradle通过简单地依赖Groovy,对Ant任务提供了强大的集成。Groovy自带了一个神奇的AntBuilder,在Gradle中使用Ant任务比在build.xml中调用更方便和强大。通过下面的例子,您可以学习到如何执行ant任务,以及如何访问ant属性:
Ant tasks are first-class citizens in Gradle. Gradle provides excellent integration for Ant tasks by simply relying on Groovy. Groovy is shipped with the fantastic AntBuilder. Using Ant tasks from Gradle is as convenient and more powerful than using Ant tasks from a build.xml file. From the example below, you can learn how to execute ant tasks and how to access ant properties:

示例6.13. 使用AntBuilder 执行 ant.loadfile 目标 - Example 6.13. Using AntBuilder to execute ant.loadfile target

build.gradle

task loadfile << {
    def files = file('../antLoadfileResources').listFiles().sort()
    files.each { File file ->
        if (file.isFile()) {
            ant.loadfile(srcFile: file, property: file.name)
            println " *** $file.name ***"
            println "${ant.properties[file.name]}"
        }
    }
}

gradle -q loadfile的输出结果
Output of gradle -q loadfile

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

在构建脚本中您可以利用Ant做更多的事情,更多细节请参阅 第十七章,在Gradle中使用Ant
There is lots more you can do with Ant in your build scripts. You can find out more in Chapter 17, Using Ant from Gradle.

6.11. 使用方法

6.11. Using methods

Gradle的内向扩展取决于您如何组织您的构建逻辑。在上面的例子中,组织您的构建逻辑的第一个层次是抽取出方法。
Gradle scales in how you can organize your build logic. The first level of organizing your build logic for the example above, is extracting a method.

示例6.14. 在构建逻辑中使用方法 - Example 6.14. Using methods to organize your build logic

build.gradle

task checksum << {
    fileList('../antLoadfileResources').each {File file ->
        ant.checksum(file: file, property: "cs_$file.name")
        println "$file.name Checksum: ${ant.properties["cs_$file.name"]}"
    }
}

task loadfile << {
    fileList('../antLoadfileResources').each {File file ->
        ant.loadfile(srcFile: file, property: file.name)
        println "I'm fond of $file.name"
    }
}

File[] fileList(String dir) {
    file(dir).listFiles({file -> file.isFile() } as FileFilter).sort()
}

gradle -q loadfile的输出结果
Output of gradle -q loadfile

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

在后面您将会看到,像这样的方法可以在多项目构建的子项目之间共享。即使您的构建逻辑变得很复杂,Gradle也提供了一种非常方便的方式去组织它。关于这一点,我们会用一整个章节来讨论,详请参阅 第五十九章,组织构建逻辑
Later you will see that such methods can be shared among subprojects in multi-project builds. If your build logic becomes more complex, Gradle offers you other very convenient ways to organize it. We have devoted a whole chapter to this. See Chapter 59, Organizing Build Logic.

6.12. 默认任务

6.12. Default tasks

Gradle允许您在构建中定义一个或多个的默认任务。
Gradle allows you to define one or more default tasks for your build.

示例6.15. 定义一个默认任务 - Example 6.15. Defining a default tasks

build.gradle

defaultTasks 'clean', 'run'

task clean << {
    println 'Default Cleaning!'
}

task run << {
    println 'Default Running!'
}

task other << {
    println "I'm not a default task!"
}

gradle -q的构建结果
Output of gradle -q

> gradle -q
Default Cleaning!
Default Running!

这与运行 gradle clean run的结果是一样的。在多项目构建中,每一个子项目都可以有它自己的指定的默认任务。如果一个子项目没有指定默认任务,而父项目定义了的话,那么将会使用父项目的。
This is equivalent to running gradle clean run. In a multi-project build every subproject can have its own specific default tasks. If a subproject does not specify default tasks, the default tasks of the parent project are used (if defined).

6.13. 使用 DAG 配置

6.13. Configure by DAG

正如我们后面会详细描述的的(见第五十五章,构建的生命周期),Gradle有一个配置阶段和一个执行阶段。在配置阶段后,Gradle会了解所有应该执行的任务。Gradle提供了一个钩子来让你使用这些信息。一个使用场景是,可以检查某个发布任务是否在这些要执行的任务当中。借由此,您可以为一些变量进行不同的赋值。
As we later describe in full detail (see Chapter 55, The Build Lifecycle), Gradle has a configuration phase and an execution phase. After the configuration phase, Gradle knows all tasks that should be executed. Gradle offers you a hook to make use of this information. A use-case for this would be to check if the release task is among the tasks to be executed. Depending on this, you can assign different values to some variables.

在下面的例子中,distributionrelease 任务的执行会导致version 变量有不同的结果。
In the following example, execution of the distribution and release tasks results in different value of the version variable.

示例6.16. 根据所选择的任务输出不同的构建结果 - Example 6.16. Different outcomes of build depending on chosen tasks

build.gradle

task distribution << {
    println "We build the zip with version=$version"
}

task release(dependsOn: 'distribution') << {
    println 'We release now'
}

gradle.taskGraph.whenReady {taskGraph ->
    if (taskGraph.hasTask(release)) {
        version = '1.0'
    } else {
        version = '1.0-SNAPSHOT'
    }
}

gradle -q distribution的输出结果
Output of gradle -q distribution

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

gradle -q release的输出结果
Output of gradle -q release

> gradle -q release
We build the zip with version=1.0
We release now

在这里,很重要的一点是,whenReady 会在发布任务执行之前影响到它。即使这个发布任务不是 主要的 (即传给 gradle 命令行的任务),这一点也同样有效。
The important thing is that whenReady affects the release task before the release task is executed. This works even when the release task is not the primary task (i.e., the task passed to the gradle command).

6.14. 下一步目标?

6.14. Where to next?

在本章中,我们已经对任务有了初步的了解。但是关于任务的内容还不仅仅是这些,如果你想了解更多的细节,请参阅 第十五章,任务进阶
In this chapter, we have had a first look at tasks. But this is not the end of the story for tasks. If you want to jump into more of the details, have a look at Chapter 15, More about Tasks.

另外,本教程接下来是 《第七章, Java 快速入门》 以及《第八章, 依赖管理基础》。
Otherwise, continue on to the tutorials in Chapter 7, Java Quickstart and Chapter 8, Dependency Management Basics.



[2] 有一些命令行开关可以改变这一行为,请参阅《附录 D, Gradle 命令行》。
[2] There are command line switches to change this behavior. See Appendix D, Gradle Command Line)