Skipping tasks

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.

Using onlyIf predicates

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()
    }
})

Skipping tasks by throwing StopExecutionException

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

Enabling and disabling tasks

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

Skipping from the command line

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.

Skipping tasks that are up-to-date

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"
    }
}
..................Content has been hidden....................

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