第五十五章. 构建的生命周期

Chapter 55. The Build Lifecycle

我们之前说过,Gradle 的核心是一种基于依赖编程的语言。用 Gradle 的话说,这意味着你可以定义任务以及任务之间的依赖。Gradle 保证这些任务按其依赖的顺序执行,并且每个任务只执行一次。这些任务形成一个 有向无环图。有许多构建工具在执行任务时生成了一个这样的依赖图。Gradle 则是在执行所有任务之前 生成这个完整的依赖图。这就是 Gradle 的核心,并且它使得许多事情由不可能变得可能。
We said earlier, that the core of Gradle is a language for dependency based programming. In Gradle terms this means that you can define tasks and dependencies between tasks. Gradle guarantees that these tasks are executed in the order of their dependencies, and that each task is executed only once. Those tasks form a Directed Acyclic Graph. There are build tools that build up such a dependency graph as they execute their tasks. Gradle builds the complete dependency graph before any task is executed. This lies at the heart of Gradle and makes many things possible which would not be possible otherwise.

你的构建脚本配置了这个依赖图。因此严格来讲,它们都是构建配置脚本
Your build scripts configure this dependency graph. Therefore they are strictly speaking build configuration scripts.

55.1. 构建阶段

55.1. Build phases

Gradle 构建有三个不同的阶段。
A Gradle build has three distinct phases.

初始化
Initialization

Gradle 支持单项目及多项目的构建。在初始化阶段,Gradle 确定哪些项目是需要构建的,并为每个项目创建一个 Project 实例。
Gradle supports single and multi-project builds. During the initialization phase, Gradle determines which projects are going to take part in the build, and creates a Project instance for each of these projects.

配置
Configuration

在这一阶段,对项目对象进行配置。属于构建部分的所有项目的构建脚本都会被执行。Gradle 1.4 引入了一个试验性的选择性加入功能,叫做按需配置。在这个模式下,Gradle 只会配置相关的项目(请参阅第 56.1.1.1 节,“按需配置”)。
During this phase the project objects are configured. The build scripts of all projects which are part of the build are executed. Gradle 1.4 introduces an incubating opt-in feature called configuration on demand. In this mode, Gradle configures only relevant projects (see Section 56.1.1.1, “Configuration on demand”).

执行
Execution

Gradle 确定在配置阶段中创建和配置的要执行的任务子集。这个子集是由传给 gradle 命令的任务名称参数和当前目录所决定的。然后,Gradle 将执行每个选定的任务。
Gradle determines the subset of the tasks, created and configured during the configuration phase, to be executed. The subset is determined by the task name arguments passed to the gradle command and the current directory. Gradle then executes each of the selected tasks.

55.2. 设置文件

55.2. Settings file

在构建脚本文件旁边,Gradle 定义了一个设置文件。这个设置文件是 Gradle 通过命名约定来决定的,它的默认名称是 settings.gradle。在本章中,稍后我们会解释 Gradle 是如何查找设置文件的。
Beside the build script files, Gradle defines a settings file. The settings file is determined by Gradle via a naming convention. The default name for this file is settings.gradle. Later in this chapter we explain how Gradle looks for a settings file.

这个设置文件会在初始化阶段执行。多项目构建必须在这个多项目的层次结构的根项目下有一个 settings.gradle 文件。这个文件是必不可少的,因为在这个设置文件中,定义了哪些项目会加处多项目构建(见《第五十六章,多项目构建》)。对于单项目构建,有没有设置文件都可以。例如,你可能需要它将一些库添加到构建脚本类路径中(见《第五十九章,组织构建逻辑》)。我们先来看一个单项目的构建:
The settings file gets executed during the initialization phase. A multiproject build must have a settings.gradle file in the root project of the multiproject hierarchy. It is required because in the settings file it is defined, which projects are taking part in the multi-project build (see Chapter 56, Multi-project Builds). For a single-project build, a settings file is optional. You might need it for example, to add libraries to your build script classpath (see Chapter 59, Organizing Build Logic). Let's first do some introspection with a single project build:

示例 55.1. 单项目构建 - Example 55.1. Single project build

settings.gradle

println 'This is executed during the initialization phase.'

build.gradle

println 'This is executed during the configuration phase.'

task configured {
    println 'This is also executed during the configuration phase.'
}

task test << {
    println 'This is executed during the execution phase.'
}

gradle test 的输出结果
Output of gradle test

> gradle test
This is executed during the initialization phase.
This is executed during the configuration phase.
This is also executed during the configuration phase.
:test
This is executed during the execution phase.

BUILD SUCCESSFUL

Total time: 1 secs

对于一个构建脚本,属性访问和方法调用会委托给一个项目对象。同样,设置文件中的属性访问和方法调用也会委派给设置对象。可以看一下 Settings
For a build script, the property access and method calls are delegated to a project object. Similarly property access and method calls within the settings file is delegated to a settings object. Have a look at Settings.

55.3. 多项目构建

55.3. Multi-project builds

多项目构建是指在一个 Gradle 执行中构建多个项目的构建。你必须在设置文件中声明参与多项目构建的项目。关于多项目构建,在专门讨论这一主题的章节中还有更多的介绍(参见《第五十六章,多项目构建》)。
A multi-project build is a build where you build more than one project during a single execution of Gradle. You have to declare the projects taking part in the multiproject build in the settings file. There is much more to say about multi-project builds in the chapter dedicated to this topic (see Chapter 56, Multi-project Builds).

55.3.1. 项目位置

55.3.1. Project locations

多项目构建总是由一个具有单个根节点的树表示。树中的每个元素都表示一个项目。一个项目有一个路径,表示其在多项目构建树中的位置。在大多数情况下,项目路径与文件系统中项目的物理位置一致。但是,这种行为是可配置的。项目树在 settings.gradle 文件中创建。默认情况下它假定设置文件的位置也是根项目的位置,但你可以在设置文件中重新定义根项目的位置。
Multi-project builds are always represented by a tree with a single root. Each element in the tree represents a project. A project has a path which denotes the position of the project in the multi-project build tree. In majority of cases the project path is consistent with the physical location of the project in the file system. However, this behavior is configurable. The project tree is created in the settings.gradle file. By default it is assumed that the location of the settings file is also the location of the root project. But you can redefine the location of the root project in the settings file.

55.3.2. 构建树

55.3.2. Building the tree

在设置文件中,你可以使用一组方法来构建项目树。其中,层次和平面的物理布局有特别的支持。
In the settings file you can use a set of methods to build the project tree. Hierarchical and flat physical layouts get special support.

55.3.2.1. 层次布局

55.3.2.1. Hierarchical layouts

示例 55.2. 层次布局 - Example 55.2. Hierarchical layout

settings.gradle

include 'project1', 'project2:child', 'project3:child1'

include 方法把项目路径作为参数。项目路径被假定为等于物理文件系统相对路径。例如,默认情况下,路径“services:api”会映射到文件夹“services/api”(相对于根项目)。你只需要指定树的叶。这意味着包含路径“services:hotels:api”将导致创建三个项目:“services”,“services:hotels”和“services:hotels:api”。
The include method takes project paths as arguments. The project path is assumed to be equal to the relative physical file system path. For example, a path 'services:api' is mapped by default to a folder 'services/api' (relative from the project root). You only need to specify the leaves of the tree. This means that the inclusion of the path 'services:hotels:api' will result in creating 3 projects: 'services', 'services:hotels' and 'services:hotels:api'.

55.3.2.2. 平面布局

55.3.2.2. Flat layouts

示例 55.3. 平面布局 - Example 55.3. Flat layout

settings.gradle

includeFlat 'project3', 'project4'

includeFlat 方法以目录名称为参数。这些目录需要作为根项目目录的同级存在,它们的位置会被视为多项目树中根项目的子项目。
The includeFlat method takes directory names as an argument. Those directories need to exist as siblings of the root project directory. The location of those directories are considered as child projects of the root project in the multi-project tree.

55.3.3. 修改项目树元素

55.3.3. Modifying elements of the project tree

在设置文件中创建的多项目树由所谓的项目描述符组成。你可以随时修改设置文件中的这些描述符。你可以这样访问一个描述符:
The multi-project tree created in the settings file is made up of so called project descriptors. You can modify these descriptors in the settings file at any time. To access a descriptor you can do:

示例 55.4. 修改项目树中的元素 - Example 55.4. Modification of elements of the project tree

settings.gradle

println rootProject.name
println project(':projectA').name

通过使用这个描述符,你可以更改项目的名称、目录和构建文件。
Using this descriptor you can change the name, project directory and build file of a project.

示例 55.5. 修改项目树中的元素 - Example 55.5. Modification of elements of the project tree

settings.gradle

rootProject.name = 'main'
project(':projectA').projectDir = new File(settingsDir, '../my-project-a')
project(':projectA').buildFileName = 'projectA.gradle'

更多信息请参阅 ProjectDescriptor
Have a look at ProjectDescriptor for more details.

55.4. 初始化

55.4. Initialization

Gradle 是怎么知道要构建单个或多项目呢?如果你在一个有设置文件的目录中触发多项目构建,那么情况很简单。但 Gradle 也允许你在参与构建的任意子项目中执行构建。[20] 如果你在没有 settings.gradle 文件的项目中执行 Gradle,Gradle 将执行以下操作:
How does Gradle know whether to do a single or multiproject build? If you trigger a multiproject build from the directory where the settings file is, things are easy. But Gradle also allows you to execute the build from within any subproject taking part in the build. [20] If you execute Gradle from within a project that has no settings.gradle file, Gradle does the following:

  • 它在名为 master 的目录中搜索 settings.gradle,该目录与当前目录有相同的嵌套级别。

    It searches for a settings.gradle in a directory called master which has the same nesting level as the current dir.

  • 如果找不到settings.gradle,它将在父目录里搜索settings.gradle文件。

    If no settings.gradle is found, it searches the parent directories for the existence of a settings.gradle file.

  • 如果还是没有找到settings.gradle文件,那么会将这个构建作为一个单项目构建来执行。

    If no settings.gradle file is found, the build is executed as a single project build.

  • 如果找到了settings.gradle 文件,那么 Gradle 会检查当前项目是否为所找到的settings.gradle 文件中定义的多项目层次结构的一部分。如果不是,这个构建还是作为单项目构建执行;否则作为多项目构建执行。

    If a settings.gradle file is found, Gradle checks if the current project is part of the multiproject hierarchy defined in the found settings.gradle file. If not, the build is executed as a single project build. Otherwise a multiproject build is executed.

这种行为的目的是什么?无论你进入的项目是否为多项目构建的子项目,Gradle 都必须查明该项目是否为多项目构建的子项目。当然,如果它是子项目,那么就只构建子项目及其依赖的项目。但是 Gradle 需要为整个多项目构建创建构建配置(请参阅《第五十六章,多项目构建》)。通过 -u 命令行选项,你可以告诉 Gradle 不要在父层次结构中查找 settings.gradle 文件。然后,当前项目将始终作为单项目进行构建。如果当前项目包含了 settings.gradle 文件,那么 -u 选项将没有意义。这样的构建始终这样执行:
What is the purpose of this behavior? Somehow Gradle has to find out, whether the project you are into, is a subproject of a multiproject build or not. Of course, if it is a subproject, only the subproject and its dependent projects are build. But Gradle needs to create the build configuration for the whole multiproject build (see Chapter 56, Multi-project Builds). Via the -u command line option, you can tell Gradle not to look in the parent hierarchy for a settings.gradle file. The current project is then always build as a single project build. If the current project contains a settings.gradle file, the -u option has no meaning. Such a build is always executed as:

  • 如果 settings.gradle 文件没有定义多项目的层次结构,作为单项目构建

    a single project build, if the settings.gradle file does not define a multiproject hierarchy

  • 如果 settings.gradle 文件定义了多项目的层次结构,作为多项目构建。

    a multiproject build, if the settings.gradle file does define a multiproject hierarchy.

自动搜索设置文件只适用于具有物理上的层次结构或平面布局的多项目构建。对于平面布局,你还必须遵循上面所述的命名约定。Gradle 支持多项目构建的任意物理布局。但是,对于这种任意布局,你需要从设置文件所在的目录执行构建。有关如何从根目录运行部分构建,请参阅《第56.4节,使用任务的绝对路径运行任务)。在下个发行版中,我们希望通过在命令行参数中指定设置文件的路径,来从子项目中启用部分构建。Gradle 为参与构建的每个项目创建项目对象。对于单项目构建而言,它是一个项目对象。而对于多项目构建,它们是 Settings 对象指定的所有项目对象(加上根项目对象)。默认情况下,每个项目对象的名称是其顶级目录的名称。除根项目之外,每个项目都有父项目,并且可能还有子项目。
The auto search for a settings file does only work for multi-project builds with a physical hierarchical or flat layout. For a flat layout you must additionally obey to the naming convention described above. Gradle supports arbitrary physical layouts for a multiproject build. But for such arbitrary layouts you need to execute the build from the directory where the settings file is located. For how to run partial builds from the root see Section 56.4, “Running tasks by their absolute path”. In our next release we want to enable partial builds from subprojects by specifying the location of the settings file as a command line parameter. Gradle creates Project objects for every project taking part in the build. For a single project build this is only one project. For a multi-project build these are the projects specified in Settings object (plus the root project). Each project object has by default a name equals to the name of its top level directory. Every project except the root project has a parent project and might have child projects.

55.5. 单项目构建的配置和执行

55.5. Configuration and execution of a single project build

对于单项目构建, 初始化后 阶段的工作流程相当简单。构建脚本会针对初始化阶段期间创建的项目对象执行。然后 Gradle 会查找与作为命令行参数传进来的名称相同的任务名。如果这些任务名存在,那么会按参数的顺序将这些任务作为单独的构建执行。关于多项目构建的配置与执行,在《第五十六章,多项目构建》中有论述。
For a single project build, the workflow of the after initialization phases are pretty simple. The build script is executed against the project object that was created during the initialization phase. Then Gradle looks for tasks with names equal to those passed as command line arguments. If these task names exist, they are executed as a separate build in the order you have passed them. The configuration and execution for multi-project builds is discussed in Chapter 56, Multi-project Builds.

55.6. 构建脚本生命周期的响应

55.6. Responding to the lifecycle in the build script

构建脚本可以在构建过程通过其生命周期时接收通知。这些通知通常采用两种形式:你可以实现一个特定的监听器接口,或提供一个用于在触发通知时执行的闭包。以下是使用闭包的例子。有关如何使用监听器接口的详细信息,请参阅 API 文档。
Your build script can receive notifications as the build progresses through its lifecycle. These notifications generally take 2 forms: You can either implement a particular listener interface, or you can provide a closure to execute when the notification is fired. The examples below use closures. For details on how to use the listener interfaces, refer to the API documentation.

55.6.1. 项目评估

55.6.1. Project evaluation

你可以在评估项目前后立刻接收通知。这可以用来做一些事情,比如一旦应用了构建脚本中的所有定义,就执行额外的配置;或者是做一些自定义的日志记录或分析。
You can receive a notification immediately before and after a project is evaluated. This can be used to do things like performing additional configuration once all the definitions in a build script have been applied, or for some custom logging or profiling.

下面是将一个 test 任务添加到每个 hasTests 属性为 true 的项目中。
Below is an example which adds a test task to each project with the hasTests property set to true.

示例 55.6. 向每个包含某些属性集的项目添加测试任务 - Example 55.6. Adding of test task to each project which has certain property set

build.gradle

allprojects {
    afterEvaluate { project ->
        if (project.hasTests) {
            println "Adding test task to $project"
            project.task('test') << {
                println "Running tests for $project"
            }
        }
    }
}

projectA.gradle

hasTests = true

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

> gradle -q test
Adding test task to project ':projectA'
Running tests for project ':projectA'

这个例子使用了 Project.afterEvaluate() 方法来添加一个在项目评估之后执行的闭包。
This example uses method Project.afterEvaluate() to add a closure which is executed after the project is evaluated.

你也可以在每一个项目评估之后都收到通知。下面的例子是执行项目评估的一些自定义日志记录。注意,无论项目评估是成功还是因异常而失败,都会收到 afterProject 通知。
It is also possible to receive notifications when any project is evaluated. This example performs some custom logging of project evaluation. Notice that the afterProject notification is received regardless of whether the project evaluates successfully or fails with an exception.

示例 55.7. 通知 - Example 55.7. Notifications

build.gradle

gradle.afterProject {project, projectState ->
    if (projectState.failure) {
        println "Evaluation of $project FAILED"
    } else {
        println "Evaluation of $project succeeded"
    }
}

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

> gradle -q test
Evaluation of root project 'buildProjectEvaluateEvents' succeeded
Evaluation of project ':projectA' succeeded
Evaluation of project ':projectB' FAILED

你也可以将一个 ProjectEvaluationListener 添加到 Gradle 中,来接收这些事件。
You can also add a ProjectEvaluationListener to the Gradle to receive these events.

55.6.2. 任务创建

55.6.2. Task creation

你可以在有任务添加到项目后立即收到通知。这可以用来在构建文件中的任务可用之前,设置一些默认值或添加行为。
You can receive a notification immediately after a task is added to a project. This can be used to set some default values or add behaviour before the task is made available in the build file.

下面的示例是在每个任务创建之前设置 srcDir 属性。
The following example sets the srcDir property of each task as it is created.

示例 55.8. 对所有任务设置特定属性 - Example 55.8. Setting of certain property to all tasks

build.gradle

tasks.whenTaskAdded { task ->
    task.ext.srcDir = 'src/main/java'
}

task a

println "source dir is $a.srcDir"

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

> gradle -q a
source dir is src/main/java

你还可以向 TaskContainer 添加一个 Action 来接收这些事件。
You can also add an Action to a TaskContainer to receive these events.

55.6.3. 任务执行图准备

55.6.3. Task execution graph ready

你可以在任务执行图生成之后立刻收到通知。在《第6.13节,使用 DAG 配置》中,我们已经看到这一点了。
You can receive a notification immediately after the task execution graph has been populated. We have seen this already in Section 6.13, “Configure by DAG”.

你也可以向 TaskExecutionGraph 添加一个 TaskExecutionGraphListener 来接收这些事件。
You can also add a TaskExecutionGraphListener to the TaskExecutionGraph to receive these events.

55.6.4. 任务执行

55.6.4. Task execution

你可以在任何任务执行前马上收到通知。
You can receive a notification immediately before and after any task is executed.

下面示例展示了在每个任务执行开始及结束时打印日志。注意,无论任务是成功完成还是异常失败,都会收到 afterTask 通知。
The following example logs the start and end of each task execution. Notice that the afterTask notification is received regardless of whether the task completes successfully or fails with an exception.

示例55.9. 在每个任务执行开始及结束时打印日志 - Example 55.9. Logging of start and end of each task execution

build.gradle

task ok

task broken(dependsOn: ok) << {
    throw new RuntimeException('broken')
}

gradle.taskGraph.beforeTask { Task task ->
    println "executing $task ..."
}

gradle.taskGraph.afterTask { Task task, TaskState state ->
    if (state.failure) {
        println "FAILED"
    }
    else {
        println "done"
    }
}

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

> gradle -q broken
executing task ':ok' ...
done
executing task ':broken' ...
FAILED

你也可以对 TaskExecutionGraph 使用一个 TaskExecutionListener 来接收这些事件。
You can also use a TaskExecutionListener to the TaskExecutionGraph to receive these events.



[20] Gradle 支持部分多项目构建(参见《第五十六章,多项目构建》)。
[20] Gradle supports partial multiproject builds (see Chapter 56, Multi-project Builds).