Gradle has three build phases: initialization, configuration, and execution. Initialization is pretty straight-forward, but coming from a more declarative build tool background (like Ant), differentiating the latter two can cause headaches. The key is to understand the difference between configuring properties and adding actions.
Let's take a simple example:
task copyFiles (type: Copy) {
from "srcDir"
into "destDir"
}
Pretty straightforward, right? I'm using an existing task type--Copy--to copy some files. When I run this in Gradle, I see the following:
C:\temp\myProject>gradle
:help
Welcome to Gradle 1.3.
To run a build, run gradle <task> ...
To see a list of available tasks, run gradle tasks
To see a list of command-line options, run gradle --help
BUILD SUCCESSFUL
Total time: 2.582 secs
Which means, of course, that nothing actually executed. In order to actually run the task, I'd need to explicitly ask Gradle to run it.
C:\temp\myProject>gradle copyFiles
:copyFiles
BUILD SUCCESSFUL
Total time: 2.04 secs
So what's really going on here? Let's add a print statement.
task copyFiles (type: Copy) {
println "copying files"
from "srcDir"
into "destDir"
}
And the output:
C:\temp\myProject>gradle copyFiles
copying files
:copyFiles
BUILD SUCCESSFUL
Total time: 2.736 secs
Notice something strange? Our println statement is happening before Gradle executes the task. What if I run Gradle without any tasks?
C:\temp\myProject>gradle
copying files
:help
...
BUILD SUCCESSFUL
Total time: 1.791 secs
Again, the output still prints, even though we didn't actually execute the task. Why?
The answer is that all task configurations get executed on every Gradle build, no matter whether the tasks actually execute or not. Think about it this way: all code inside the main body of a task is setup. So when we invoke the methods "from" and "into" on the Copy task, Gradle is not actually copying the files ... yet. It's simply instructing the task: "when it's your turn to execute, here's what I want you to do."
Because our println statement is in the configuration section of the task, it always runs, whether or not the task actually executes.
So the question now becomes, how to we write code that only executes when the task executes? This is handled by what Gradle calls actions. Actions are simply chunks of code attached to a task that run--in succession--when the task executes. Actually performing the copy operation is the Copy task's default action. We can add actions to any task by invoking doLast().
task copyFiles (type: Copy) {
from "srcDir"
into "destDir"
doLast {
println "copying files"
}
}
Now when we run the task, we get the expected behavior. Our println happens during the execution phase.
C:\temp\myProject>gradle copyFiles
:copyFiles
copying files
BUILD SUCCESSFUL
Total time: 1.932 secs
Likewise if we omit the task, we no longer see the text.
C:\temp\gradle2>gradle
:help
...
BUILD SUCCESSFUL
Total time: 1.932 secs
The method doLast() (and its counterpart doFirst()) simply manipulate the stack of actions attached to a task. In order to make task definitions a little simpler, Gradle introduces a little syntactic sugar for doLast():
task newTask << {
print "You will only see this in the execution phase"
}
Notice, this is not the same thing as this:
task newTask {
print "You will see this in the configuration phase"
}
So remember these little rules:
- Task configuration runs every build (during the configuration phase)
- Task actions run only when a task is actually run (during the execution phase)
- Code in the main body of a task declaration is configuration code
- Add actions to a task using doFirst(), doLast() and the << shortcut
One concluding note: beginning with version 1.4, the Gradle team has begun experimenting with "configuration on demand". This feature is just like it sounds; Gradle will try to determine which tasks will actually be executed, and only configure those tasks. This is to mitigate an excessively long configuration phase for a small number of actual executed tasks.