I have a task class which declares (trimming some boilerplate):
class ClosureCompiler extends DefaultTask {
@Input
@Optional
def args = []
@TaskAction
def compile() {
println "BEF: " + args
...
project.javaexec {
...
args = localArgs
}
println "AFT: " + args
}
When I run the following sequence:
mk clean && mk -i carrot2:dist > out1 && mk -i carrot2:dist > out2
This task is executed in the second run even though the arguments have not
changed at all. The output log says that:
“Executing task ‘:carrot2:minify’ due to: Value of input property ‘args’ has changed for task ‘:carrot2:minify’”
but when I println this parameter is it identical in both builds. Since it’s a list of values the ordering of values doesn’t change either (and I see it when I diff those output dumps that they’re identical).
Any clue how I can diagnose this or what I’m doing wrong?
A logdiff of the two runs. Note the ‘BEF’ and ‘AFT’ blocks; the arg’s value seems to be identical?
Oh, I’m now thinking that maybe these arguments are not strings and this may be confusing the hashing system? Is there a way to dump how the hashes are constructed from Input parameters?
I’ve tracked this based on a custom injected snippet of code into gradle’s InputPropertiesChangedUpToDateRule. The problem is that arguments is empty when comparing the old value to the new one. I think it may have something to do with the fact that in the task that uses it I populate it in the doFirst call. It makes sense - it’s in the execution phase after all.
I would appreciate an explanation (or a pointer to the documentation) explaining how configuration/execution phases and each task’s doFirst/doLast/configuration closure block relate to each other, in particular with respect to evaluation of inputs/outputs hashes. I couldn’t find this information anywhere and it seems crucial for correct implementation of up-to-date aware tasks.
The up-to-date check is made immediately before the task is executed (or not). Hence a change to inputs in ‘doFirst’ isn’t visible to the check and should, if possible, be avoided.
Thanks Peter, I see where I made a mistake. I still don’t know how to solve the problem I’m facing. If you take a look at this build it shows the issue:
configure(subprojects) { project ->
task run(type: MyTask) {
outputFile = file(buildDir.absolutePath + "/output.txt")
// We want per-project definition of 'defines' property and we copy its value here.
defines.each { args << it }
}
}
project("suba") {
ext.defines = ["foo"]
}
project("subb") {
ext.defines = ["bar"]
}
class MyTask extends DefaultTask {
@OutputFile
def outputFile
@Input @Optional
def args = []
MyTask() {
outputs.upToDateWhen { false }
}
@TaskAction
def doRun() {
println project.name + " => " + args
}
}
During the configuration phase the property “defines” is not yet assigned when the subprojects closure is executed and it fails with an error (sensibly). I see a workaround in moving per-project configuration closures before subprojects configuration (doable). Things work then. But what if I want to keep the order presented here? Is there a way to express a configuration logic of a task that happens after all the configuration has been parsed but before the tasks are executed and inputs/outputs are hashed/compared with the previous state? This kind of works but it feels clumsy:
configure(subprojects) { project ->
task run(type: MyTask) {
outputFile = file(buildDir.absolutePath + "/output.txt")
gradle.taskGraph.whenReady {taskGraph ->
defines.each { args << it }
}
}
}
Just trying to wrap my head around the possible solutions, I appreciate your time.
Just to complete – this also doesn’t work if I move ext.defines definition to a subproject’s build.gradle. Subproject files seem to be evaluated after the parent so the parent doesn’t see subproject’s properties.
Read through the manual again and found this – evaluationDependsOnChildren. I missed it previously, sorry. It still doesn’t solve the all-in-one-file example but I think it’s a minor nuisance and it’s probably unsolvable since it’s a groovy script.
‘evaluationDependsOnChildren’ is a last resort. In the present case, I might do:
subprojects {
task run(type: MyTask) { ... }
}
project("suba") {
run {
args = ["foo"]
}
}
project("suba") {
run {
args = ["bar"]
}
}
Some methods in the Gradle API accept a closure to allow you to defer evaluation. Another option (to be used sparingly) is to use ‘project.afterEvaluate { … }’ or ‘gradle.projectsEvaluated { … }’.
Damn the most obvious answer eluded me… Brilliant, this is it – exactly what I was looking for. Don’t know why I wanted to push it via project properties. Thanks!