Writing Gradle build tasks is often easy and straight forward, but as soon as you start to write more generic tasks for multiple modules or projects it can get a little tricky.
Why Lazy Evaluation?
Recently I wrote a task to configure a docker build for different Java modules. Some of them are packaged as JAR and some as WAR artifacts. Now this configuration was not that complicated, but I really hate duplicating stuff. So I wondered how to write a generic configuration and let each module override some parts of this config? That’s where lazy property evaluation comes in very handy.
Lazy Evaluation of String Properties
Let’s check this simple project configuration, which logs the the evaluated properties to the console using the build-in Gradle Logger.
allprojects {
version = '1.0.0'
ext {
artifactExt = "jar"
dockerArtifact = "${name}-${version}.${artifactExt}"
}
}
subprojects {
task printArtifactName {
doLast {
logger.lifecycle "Artifact ${dockerArtifact}"
}
}
}
project('A') {
// using default configuration
}
project('B') {
artifactExt = 'war'
}
The above code should do exactly what we want:
./gradlew printArtifactName
:A:printArtifactName
Artifact A-1.0.0.jar
:B:printArtifactName
Artifact B-1.0.0.jar
Wait, didn’t we override the default artifactExt
property within module B? Gradle seems to ignore the overridden property!
Let’s modify the example task to get a deeper insight:
task printArtifactName {
doLast {
logger.lifecycle dockerArtifact
logger.lifecycle artifactExt
}
}
./gradlew printArtifactName
:A:printArtifactName
Artifact A-1.0.0.jar
Extension jar
:B:printArtifactName
Artifact B-1.0.0.jar
Extension war
Looks like the property artifactExt
gets overridden correctly. The problem is caused by the evaluation time of the property dockerArtifact
. Within Gradles configuration phase dockerArtifact
gets evaluated directly, but at that time artifactExt
is defined with it’s default value jar
. Later when configuring project B, dockerArtifact
is already set and overriding artifactExt
does not affect the value of dockerArtifact
anymore. So we have to tell Gradle to evaluate the property artifactExt
at execution time.
We can do that by turning the property into a Closure
like that:
dockerArtifact = "${name}-${version}.${-> artifactExt}"
Now Gradle evaluates name
and version
properties eagerly but artifactExt
gets evaluated lazily each time dockerArtifact
is used.
Running the modified code again gives us the expected result:
./gradlew printArtifactName
:A:printArtifactName
Artifact A-1.0.0.jar
Extension jar
:B:printArtifactName
Artifact B-1.0.0.war
Extension war
This simple hack can come in quite handy, but can only be used within Groovy Strings, as it uses Groovys build-in Lazy String Evaluation. Note that Groovy Strings are those Strings wrapped in double quotes, whereas regular Java Strings are wrapped in single quotes.
Lazy Evaluation of non-String Properties
Using Closures you can also use lazy evaluation for other property types like shown below.
Let’s define another property called maxMemory
as a Closure.
allprojects {
version = '1.0.0'
ext {
artifactExt = "jar"
dockerArtifact = "${name}-${version}.${-> artifactExt}"
minMemory = 128
// use a Closure for maxMemory calculation
maxMemory = { minMemory * 2 }
}
}
subprojects {
task printArtifactName {
doLast {
logger.lifecycle "Artifact ${dockerArtifact}"
logger.lifecycle "Extension ${artifactExt}"
logger.lifecycle "Min Mem ${minMemory}"
// running maxMemory Closure by invoking it
logger.lifecycle "Max Mem ${maxMemory()}"
}
}
}
project('B') {
artifactExt = 'war'
minMemory = 512
}
As you can see the real difference to lazy String evaluation is how the closure gets invoked at execution time. We invoke the Closure by adding parenthesis to the property name.
Running the modified code again gives us the expected result:
./gradlew printArtifactName
:A:printArtifactName
Artifact A-1.0.0.jar
Extension jar
Min Mem 128
Max Mem 256
:B:printArtifactName
Artifact B-1.0.0.war
Extension war
Min Mem 512
Max Mem 1024
As you can see lazy evaluation of properties is really simple and allows more complex configurations without the need of duplicating code.