Understanding Gradle DSL
Most of the time, Gradle works just fine for me, but I had difficulties understanding the build script and the Gradle DSL documentation. This post documents the points which helped me to understand them.
Groovy basics
To understand Gradle, we first need to understand a bit of Groovy, because Gradle is based on Groovy.
Closure
A closure is a function bound to (or executed against) some object instances, which can be one of these three things: this, owner, and delegate.
- this: the instance of the enclosing class
- owner: same as this, or the enclosing closure if exists
- delegate: same as owner, but can be changed
By default, a variable accessed in a Groovy closure is resolved as follows: the closure itself will be checked first, followed by the closure’s this scope, then the closure’s owner, then its delegate.
The following example demostrates changing the delegate of a closure:
It is important to notice that closure can access member fields and methods defined in another object, which can be changed dynamically at runtime. As a side note: closure and lambda are different concepts. A lambda is simply an anonymous function, without all the this, owner, and delegate stuff.
Method calls
Method calls in Groovy look quite different than in Java. The following are all method call examples in Groovy:
The return type distinguishes method definitions from method calls, e.g., this is a method definition:
Gradle basics
With a basic understanding of closures and method calls in Groovy, we can try to make sense of the following method calls:
Note in Gradle DSL, closures are frequently (and idiomatically) used as the last method parameter to configure some object. This is a pattern called configuration closure.
Each closure has a delegate object, which Groovy uses to look up variable and method references which are not local variables or parameters of the closure. Gradle uses this for configuration closures, where the delegate object is set to the object to be configured.
Gradle processes a build script in three phases: initialization, configuration, and execution. The initialization and configuration phases create and configure project objects and construct a DAG of tasks, which are then executed in the execution phase.
Dependencies
With the above being discussed, I understand the following is calling the dependencies()
method on the project object.
And the documentation indicates the configuration closure is executed against the DependencyHandler object.
But where is testCompile
defined?
The DependencyHandler documentation says:
To declare a specific dependency for a configuration you can use the following syntax:
dependencies { configurationName dependencyNotation1, dependencyNotation2, ... }
So testCompile
is a configurationName
, but where is this configurationName defined? It turns out that Gradle plugins can add various stuff to the project.
In our case, the testCompile
is a dependency configuration added by the java plugin. That is why we need this line in our build script:
Tasks
Gradle plugins can also add tasks to the project, e.g., the jar task added by the java plugin:
The documentation says:
Each jar or war object has a
manifest
property with a separate instance of Manifest.
This explains the manifest {}
construct. Following the documentation of
Manifest,
we can find the method signature Manifest attributes(Map<String,?>
attributes)
, which explains the method call with named parameters
attributes(key1:value1, key2:value2)
.
Custom tasks
The syntax of a custom task is tricky. For example:
I could figure out that this was probably calling the method Task
task(Map<String,?> args, String name, Closure configureClosure)
defined on
the project object,
but I had no clue how to match the myTask()
construct with the name
parameter. And I am not alone, similar disussions are
here
and
here.
It turns out that Gradle uses some advanced meta programming features of Groovy
(compile-time metaprogramming) to transform the myTask()
construct to the
name
parameter. To be honest, this is the part of Gradle that I do not like,
because it seems to be too tricky to implement those syntax sugars (and too
much sugar may not be healthy). Afterall, Gradle is just a build tool, which
shall be easy to understand, to use, and to extend.
Overall, I find Gradle really cool, because build scripts written in Gradle DSL (or in Groovy) seem to be much cleaner than in XML. With Maven (or Ant), one has to read or write build logic in XML, which is supposed to be processed by machines .