第二十一章. Gradle 插件

Chapter 21. Gradle Plugins

Gradle 在它的核心中有意地为真实世界的自动化提供一些有用的功能。所有有用的功能,例如能够编译 Java代码,都是通过插件来添加的。插件添加了新任务(如 JavaCompile),域对象(如 SourceSet),约定(如主要的 Java 源码是位于 的src/main/java),以及从扩展的核心对象和其他插件的对象。
Gradle at its core intentionally provides little useful functionality for real world automation. All of the useful features, such as the ability to compile Java code for example, are added by plugins. Plugins add new tasks (e.g. JavaCompile), domain objects (e.g. SourceSet), conventions (e.g. main Java source is located at src/main/java) as well as extending core objects and objects from other plugins.

在本章中,我们将讨论如何使用插件以及有关插件的术语和概念。
In this chapter we will discuss how to use plugins and the terminology and concepts surrounding plugins.

21.1. 应用插件

21.1. Applying plugins

插件可以说是被应用的,通过 Project.apply()方法来完成。
Plugins are said to be applied, which is done via the Project.apply() method.

示例 21.1. 应用插件 - Example 21.1. Applying a plugin

build.gradle

apply plugin: 'java'

插件都有表示自己的一个简称。在上述例子中,我们使用简称“java”来应用 JavaPlugin
Plugins advertise a short name for themselves. In the above case, we are using the short name ‘java’ to apply the JavaPlugin.

我们还可以使用下面的语法:
We could also have used the following syntax:

示例 21.2. 通过类型应用插件 - Example 21.2. Applying a plugin by type

build.gradle

apply plugin: org.gradle.api.plugins.JavaPlugin

归功于 Gradle 的默认导入(见《附录 E,现有的 IDE 支持和如何应对不支持的情况》),你也可以这样写:
Thanks to Gradle's default imports (see Appendix E, Existing IDE Support and how to cope without it) you could also write:

示例 21.2. 通过类型应用插件 - Example 21.3. Applying a plugin by type

build.gradle

apply plugin: JavaPlugin

插件的应用是幂等的。也就是说,一个插件可以应用多次。如果插件之前已经被应用,则任何进一步的应用都不会有影响。
The application of plugins is idempotent. That is, a plugin can be applied multiple times. If the plugin has previously been applied, any further applications will have no effect.

一个插件是简单的任何实现了Plugin 接口的类。 Gradle 提供了核心插件作为其发行版的一部分,所以你只需要简单地使用上面的插件即可。但是,对于第三方插件,你需要进行配置以使插件在构建类路径中可用。有关如何执行此操作的详细信息,请参阅第 59.5 节,《构建脚本的外部依赖》
A plugin is simply any class that implements the Plugin interface. Gradle provides the core plugins as part of its distribution so simply applying the plugin as above is all you need to do. For 3rd party plugins however, you need to make the plugins available to the build classpath. For more information on how to do this, see Section 59.5, “External dependencies for the build script”.

关于编写自己的插件的详细信息,请参阅《第 58章,编写自定义插件》。
For more on writing your own plugins, see Chapter 58, Writing Custom Plugins.

21.2. 插件都做了什么

21.2. What plugins do

在项目中应用插件可以使插件扩展 project 的功能。它可以做的事情有:
Applying a plugin to the project allows the plugin to extend the project's capabilities. It can do things such as:

  • 将任务添加到 project(如编译、测试)
    Add tasks to the project (e.g. compile, test)
  • 使用有用的默认设置对已添加的任务进行预配置。
    Pre-configure added tasks with useful defaults.
  • 向项目中添加依赖配置 (见第 8.3 节,《依赖配置》)。
    Add dependency configurations to the project (see Section 8.3, “Dependency configurations”).
  • 通过扩展对现有类型添加新的属性和方法。
    Add new properties and methods to existing type via extensions.

我们来看看以下内容:
Let's check this out:

示例 21.4. 通过插件添加任务 - Example 21.4. Tasks added by a plugin

build.gradle

apply plugin: 'java'

task show << {
    println relativePath(compileJava.destinationDir)
    println relativePath(processResources.destinationDir)
}

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

> gradle -q show
build/classes/main
build/resources/main

Java 插件已经向 project 添加了 compileJavaprocessResources 任务,并且配置了这两个任务的 destinationDir 属性。
The Java plugin has added a compileJava task and a processResources task to the project and configured the destinationDir property of both of these tasks.

21.3. 约定

21.3. Conventions

插件可以通过智能的方式对项目进行预配置,以支持约定优于配置配置。Gradle 为此提供了相关机制和复杂的支持,这正是强大而简洁的构建脚本的关键组成部分。
Plugins can pre-configure the project in smart ways to support convention-over-configuration. Gradle provides mechanisms and sophisticated support and it's a key ingredient in powerful-yet-concise build scripts.

在上面的示例中我们看到,Java 插件添加了一个名为 compileJava 的任务,这个任务有一个名为 destinationDir 的属性(即配置编译后的 Java 代码存放的地方)。Java 插件默认设置这个属性指向项目目录中的 build/classes/main。这就是一个合理的默认值的约定优于配置的例子。
We saw in the example above that the Java plugins adds a task named compileJava that has a property named destinationDir (that configures where the compiled Java source should be placed). The Java plugin defaults this property to point to build/classes/main in the project directory. This is an example of convention-over-configuration via a reasonable default.

我们可以简单地通过给它一个新的值来更改此属性。
We can change this property simply by giving it a new value.

示例 21.5. 更改插件的默认设置 - Example 21.5. Changing plugin defaults

build.gradle

apply plugin: 'java'

compileJava.destinationDir = file("$buildDir/output/classes")

task show << {
    println relativePath(compileJava.destinationDir)
}

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

> gradle -q show
build/output/classes

然而,compileJava 任务可能不是唯一需要知道类文件位置的任务。
However, the compileJava task is likely to not be the only task that needs to know where the class files are.

Java 插件增加了源文件集(见SourceSet)的概念来描述一组源文件的各个方面,其中一个方面是在编译的时候写入类文件的地方。Java 插件将 compileJava 任务的 destinationDir 属性映射到源文件集的这一方面。
The Java plugin adds the concept of source sets (see SourceSet) to describe the aspects of a set of source, one aspect being where the class files should be written to when it is compiled. The Java plugin maps the destinationDir property of the compileJava task to this aspect of the source set.

我们可以通过这个源文件集来修改类文件的写入位置。
We can change where the class files are written via the source set.

示例 21.6. 插件约定对象 - Example 21.6. Plugin convention object

build.gradle

apply plugin: 'java'

sourceSets.main.output.classesDir = file("$buildDir/output/classes")

task show << {
    println relativePath(compileJava.destinationDir)
}

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

> gradle -q show
build/output/classes

在上面的示例中,我们应用了 Java 插件,除此之外还做了下列操作:
In the example above, we applied the Java plugin which, among other things, did the following:

  • 添加了一个新的域对象类型: SourceSet
    Added a new domain object type: SourceSet
  • 通过属性的默认值(即约定值)配置了一个 main 源码集
    Configured a main source set with default (i.e. conventional) values for properties
  • 配置了支持任务来使用这些属性执行工作
    Configured supporting tasks to use these properties to perform work

所有这一切都发生在 apply plugin: "java" 这一步过程中。在上面的例子中,我们在约定配置被执行之后,修改了类文件的预定位置。通过这个例子的输出可以注意到,compileJava.destinationDir 的值也改变了,以反映配置的变化。
All of this happened during the apply plugin: "java" step. In the example above, we changed the desired location of the class files after this conventional configuration had been performed. Notice by the output with the example that the value for compileJava.destinationDir also changed to reflect the configuration change.

考虑一下有另一个任务使用到类文件的情况。如果这个任务被配置为使用 sourceSets.main.output.classesDir 的值,那么在这里修改它就会同时更新 compileJava 任务和那另一个任务,不管它的值是什么时候改的。
Consider the case where another task is to consume the classes files. If this task is configured to use the value from sourceSets.main.output.classesDir, then changing it in this location will update both the compileJava task and this other consumer task whenever it is changed.

这种配置对象属性以随时反映另一个对象任务值(即使它发生变化)的能力被称为“约定映射”。它可以令 Gradle 通过约定优于配置和合理的默认设置来实现简洁的配置,而如果需要修改约定的默认值时,也不需要完全重新配置。如果没有这一点,在上面的例子中,我们就不得不重新配置每个需要使用类文件的对象。
This ability to configure properties of objects to reflect the value of another object's task at all times (i.e. even when it changes) is known as “convention mapping”. It allows Gradle to provide conciseness through convention-over-configuration and sensible defaults yet not require complete reconfiguration if a conventional default needs to be changed. Without this, in the example above, we would have had to reconfigure every object that needs to work with the class files.

21.4. 更多关于插件的内容

21.4. More on plugins

本章旨在介绍插件和 Gradle 以及它们所扮演的角色。有关插件内部运作的详细信息,请参阅《第五十八章,编写自定义插件》。
This chapter aims to serve as an introduction to plugins and Gradle and the role they play. For more information on the inner workings of plugins, see Chapter 58, Writing Custom Plugins.