Sometimes, we want tasks to be excluded from a build. In certain circumstances, we just want to skip a task and continue executing other tasks. We can use several methods to skip tasks in Gradle.
Every task has a method onlyIf
that accepts a closure as an argument. The result of the closure must be true
or false
. If the task must be skipped, the result of the closure must be false
, otherwise the task is executed. The task
object is passed as a parameter to the closure. Gradle evaluates the closure just before the task is executed.
The following build file will skip the task longrunning
, if the file is executed during weekdays, but will execute it during the weekend:
import static java.util.Calendar.* task longrunning { onlyIf { task -> def now = Calendar.instance def weekDay = now[DAY_OF_WEEK] def weekDayInWeekend = weekDay in [SATURDAY, SUNDAY] return weekDayInWeekend } doLast { println "Do long running stuff" } }
If we run our build during weekdays, we get the following output:
$ gradle longrunning :longrunning SKIPPED BUILD SUCCESSFUL Total time: 2.448 secs
And if we run the build during the weekend, we see that the task is executed:
$ gradle longrunning :longrunning Do long running stuff BUILD SUCCESSFUL Total time: 1.823 secs
We can invoke the
onlyIf
method multiple times for a task. If one of the predicates returns false
, the task is skipped. Besides using a closure to define the condition that determines if the task needs to be executed or not, we can use an implementation of the org.gradle.api.specs.Spec
interface. The Spec
interface has one method: isSatisfiedBy
. We must write an implementation and return true
if the task must be executed or false
if we want the task to be skipped. The current task
object is passed as a parameter to the isSatisfiedBy
method.
In the following sample we check if a file exists. And if the file exists we can execute the task, otherwise the task is skipped:
def file = new File('data.sample') task 'handleFile' << { println "Work with file ${file.name}" } handleFile.onlyIf(new Spec() { boolean isSatisfiedBy(task) { file.exists() } })
Another way to the skip execution of a task is to throw a StopExecutionException
exception. If such an exception is thrown, the build will stop the current task and continue with the next task. We can use the doFirst
method to add a precondition check for a task. In the closure, when we pass to the doFirst
method, we can check for a condition and throw a StopExecutionException
exception if necessary.
In the following build script, we check if the script is executed during working hours. If so, the exception is thrown and task first
is skipped:
def printTaskName = { task -> println "Running ${task.name}" } task first << printTaskName first.doFirst { def today = Calendar.instance def workingHours = today[Calendar.HOUR_OF_DAY] in 8..17 if (workingHours) { throw new StopExecutionException() } } task second(dependsOn: 'first') << printTaskName
If we run our script during working hours and look at the output of our build script, we will notice that we cannot see the task has been skipped. If we use the
onlyIf
method, Gradle will add SKIPPED
to a task that is not executed:
$ gradle second :first :second Running second BUILD SUCCESSFUL Total time: 2.174 secs
We have seen how we can skip tasks
with the onlyIf
method or by throwing StopExecutionException
. But we can also use another method to skip a task. Every task has an enabled
property. By default, the value of the property is true
, which means the task is enabled and is executed. We can change the value and set it to false
to
disable the task and skip its execution.
In the following sample, we check for the existence of a directory, and if it exists, the enabled
property is set to true
, if not, it is set to false
:
task 'listDirectory' { def dir = new File('assemble') enabled = dir.exists() doLast { println "List directory contents: ${dir.listFiles().join(',')}" } }
If we run the task and the directory doesn't exist, we get the following output:
$ gradle listDirectory :listDirectory SKIPPED BUILD SUCCESSFUL Total time: 2.112 secs
If we run the task, and this time the directory exists, containing a single file with the name sample
, we get the following output:
$ gradle lD :listDirectory List directory contents: assemble/sample BUILD SUCCESSFUL Total time: 2.118 secs
Until now, we have defined the rules to skip a task in the build file. But we can use the --exclude-tasks
(-x
) command-line option if we run the build. We must define, as an argument, which task we want to exclude from the tasks to be executed.
The following script has three tasks with some task dependencies:
def printTaskName = { task -> println "Run ${task.name}" } task first << printTaskName task second(dependsOn: first) << printTaskName task third(dependsOn: [second, first]) << printTaskName
If we run the gradle
command and exclude task second
, we get the following output:
$ gradle third -x second :first Run first :third Run third BUILD SUCCESSFUL Total time: 1.618 secs
If our task third
didn't depend on task first
, only task third
would be executed.
Until now, we have defined conditions that are evaluated to determine whether a task needs to be skipped or not. But with Gradle, we can be even more flexible. Suppose we have a task that works on a file and generates some output based on the file. For example, a compile task fits this pattern. In the following sample build file, we have task convert
that will take an XML file, parse the contents, and write data to a text file.
task(convert) { def source = new File('source.xml') def output = new File('output.txt') doLast { 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}" } }
We can run this task a couple of times. Each time, the data is read from the XML file and written to the text file:
$ gradle -q convert Converted source.xml to output.txt $ gradle -q convert Converted source.xml to output.txt
But our input file hasn't changed between the task invocations, so the task doesn't have to be executed. We want the task to be executed only if the source file has changed or the output file is missing or has changed since the last run of the task.
Gradle supports this pattern; this support is known as incremental build support. A task only needs to be executed if necessary. This is a very powerful feature of Gradle. It will really speed up a build process, because only those tasks that need to be executed are executed.
We need to change the definition of our task, so that Gradle can determine whether the task needs to be executed based on changes in the input file or output file of the task. A task has the properties inputs
and outputs
that are used for this purpose. To define an input file, we invoke the file
method of the inputs
property with the value of our input file. We set the output file by invoking the
file
method of the outputs
property.
Let's rewrite our task to make it support Gradle's incremental build feature:
task(convert) { def source = new File('source.xml') def output = new File('output.txt') // Define input file inputs.file source // Define output file outputs.file output doLast { 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}" } }
When we run the build file a couple of times, we see that our task is skipped the second time we run it, because the input and output file haven't changed:
$ gradle convert : convert Converted source.xml to output.txt BUILD SUCCESSFUL Total time: 2.623 secs $ gradle convert :convert UP-TO-DATE BUILD SUCCESSFUL Total time: 1.53 secs
We have defined a single file for the inputs
and outputs
properties. But Gradle supports more ways to define values for these properties. The
inputs
property has methods to add a directory, multiple files, or even properties to be watched for changes. The outputs
property has methods to add a directory or multiple files to be monitored for changes. And if these methods are not appropriate for our build, we can even use the method upToDateWhen
for the outputs
property. We pass a closure or implementation of the org.gradle.api.specs.Spec
interface to define a predicate that determines whether the output of the task is up-to-date.
The following build script uses some of these methods:
project.version = '1.0' task createVersionDir { def outputDir = new File('output') // If project.version changes then the // task is no longer up-to-date inputs.property 'version', project.version outputs.dir outputDir doLast { println "Making directory ${outputDir.name}" mkdir outputDir } } task convertFiles { // Define multiple files to be checked as inputs. inputs.files 'input/input1.xml', 'input/input2.xml' // Or use inputs.dir 'input' to check a complete directory. // Use upToDateWhen method to define predicate. outputs.upToDateWhen { // If output directory contains any file which name // starts with output and has the txt extension, // then the task is up-to-date. new File('output').listFiles().any { it.name ==~ /output.*.txt$/ } } doLast { println "Running convertFiles" } }
13.58.51.228