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.
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.
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
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:
3.143.239.44