vlambda博客
学习文章列表

读书笔记《gradle-effective-implementations-guide-second-edition》编写自定义任务和插件

Chapter 10.  Writing Custom Tasks and Plugins

在 Gradle 中,我们可以在构建文件中编写一个简单的任务,在其中添加带有闭包的操作,或者我们可以配置包含在 Gradle 中的现有任务。编写我们自己的任务的过程很容易。有多种方法可以创建自定义任务,我们将在本章中介绍:

  • 我们将看到如何在我们的构建文件中创建一个新的任务类并在我们的项目中使用它。

  • 我们将讨论如何在单独的源文件中创建自定义任务。我们还将讨论如何使我们的任务在其他项目中可重用。

  • 我们将讨论如何为 Gradle 编写插件。与编写自定义任务类似,我们将介绍编写插件的不同方法。我们还将看到如何发布我们的插件并讨论如何在新项目中使用它。

  • 我们可以在 Groovy 中编写任务和插件,这与 Gradle API 配合得很好,但我们也可以使用其他语言,例如 Java 和 Scala。只要将代码编译成字节码就可以了。

Creating a custom task


当我们在构建中创建一个新任务并使用 type 属性指定一个任务时,我们实际上配置了一个现有任务。现有任务在 Gradle 中称为 增强任务。例如,Copy 任务类型是增强任务。我们将在构建文件中配置任务,但 Copy 任务的实现在单独的类文件中。将任务使用与任务实现分开是一种很好的做法。它提高了任务的可维护性和可重用性。在本节中,我们将创建自己的增强任务。

Creating a custom task in the build file

首先,让我们看看如何通过简单的操作添加一个新任务来创建一个任务以在我们的构建中显示当前的 Gradle 版本。我们之前在其他示例构建文件中已经看到了这些类型的任务。在以下示例构建中,我们将创建一个新的 info 任务:

task info(description: 'Show Gradle version') << { 
    println "Current Gradle version: $project.gradle.gradleVersion" 
} 

当我们从命令行调用 info 任务时,我们将看到以下输出:

$ gradle info
:info
Current Gradle version: 2.10
BUILD SUCCESSFUL
Total time: 0.829 secs

现在,我们将在我们的构建文件中创建一个新的任务定义,并使其成为一个增强的任务。我们将在构建文件中创建一个新类,该类扩展 org.gradle.api.DefaultTask。我们将通过添加一个新方法来为该类编写一个实现。为了表明该方法是类的动作,我们将使用 @TaskAction注解。

在我们定义了我们的任务类之后,我们可以在我们的构建文件中使用它。我们将向 tasks 项目容器添加一个任务,并使用 type 属性来引用我们的新任务类。

在以下示例构建文件中,我们有一个新的 InfoTask 任务类和使用这个新任务类的info 任务:

/** 
* New class that defines a Gradle task. 
*/ 
class InfoTask extends DefaultTask { 
 
    /** 
    * Method that has the logic for the task. 
    * We tell this to Gradle with the @TaskAction annotation. 
    */ 
    @TaskAction 
    def info() { 
        // Show current Gradle version. 
        println "Current Gradle           
        version:$project.gradle.gradleVersion" 
    } 
} 
 
// Define new task in our Gradle build file 
// with name info and of type InfoTask. 
// InfoTask implementation is at the top. 
task info(type: InfoTask) 

接下来,我们将使用 info 任务运行我们的构建文件。在以下输出中,我们可以看到我们当前的 Gradle 版本:

$ gradle info
:info
Current Gradle version:2.10
BUILD SUCCESSFUL
Total time: 0.6 secs

要自定义我们的简单任务,我们可以向我们的任务添加属性。当我们在构建文件中配置任务时,我们可以为这些属性分配值。

对于我们的示例任务,我们将首先添加一个 prefix 属性。当我们打印 Gradle 版本而不是 'Current Gradle version' 文本时使用此属性。我们给它一个默认值,所以当我们使用任务并且不设置属性值时,我们仍然会得到一个有意义的前缀。由于默认值,我们可以使用  @Optional 注释将我们的属性标记为可选。通过这种方式,我们记录了在使用任务时不需要配置我们的属性。

如果我们想在输出中添加另一个前缀,我们可以在构建文件中配置 info 任务。我们将 'Running Gradle'值分配给我们的 InfoTask的前缀属性:

/** 
* New class that defines a Gradle task. 
*/ 
class InfoTask extends DefaultTask { 
 
    /** 
    * An optional property for our task. 
    */ 
    @Optional 
    String prefix = 'Current Gradle version' 
 
    /** 
    * Method that has the logic for the task. 
    * We tell this to Gradle with the @TaskAction annotation. 
    */ 
    @TaskAction 
    def info() { 
        // Show current Gradle version. 
        println "$prefix: $project.gradle.gradleVersion" 
    } 
} 
 
// Define new task in our Gradle build file 
// with name info and of type InfoTask. 
// InfoTask implementation is at the top. 
// We give the optional property prefix a value. 
task info(type: InfoTask) { 
    prefix = 'Running Gradle' 
} 

现在,如果我们运行我们的构建文件,我们可以在输出中看到我们的新前缀值:

$ gradle info
:info
Running Gradle: 2.10
BUILD SUCCESSFUL
Total time: 0.588 secs

Using incremental build support

我们知道 Gradle 支持增量构建。这意味着 Gradle 可以检查任务是否对文件、目录和属性的输入或输出有任何依赖关系。如果自上次构建以来这些都没有更改,则不会执行任务。我们将讨论如何将注释与我们的任务属性一起使用,以确保我们的任务支持 Gradle 的增量构建功能。

我们已经了解了如何使用我们迄今为止创建的任务的 inputs 和 outputs 属性。为了指示我们新的增强任务的属性,即输入和输出属性,Gradle 的增量支持使用的属性,我们必须在类定义中添加某些注释。我们可以将注解分配给  field 属性或属性的 getter 方法。

在前一章中,我们创建了一个读取 XML 源文件并将内容转换为文本文件的任务。让我们为此功能创建一个新的增强任务。我们将对保存源 XML 文件值的属性使用 @InputFile 注释。  @OutputFile 注解分配给保存输出文件的属性,如下所示:

class ConvertTask extends DefaultTask { 
 
    /** 
    * Input file for this task and by 
    * using the @InputFile annotation we 
    * tell Gradle the file can be used 
    * for determining incremental build support. 
    */ 
    @InputFile 
    File source 
 
    /** 
    * Output file for this task and by 
    * using the @OutputFile annotation we 
    * tell Gradle the file can be used 
    * for determining incremental build support. 
    */ 
    @OutputFile 
    File output 
 
    /** 
    * Method with the real implementation of the task. 
    * We convert the source file and save the output. 
    */ 
    @TaskAction 
    void convert() { 
        def xml = new XmlSlurper().parse(source) 
        output.withPrintWriter { writer -> 
            xml.person.each { person -> 
                writer.println "${person.name},${person.email}" 
            } 
        } 
        println "Converted ${source.name} to ${output.name}" 
    } 
} 
 
// Configure task for this build 
task convert(type: ConvertTask) { 
    source = file("src/people.xml") 
    output = file("$buildDir/convert-output.txt") 
} 

让我们在当前目录中创建一个名为 people.xml 的 XML 文件,代码如下:

<?xml version="1.0"?> 
<people> 
    <person> 
        <name>mrhaki</name> 
        <email>[email protected]</email> 
    </person> 
</people> 

现在,我们可以在构建文件中调用 convert 任务。我们可以在输出中看到文件已转换:

$ gradle convert
:convert
Converted people.xml to convert-output.txt
BUILD SUCCESSFUL
Total time: 0.99 secs

如果我们查看 convert-output.txt 文件的内容,我们将从源文件中看到以下值:

$ cat build/convert-output.txt
mrhaki,[email protected]

当我们第二次调用convert任务时,可以看到Gradle的增量构建支持已经注意到输入输出文件没有变化,所以我们的任务到了日期:

$ gradle convert
:convert UP-TO-DATE
BUILD SUCCESSFUL
Total time: 0.621 secs

下表显示了我们可以用来指示我们增强任务的输入和输出属性的注释:

注解名称

说明

@Input

指示该属性指定一个输入值。当此属性的值更改时,任务不再是最新的。

@InputFile

指示该属性是输入文件。将此用于引用 File 类型的单个文件的属性。

@InputFiles

将属性标记为包含 File 对象集合的属性的输入文件。

@InputDirectory

指示该属性是输入目录。将此用于引用目录结构的 File 类型属性。

@OutputFile

指示该属性是一个输出文件。将此用于引用 File 类型的单个文件的属性。

@OutputFiles

将属性标记为包含 File 对象集合的属性的输出文件。

@OutputDirectory

指示该属性是一个输出目录。将此用于引用目录结构的 File 类型属性。如果输出目录不存在,它将被创建。

@OutputDirectories

将属性标记为输出目录 将此属性用于引用 File 对象集合的属性,这些对象是对目录结构的引用。

@Optional

如果应用于前面的任何注释,我们会将其标记为可选。该值不必应用于此属性。

@Nested

我们可以将此注解应用于 JavaBean 属性。检查 bean 对象是否有任何前面的注释。这样,我们可以使用任意对象作为输入或输出属性。

Creating a task in the project source directory

在上一节中,我们在同一个构建文件中定义并使用了我们自己的增强任务。现在我们将从构建文件中提取类定义并将其放在一个单独的文件中。我们将把文件放在 buildSrc 项目源目录中。

让我们将 InfoTask 移动到项目的 buildSrc 目录。我们将首先创建 buildSrc/src/main/groovy/sample 目录。我们将在这个目录下创建一个InfoTask.groovy文件,代码如下:

package sample 
 
import org.gradle.api.DefaultTask 
import org.gradle.api.tasks.TaskAction 
 
class InfoTask extends DefaultTask { 
    /** 
    * Task property can be changed by user 
    * of this task. 
    */ 
    String prefix = 'Current Gradle version' 
 
    /** 
    * Method with actual implementation for this task. 
    */ 
    @TaskAction 
    def info() { 
        println "$prefix: $project.gradle.gradleVersion" 
    } 
} 

请注意,我们必须为 Gradle API 的类添加 import 语句。这些导入由 Gradle 隐式添加到构建脚本中;但如果我们在构建脚本之外定义任务,我们必须自己添加import 语句。

在我们的项目构建文件中,我们只需要创建一个新的 info 任务 InfoTask 类型。请注意,我们必须使用包名来标识我们的 InfoTask 类或添加 import sample.InfoTask 语句:

// Define new task of type sample.InfoTask. 
task info(type: sample.InfoTask) { 
    // Set task property/ 
    prefix = "Running Gradle" 
} 

如果我们运行构建,我们可以看到Gradle首先编译了InfoTask.groovy源文件,如下:

$ gradle info
:buildSrc:compileJava UP-TO-DATE
:buildSrc:compileGroovy
:buildSrc:processResources UP-TO-DATE
:buildSrc:classes
:buildSrc:jar
:buildSrc:assemble
:buildSrc:compileTestJava UP-TO-DATE
:buildSrc:compileTestGroovy UP-TO-DATE
:buildSrc:processTestResources UP-TO-DATE
:buildSrc:testClasses UP-TO-DATE
:buildSrc:test UP-TO-DATE
:buildSrc:check UP-TO-DATE
:buildSrc:build
:info
Running Gradle: 2.10
BUILD SUCCESSFUL
Total time: 1.751 secs

事实上,执行buildSrc目录下的build任务。我们可以通过添加一个 build.gradle文件来自定义 buildSrc目录的构建。在这个文件中,我们可以配置任务、添加新任务,并且实际上可以在普通项目构建文件中做任何我们能做的事情。  buildSrc 目录甚至可以是多项目构建。

让我们在buildSrc目录中添加一个新的build.gradle文件。我们将向 build 任务添加一个简单的操作,它会打印 'Done building buildSrc' 值:

// File: buildSrc/build.gradle 
 
// Add new action to the build task 
// for the buildSrc directory. 
build.doLast { 
    println 'Done building buildSrc' 
} 

如果我们运行我们的项目构建,我们可以看到以下输出:

$ gradle info
:buildSrc:compileJava UP-TO-DATE
:buildSrc:compileGroovy UP-TO-DATE
:buildSrc:processResources UP-TO-DATE
:buildSrc:classes UP-TO-DATE
:buildSrc:jar UP-TO-DATE
:buildSrc:assemble UP-TO-DATE
:buildSrc:compileTestJava UP-TO-DATE
:buildSrc:compileTestGroovy UP-TO-DATE
:buildSrc:processTestResources UP-TO-DATE
:buildSrc:testClasses UP-TO-DATE
:buildSrc:test UP-TO-DATE
:buildSrc:check UP-TO-DATE
:buildSrc:build
Done building buildSrc
:info
Running Gradle: 2.10
BUILD SUCCESSFUL
Total time: 0.699 secs

Writing tests

由于 buildSrc 目录与任何其他 Java/Groovy 项目相似,我们也可以为我们的任务创建测试。我们的目录结构与 Java/Groovy 项目的目录结构相同,我们还可以在 build.gradle 文件中定义额外的依赖项。

如果我们想在我们的测试类中访问 Project 对象,我们可以使用 org.gradle.testfixtures.ProjectBuilder 类.使用这个类,我们可以配置一个Project 对象并在我们的测试用例中使用它。在使用 build()方法创建新的 项目之前,我们可以选择配置名称、父级和项目目录目的。我们可以使用 Project对象,例如,添加一个新的增强任务类型的新任务,看看有没有错误。 ProjectBuilder 用于低级测试。不执行实际任务。

在下面的JUnit测试中,我们将测试是否可以设置属性值。我们有第二个测试来检查 InfoTask 类型的任务是否被添加到项目的任务容器中:

package sample 
 
import org.junit.* 
import org.gradle.api.* 
import org.gradle.testfixtures.ProjectBuilder 
 
class InfoTaskTest { 
 
    @Test 
    void createTaskInProject() { 
        final Task newTask = createInfoTask() 
        assert newTask instanceof InfoTask 
    } 
 
    @Test 
    void propertyValueIsSet() { 
        final Task newTask = createInfoTask() 
        newTask.configure { 
            prefix = 'Test' 
        } 
        assert newTask.prefix == 'Test' 
    } 
 
    private Task createInfoTask() { 
        // We cannot use new InfoTask() to create a new instance, 
        // but we must use the Project.task() method. 
        final Project project = ProjectBuilder.builder().build() 
        project.task('info', type: InfoTask) 
    } 
} 

在我们的 build.gradle 文件中 buildSrc 目录中,我们必须添加一个 Maven 存储库和对 JUnit 库的依赖,使用以下代码行:

repositories { 
    jcenter() 
} 
 
dependencies { 
    testCompile 'junit:junit:4.12' 
} 

我们的测试会自动执行,因为 test 任务是 buildSrc 目录构建过程的一部分。

Creating a task in a standalone project


为了使一个任务可被其他项目重用,我们必须有一种分配任务的方法。另外,其他想要使用该任务的项目必须能够找到我们的任务。我们将看到如何在存储库中发布我们的任务,以及其他项目如何在他们的项目中使用该任务。

我们已经看到了如何将构建文件中的任务实现放在 buildSrc 目录中。  buildSrc 目录类似于普通的 Gradle 构建项目,因此很容易为我们的任务创建一个独立的项目。我们只需要将 buildSrc目录的内容复制到我们新创建的项目目录中。

让我们创建一个新的项目目录并复制 buildSrc 目录的内容。我们必须编辑我们独立项目的 build.gradle 文件。当 build.gradle文件在 buildSrc 目录。现在我们有一个独立的项目,我们必须自己添加这些依赖项。

以下 build.gradle 文件包含构建工件并将其部署到本地分发目录所需的所有定义。我们还可以定义一个公司内部网存储库,以便其他项目可以在他们的项目中重用我们的 InfoTask

apply plugin: 'groovy' 
apply plugin: 'maven' 
 
version = '1.0' 
group = 'sample.infotask' 
 
// Set the name for the archive file 
// with the code. Is used for deploying 
// to Maven repository. 
archivesBaseName = 'infotask' 
 
dependencies { 
    // Define dependency on Gradle API classes. 
    compile gradleApi() 
 
    // Define dependency on the Groovy version 
    // that is part of the Gradle distribution. 
    compile localGroovy() 
} 
 
uploadArchives { 
    repositories { 
        mavenDeployer { 
            // For our example we deploy to a local 
            // directory lib. 
            repository(url: 'file:../lib') 
        } 
    } 
} 

当我们调用uploadArchives任务发布我们打包的 InfoTask到 ../ lib 目录,我们将看到以下输出:

$ gradle uploadArchives
:compileJava UP-TO-DATE
:compileGroovy
:processResources UP-TO-DATE
:classes
:jar
:uploadArchives
BUILD SUCCESSFUL
Total time: 2.223 secs

我们已经发布了我们的任务,其他项目可以在他们的构建中使用它。请记住,项目的 buildSrc 目录中的任何内容都会自动添加到构建的类路径中。但是,如果我们有一个已发布的带有任务的工件,这将不会自动发生。我们必须配置我们的构建并将工件添加为构建脚本的依赖项。

我们将在构建中使用 buildscript{} 脚本块来配置 Gradle 项目的类路径。要将我们发布的 InfoTask 包含在新项目中,我们必须将工件添加为我们构建的 classpath 配置依赖项。

我们将创建一个新目录并将以下 build.gradle 文件添加到该目录:

buildscript { 
    repositories { 
        maven { 
            // Set Maven repository to the local 
            // directory we also used to publish 
            // our custom Gradle task. 
            url = 'file:../lib' 
        } 
    } 
    dependencies { 
        // Define dependency on the InfoTask implementation 
        // for this build script. 
        classpath group: 'sample.infotask', 
            name: 'infotask', 
            version: '1.0' 
    } 
} 
 
task info(type: sample.InfoTask) 

接下来,我们可以运行构建并在输出中看到 InfoTask 已执行:

$ gradle info
:info
Current Gradle version: 2.10
BUILD SUCCESSFUL
Total time: 0.73 secs

Creating a custom plugin


Gradle 的一大特色是对插件的支持。插件可以包含任务、配置、属性、方法、概念等,以便为我们的项目添加额外的功能。例如,如果我们将 Java 插件应用到我们的项目中,我们可以立即调用编译、测试和构建任务。我们还有可以使用的新依赖配置和可以配置的额外属性。 Java 插件本身应用 Java 基础插件。 Java 基础插件没有引入任务,但它引入了源集的概念。这是创建我们自己的插件的好模式,其中一个基本插件引入了新概念,另一个插件从基本插件派生并添加了显式的构建逻辑类任务。

因此,插件是分发我们希望在项目之间共享的构建逻辑的好方法。我们可以编写自己的插件,给它一个明确的版本,然后发布它;例如,存储库。然后,其他项目可以通过简单地将插件应用到项目来重用该功能。我们可以创建自己的插件并在我们的项目中使用它们。我们将从在构建文件中定义插件开始。

Creating a plugin in the build file

我们可以在项目构建文件中创建自定义插件。与自定义任务类似,我们可以使用插件的逻辑添加新的类定义。我们必须实现 org.gradle.api.Plugin<T> 接口。该接口有一个 apply()方法。当我们编写自己的插件时,我们必须重写这个方法。该方法接受一个对象作为参数。对象的类型与泛型的 T类型相同。当我们为项目创建插件时,使用Project 类型。我们还可以为其他 Gradle 类型编写插件,例如任务。那么我们必须使用 Task类型。

我们将创建一个简单的插件来打印 Gradle 版本。该插件向项目添加了一个新的 info 任务。以下示例构建文件定义了一个新的 InfoPlugin 插件。我们将覆盖 apply() 方法并向项目添加一个名为 info 的新任务。此任务打印 Gradle 版本。在构建文件的顶部,我们将使用 apply() 方法并通过名称引用插件 InfoPlugin ,这是插件的类名:

/** 
* Plugin class that adds a new task to the project. 
*/ 
class InfoPlugin implements Plugin<Project> { 
 
    void apply(Project project) { 
        // Add new info task to the project. 
        project.tasks.create('info') << { 
            println "Running Gradle: $project.gradle.gradleVersion" 
        } 
    } 
 
} 
 
apply plugin: InfoPlugin 

在命令行中,我们可以在运行 Gradle 时调用 info 任务。我们可以在以下输出中看到 Gradle 版本:

$ gradle info
:info
Running Gradle: 2.10
BUILD SUCCESSFUL
Total time: 0.567 secs

info 任务总是在 Gradle 版本之前打印相同的文本。我们可以重写任务并使文本可配置。 Gradle 项目有一个关联的 ExtensionContainer 对象。这个对象可以保存我们想要传递给插件的所有设置和属性。我们可以将 JavaBean 添加到 ExtensionContainer,以便我们可以从构建文件中配置 bean 的属性。  JavaBean 是一个所谓的扩展对象

在我们的示例构建文件中,我们将首先添加一个具有String 属性前缀的新 InfoPluginExtension 类。这是我们添加到 ExtensionContainer 的符合 JavaBean 的类。在 apply()方法中,我们将使用 create()方法>ExtensionContainer 添加 InfoPluginExtension 名称为 info 到项目中。在构建文件中,我们将使用 info 配置闭包配置 prefix 属性。我们也可以通过 info扩展对象简单地引用 prefix属性:

/** 
* Simple JavaBean class that acts as the 
* extension point for the InfoPlugin. 
*/ 
class InfoPluginExtension { 
 
    /** 
    * Define property that can be set by 
    * the user of the InfoPlugin. 
    */ 
    String prefix = 'Running Gradle' 
 
} 
 
/** 
* Plugin class that adds a new task to the project. 
*/ 
class InfoPlugin implements Plugin<Project> { 
 
    void apply(Project project) { 
        // Add InfoPluginExtension to the project. 
        // The user can add a info{} configuration block in 
        // the build file to configure the plugin. 
        project.extensions.create('info', InfoPluginExtension) 
 
        // Add new info task to the project. 
        project.tasks.create('info') << { 
            // Use prefix set via info extension. 
            println "$project.info.prefix: $project.gradle.gradleVersion" 
        } 
    } 
 
} 
 
apply plugin: InfoPlugin 
 
info { 
    prefix = 'Gradle version' 
} 

如果我们运行 info 任务,我们将在输出中看到我们配置的 prefix

$ gradle info
:info
Gradle version: 2.10
BUILD SUCCESSFUL
Total time: 0.62 secs

Creating a plugin in the project source directory


我们已经定义了插件并在同一个构建文件中使用它。我们将看到如何从构建文件中提取插件代码并将其放在项目源目录中的单独源文件中。此外,我们将讨论如何测试插件。

当我们在构建文件中定义插件时,我们不能在其他项目中重用它。我们现在在同一个文件中有了插件的定义和使用。为了分离定义和使用,我们可以在 Gradle 项目的 buildSrc 目录下创建插件类。在 Gradle 多项目中,我们必须使用根项目的 buildSrc 目录。这意味着对于多项目构建,我们可以在多项目构建的其他项目中重用插件。

我们在编写自定义任务时已经讨论过 buildSrc 目录中的任何源代码都会自动编译并添加到项目的类路径中。首先,我们将创建 buildSrc/src/main/groovy/sample 目录。在此目录中,我们将创建一个 InfoPlugin.groovy 文件,其代码如下:

package sample 
 
import org.gradle.api.* 
 
/** 
* Gradle plugin to show Gradle version. 
*/ 
class InfoPlugin implements Plugin<Project> { 
 
    void apply(Project project) { 
        // Add InfoPluginExtension to project accessible 
        // via info{} configuration block. 
        project.extensions.create('info', InfoPluginExtension) 
 
        // Add info task to project. 
        project.tasks.create('info') << { 
            println "$project.info.prefix: $project.gradle.gradleVersion" 
        } 
    } 
 
} 

接下来,我们将在目录中创建 InfoPluginExtension.groovy 文件:

package sample 
 
/** 
* Extension class for the InfoPlugin. 
*/ 
class InfoPluginExtension { 
 
    /** 
    * Used in InfoPlugin. 
    */ 
    String prefix 
} 

在项目根目录的构建文件中,我们将使用包和类名引用我们的插件:

// Apply InfoPlugin from buildSrc directory. 
apply plugin: sample.InfoPlugin 
 
// Configure InfoPlugin via InfoPluginExtension. 
info { 
    prefix = 'Gradle version' 
} 

当我们运行  info 任务时,我们将在输出中看到首先编译插件代码,然后是  info 任务执行:

$ gradle info
:buildSrc:clean
:buildSrc:compileJava UP-TO-DATE
:buildSrc:compileGroovy
:buildSrc:processResources UP-TO-DATE
:buildSrc:classes
:buildSrc:jar
:buildSrc:assemble
:buildSrc:compileTestJava UP-TO-DATE
:buildSrc:compileTestGroovy UP-TO-DATE
:buildSrc:processTestResources UP-TO-DATE
:buildSrc:testClasses UP-TO-DATE
:buildSrc:test UP-TO-DATE
:buildSrc:check UP-TO-DATE
:buildSrc:build
:info
Gradle version: 2.10
BUILD SUCCESSFUL
Total time: 1.815 secs

Testing a plugin

buildSrc 目录中为项目执行的任务之一是测试任务。我们可以编写测试用例来测试插件代码,就像在任何其他项目中一样。我们在buildSrc 中添加一个build.gradle 文件,并定义JUnit 测试框架的依赖关系。在以下示例构建文件中,我们将为 JUnit 添加一个依赖项:

repositories { 
    jcenter() 
} 
 
dependencies { 
    testCompile 'junit:junit:4.12' 
} 

接下来,我们可以在 buildSrc/src/test/groovy/sample目录下添加一个InfoPluginTest.groovy测试用例:

package sample 
 
import org.gradle.api.* 
import org.gradle.testfixtures.ProjectBuilder 
import org.junit.* 
 
class InfoPluginTest { 
 
    @Test 
    void infoTaskIsAddedToProject() { 
        final Project project = ProjectBuilder.builder().build() 
        project.apply plugin: sample.InfoPlugin 
        assert project.tasks.findByName('info') 
    } 
 
    @Test 
    void configurePrefix() { 
        final Project project = ProjectBuilder.builder().build() 
        project.apply plugin: sample.InfoPlugin 
        project.info.prefix = 'Sample' 
        assert project.info.prefix == 'Sample' 
    } 
 
} 

我们使用 ProjectBuilder 类为Project 对象创建一个fixture。我们可以将插件应用到项目中,然后测试一下info任务是否可用。  Project对象无法执行项目中的任务;它仅适用于像这样的简单检查。

当我们从命令行调用 info 任务时,我们的测试类被编译并执行。如果测试失败,项目将中止;但如果所有测试都通过,项目将继续。

Creating a plugin in a standalone project


我们已经在项目源目录中定义了我们的插件,但是我们不能在另一个项目中重用它。我们将讨论如何使用独立项目分发我们的插件逻辑。此外,我们将了解如何在其他项目中使用该插件。

通过将插件代码放在 buildSrc 目录中,我们将插件的定义和使用分开。该插件仍然不能被其他项目使用。为了使插件可重用,我们将创建一个独立项目并使用插件代码创建一个工件并将工件发布到存储库。然后其他项目可以从存储库中获取插件,并使用项目中插件的构建逻辑。

我们已经在 buildSrc 目录中获得了插件的代码和测试代码(来自上一节)。我们可以将此代码复制到包含插件项目的新目录中。在这个新目录中,我们还必须创建一个 build.gradle 文件。在 buildSrc 目录中添加到项目的隐式依赖项和插件必须在独立项目中显式。

让我们在 plugin 目录中创建一个新的 Gradle 项目,同时创建具有以下内容的build.gradle 文件:

apply plugin: 'groovy' 
apply plugin: 'maven' 
 
version = '1.0' 
group = 'sample.infoplugin' 
 
// Set name of archive containing the plugin. 
archivesBaseName = 'infoplugin' 
 
repositories { 
    jcenter() 
} 
 
dependencies { 
    // Define dependency on the Gradle API. 
    compile gradleApi() 
 
    // Define dependency on the Groovy version 
    // bundled with Gradle. 
    compile localGroovy() 
 
    testCompile 'junit:junit:4.12' 
} 
 
uploadArchives { 
    repositories { 
        mavenDeployer { 
            // Define a local directory as the 
            // Maven repository where the plugin 
            // is deployed to. 
            repository(url: 'file:../lib') 
        } 
    } 
} 

接下来,我们将创建 plugin/src/main/groovy/sample 和 plugin/src/test/groovy/sample目录。我们将 InfoPlugin.groovy和 InfoPluginExtension.groovy文件复制到 src/ main/groovy/sample 目录和 InfoPluginTest.groovy 文件到 plugin/src/test/groovy/sample< /代码>目录。

到目前为止,我们拥有使用插件代码创建工件 JAR 文件的所有要素。工件被部署到本地 ../lib 目录。当然,我们可以定义任何 Maven 或 Ivy 存储库来部署插件工件。

为了确保 Gradle 可以找到插件,我们必须在 plugin/src/main/resources/META-INF 中提供一个 properties 文件/gradle-plugins 目录与我们的插件名称。属性文件有一个 implementation-class 属性键和 Plugin 类的完整类名。

我们想将我们的插件命名为info,所以在 plugin/src/main/resources/META-INF/gradle-plugins 目录,我们将使用以下代码创建 info.properties 文件:

implementation-class = sample.InfoPlugin 

我们已准备好使用插件创建工件并将其上传到我们的存储库。我们将调用 uploadArchives 任务并获得以下输出:

$ gradle uploadArchives
:compileJava UP-TO-DATE
:compileGroovy
:processResources
:classes
:jar
:uploadArchives
BUILD SUCCESSFUL
Total time: 4.991 secs

该插件现在位于存储库中。要使用该插件,我们必须创建一个新的 Gradle 项目。我们必须扩展这个新项目的类路径并将插件作为依赖项包含在内。我们使用 buildscript{} 脚本块,我们可以在其中配置存储库位置和 classpath 依赖项。对于我们的示例,我们将引用本地 ../lib 目录。在依赖项部分,我们将 classpath 配置设置为 InfoPlugin 工件。

以下示例构建文件包含定义:

buildscript { 
    repositories { 
        maven { 
            // Define local directory lib 
            // as Maven repository. This directory 
            // contains the plugin we build ourselves. 
            url = 'file:../lib' 
        } 
    } 
 
    dependencies { 
        classpath group: 'sample.infoplugin', 
            name: 'infoplugin', 
            version: '1.0' 
    } 
} 
 
// Apply plugin. We can use the value 'info', 
// because we packaged our plugin with the file 
// info.properties. 
apply plugin: 'info' 
 
info { 
    // Set prefix property for InfoPlugin. 
    prefix = "Gradle version" 
} 

我们的项目现在有来自插件的 info 任务。我们可以通过info 对象或配置闭包来配置插件扩展。

如果我们运行 info 任务,我们将得到以下输出:

$ gradle info
:info
Gradle version: 2.10
BUILD SUCCESSFUL
Total time: 0.739 secs

Summary


在本章中,我们讨论了如何创建自己的增强任务。我们还看到了如何在构建文件中添加类定义并直接在构建中使用它。

如果我们将任务定义放在 Gradle 项目或多项目构建的 buildSrc 目录中,我们可以在 Gradle 构建的上下文中重用任务。此外,我们现在已经很好地分离了任务的定义和配置。

然后,我们讨论了如何将任务作为工件发布到存储库。其他项目可以使用 buildscript{} 脚本块将任务包含在其类路径中。然后,我们就可以在项目中配置和使用任务了。

在本章中,我们还讨论了如何编写自己的 Gradle 插件。我们已经看到了如何将插件类添加到我们的 Gradle 构建文件中。然后你学会了使用 buildSrc 目录并将插件的源代码放在那里。

最后,为了使插件在其他项目中真正可重用,我们将插件代码放在一个单独的项目中。然后将插件代码打包到 JAR 文件中并发布到存储库。然后其他项目可以在插件中定义依赖项并使用插件中的构建逻辑。

在下一章中,我们将看到如何在持续集成工具中使用 Gradle。