When we create our own custom tasks we might need to support lazy evaluation of task properties. A Gradle build has three phases: initialisation, configuration and execution. Task properties can be set during the configuration phase, for example when a task is configured in a build file. But our task can also evaluate the value for a task property at execution time. To support evaluation during execution time we must write a lazy getter method for the property. Also we must define the task property with def
or type Object
. Because of the Object
type we can use different types to set a value. For example a Groovy Closure
or Callable
interface implementation can be used to execute later than during the configuration phase of our Gradle build. Inside the getter method we invoke the Closure
or Callable
to get the real value of the task property.
Let's see this with a example task. We create a simple class with two task properties: user
and outputFile
. The user
property must return a String
object and the outputFile
property a File
object. For the outputFile
property we write a getOutputFile
method. We delegate to the Project.file
method, which already accepts different argument types for lazy evaluation. We also write an implementation for getUser
method where we run a Closure
or Callable
object if that is used to set the property value.
// File: buildSrc/src/main/groovy/com/mrhaki/gradle/Hello.groovy package com.mrhaki.gradle import org.gradle.api.DefaultTask import org.gradle.api.tasks.TaskAction import java.util.concurrent.Callable class Hello extends DefaultTask { // Setter method will accept Object type. def outputFile File getOutputFile() { // The Project.file method already // accepts several argument types: // - CharSequence // - File // - URI or URL // - Closure // - Callable project.file outputFile } // Setter method will accept Object type. def user String getUser() { // If the value is set with a Closure // or Callable then we calculate // the value. Closure implements Callable. if (user instanceof Callable) { user.call() // For other types we return the // result of the toString method. } else { user.toString() } } @TaskAction void sayHello() { getOutputFile().text = "Hello ${getUser()}" } }
We already learned in a previous post that setting a property value can also be done using a setter method that is created by Gradle. In the following specification we use different methods and types to assign values to the user
and outputFile
properties:
package com.mrhaki.gradle import org.gradle.api.Project import org.gradle.testfixtures.ProjectBuilder import spock.lang.Specification import spock.lang.Subject import java.util.concurrent.Callable class HelloTaskSpec extends Specification { @Subject private Hello task private Project project = ProjectBuilder.builder().build() def setup() { task = project.task('helloSample', type: Hello) } def "set user property during configuration phase"() { when: project.ext.username = 'sample' and: // Property value known at configuration time. task.user project.property('username') then: task.user == 'sample' } def "user property not set when value not known during configuration phase"() { when: // Property value not known at configuration time. // Exception will be thrown because username property // is not set yet. task.user project.property('username') and: project.ext.username = 'sample' then: thrown(MissingPropertyException) } def "set user property with lazy evaluation GString during configuration phase"() { when: // Property value not known at configuration time. // Using GString lazy evaluation. task.user "${ -> project.property('username')}" and: project.ext.username = 'lazyGString' then: task.user == 'lazyGString' } def "set user property during execution phase using Closure"() { when: // Property value known at execution time, but not // during configuration phase. task.user { project.property('username') } and: // Set value for user property assignment. project.ext.username = 'closureValue' then: task.user == 'closureValue' } def "set user property during execution phase using Callable"() { when: // Property value known at execution time, but not // during configuration phase. task.user = new Callable<String>() { @Override String call() throws Exception { project.property('username') } } and: // Set value for user property assignment. project.ext.username = 'callableValue' then: task.user == 'callableValue' } def "delegate getOutputFile to project.file() and support all types for project.file()"() { given: task.user 'mrhaki' task.outputFile { project.property('helloOutput') } and: project.ext.helloOutput = 'hello.txt' when: task.sayHello() then: task.outputFile.text == 'Hello mrhaki' } }
Written with Gradle 2.11.