Gradle Goodness: Lazy Project Property Evaluation
Sometime we need to define a project property in our Gradle build file for which the value must be evaluated later than the assignment.
We can do this in different ways in Gradle.
For example for a String
type property we can rely on Groovy's support for lazy String
evaluation.
If the property is of a different type we can use Closure
to define the value.
The Closure
code is not executed during the configuration phase directly, but we can write code to invoke the Closure
at the right moment and get the value.
Finally we can use the afterEvaluate
method of the Project
class to define a lazy property.
Let's look at the different options we have with some code samples.
First we look at a lazy String
property.
We illustrate this with an example of a multi-project build with the following layout:
.
├── api
├── app
├── build.gradle
├── lib
└── settings.gradle
In our settings.gradle
file we define the projects we want to be part of the root project:
include 'api'
include 'lib'
include 'app'
Let's create a build file with a lazy property buildVersion
:
subproject {
version = '1.0.0'
// Use lazy String evaluation using
// the ${->...} syntax. If we don't
// make it lazy the value of project.version
// is always 1.0.0, because that is the only
// known value at this point and the changed value
// in the app project is not noticed.
ext.buildVersion = "${project.name}-${-> project.version}"
task displayBuildVersion {
description = 'Display value of buildVersion property'
doFirst {
println buildVersion
}
}
}
project('app') {
// In the project app we define
// a different value for the version
// property. Remember this property
// is used in the definition of the
// buildVersion property.
version = '1.0.3'
}
If we run the displayBuildVersion
task we get the following output:
$ gradle -q displayBuildVersion
api-1.0.0
app-1.0.3
lib-1.0.0
$
If we have a property of another type than String
we can use another mechanism to support lazy properties.
We use a Closure
to define the value and then invoke the Closure
to calculate the value at a later time.
In the next build script we add a support method lazyProperty
to check if the property is defined with a Closure
.
If so we execute the Closure
to calculate the value. Any other type is return as-is.
subprojects {
ext {
// Project property with Integer value.
// Value is overriden in the 'app' project.
port = 80
// Value is lazy and depends on port
// property. Port property can be overriden
// later in the configuration phase of Gradle.
// Using a Closure we can make the value
// calculation at a later point in time.
calculatedPort = { port + 10 }
}
task displayCalculatedPort {
description = 'Display value of calculatedPort property'
doFirst {
print "Calculated port for ${project.name} "
println lazyProperty(project, 'calculatedPort')
// Alternative without support method:
// println project.property('calculatedPort')()
// Or even shorter:
// println calculatedPort()
}
}
}
project('app') {
// Override port property.
ext.port = 8000
}
/**
* Check if property value is a Closure. If so the value
* is calculated by running the Closure and returned.
* Otherwise the value is returned without calculation.
*
* @param project Project containing the property to calculate.
* @param propertyName Name of the property to calculate value for.
*/
def lazyProperty(final Project project, final String propertyName) {
// Get property.
def propertyValue = project.property(propertyName)
// Check for type of property to see if we can
// run it as a Closure.
if (propertyValue instanceof Closure) {
// Invoke Closure to calculate the value.
propertyValue()
} else {
// Return value as-is.
propertyValue
}
}
Let's run the displayBuildVersion
task and look at the output:
$ gradle -q displayCalculatedPort
Calculated port for api 90
Calculated port for app 8010
Calculated port for lib 90
$
Finally we use the afterEvaluate
method:
subprojects {
// Project property with Integer value.
// Value is overriden in the 'app' project.
ext.port = 80
afterEvaluate {
// Create calculatedPort property with
// value at the latest moment.
ext.calculatedPort = port + 10
}
task displayCalculatedPort {
description = 'Display value of calculatedPort property'
doFirst {
print "Calculated port for ${project.name} "
println calculatedPort
}
}
}
project('app') {
// Override port property.
ext.port = 8000
}
When we run the displayBuildVersion
task and look at the output we see expected results:
$ gradle -q displayCalculatedPort
Calculated port for api 90
Calculated port for app 8010
Calculated port for lib 90
$
In our simple examples we used the lazy project property only in one task. We could have simply evaluated the value in the task definition as it is the only place where it is used. But if a property is used by other tasks and code in our build script we can rely on the mentioned solutions in this blog post.
Written with Gradle 3.0.