Chapter 10. Writing Custom Tasks and Plugins

In Gradle, we can either write a simple task in a build file where we add actions with a closure, or we can configure an existing task that is included with Gradle. The process of writing our own task is easy. There are different ways in which we can create a custom task, which we will cover in this chapter.

We will see how we can create a new task class in our build file and use it in our project. Next, we will learn how to create custom tasks in a separate source file. We also learn in this chapter how we can make our task reusable in other projects.

We will learn how to write a plugin for Gradle. Similar to writing custom tasks, we will cover the different ways to write a plugin. We will also see how we can publish our plugin and learn how we can use it in a new project.

We can write our tasks and plugins in Groovy, which works very well with the Gradle API, but we can also use other languages, such as Java and Scala. As long as the code is compiled into bytecode, we are fine.

Creating a custom task

When we create a new task in a build and specify a task with the type property, we actually configure an existing task. The existing task is called an enhanced task in Gradle. For example, the Copy task type is an enhanced task. We configure the task in our build file, but the implementation of the Copy task is in a separate class file. It is a good practice to separate the task usage from the task implementation. It improves the maintainability and reusability of the task. In this section, we are creating our own enhanced tasks.

Creating a custom task in the build file

First, let's see how we can create a task to display the current Gradle version in our build by simply adding a new task with a simple action. We have seen these types of tasks earlier in other sample build files. In the following sample build, we create a new info task:

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

When we invoke the info task from the command line, we see the following output:

gradle info
:info
Current Gradle version: 1.1

BUILD SUCCESSFUL

Total time: 2.061 secs

Now, we are going to create a new task definition in our build file and make it an enhanced task. We create a new class in our build file and this class extends org.gradle.api.DefaultTask. We write an implementation for the class by adding a new method. To indicate that the method is the action of the class, we use the annotation @TaskAction.

After we have defined our task class, we can use it in our build file. We add a task to the project tasks container and use the type property to reference our new task class.

In the following sample build file, we have a new task class InfoTask and the task info that uses this new task class:

// Use the InfoTask we defined
task info(type: InfoTask)

defaultTasks 'info'

/*** Show current Gradle version.
 */
class InfoTask extends DefaultTask {

    @TaskAction
    def info() {
        println "Current Gradle version:$project.gradle.gradleVersion"
    }
}

When we run our build file, the info task is invoked automatically because it is a part of the default tasks for the project. In the following output, we can see our current Gradle version:

$ gradle
:info
Current Gradle version: 1.1

BUILD SUCCESSFUL

Total time: 2.116 secs

To customize our simple task, we can add properties to our task. We can assign values to these properties when we configure the task in our build file.

For our sample task, we first add a prefix property. This property is used when we print the Gradle version instead of the text 'Current Gradle version:'. We give it a default value, so when we use the task and don't set the property value, we still get a meaningful prefix. We can mark our property as optional, because of the default value, with the annotation @Optional. This way, we have documented that our property doesn't need to be configured when we use the task:

task info(type: InfoTask)

defaultTasks 'info'

class InfoTask extends DefaultTask {
    @Optional
    String prefix = 'Current Gradle version'

    @TaskAction
    def info() {
        println "$prefix: $project.gradle.gradleVersion"
    }
}

If we want another prefix in our output, we can configure the info task in our build file. We assign the 'RunningGradle' value to the prefix property of our InfoTask:

task info(type: InfoTask) {
    prefix = 'Running Gradle'
}

defaultTasks 'info'

class InfoTask extends DefaultTask {

    String prefix = 'Current Gradle version'

    @TaskAction
    def info() {
        println "$prefix: $project.gradle.gradleVersion"
    }
}

Now, if we run our build file, we can see our new prefix value in the output:

$ gradle
:info
Running Gradle: 1.1

BUILD SUCCESSFUL

Total time: 2.139 secs

Using incremental build support

We know Gradle supports incremental builds. This means that Gradle can check if a task has any dependencies for input or output on files, directories, and properties. If none of these have changed since the last build, the task is not executed. We will learn how we can use annotations with our task properties to make sure our task supports Gradle's incremental build feature.

We have seen how we can use the inputs and outputs properties of tasks we have created so far. To indicate which properties of our new enhanced tasks are input and output properties, the ones used by Gradle's incremental support, we must add certain annotations to our class definition. We can assign the annotation to the field property or the getter method for the property.

In a previous chapter, we have created a task that reads a XML source file and converts the contents to a text file. Let's create a new enhanced task for this functionality. We use the @InputFile annotation for the property that holds the value for the source XML file. The @OutputFile annotation is assigned to the property that holds the output file:

task convert(type: ConvertTask) {
    source = file('source.xml')
    output = file("$buildDir/convert-output.txt")
}

/**
 * Convert XML source file to text file.
 */
class ConvertTask extends DefaultTask {

    @InputFile
    File source

    @OutputFile
    File 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}"
    }
}

Let's create an XML file with the name source.xml in the current directory, with the following code:

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

Now, we can invoke the convert task in our build file. We can see in the output that the file is converted:

$ gradle convert
:convert
Converted source.xml to convert-output.txt

BUILD SUCCESSFUL

Total time: 3.8 secs

If we look at the contents of the convert-output.txt file, we see the following values from the source file:

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

When we invoke the convert task for the second time, we can see Gradle's incremental build support has noticed that the input and output file haven't changed, so our task is up-to-date:

$ gradle convert
:convert UP-TO-DATE

BUILD SUCCESSFUL

Total time: 1.664 secs

The following table shows the annotations we can use to indicate the input and output properties of our enhanced task:

Annotation Name

Description

@Input

Indicates property specifies an input value. When the value of this property changes, the task is not longer up-to-date.

@InputFile

Indicates property is an input file. Use this for properties that reference a single file of type File.

@InputFiles

Mark property as input files for a property that holds a collection of File objects.

@InputDirectory

Indicates property is an input directory. Use this for a File type property that references a directory structure.

@OutputFile

Indicates property as output file. Use this for properties that reference a single file of type File.

@OutputFiles

Mark property as output files for a property that holds a collection of File objects.

@OuputDirectory

Indicates property is an output directory. Use this for a File type property that references a directory structure. If the output directory doesn't exist, it will be created.

@OutputDirectories

Mark property is an output directory Use this for a property that references a collection of File objects, which are references to directory structures.

@Optional

If applied to any of the above annotations, we mark it as optional. The value doesn't have to be applied for this property.

@Nested

We can apply this annotation to a Java Bean property. The bean object is checked for any of the above annotations. This way, we can use arbitrary objects as input or output properties.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.254.151