I'm having a hard time understanding how Gradle's Groovy DSL works.
Unfortunately Gradle is the main use-case for Groovy that I come across in my day to day work, and I've noticed that for many devs, their exposure to Groovy is strictly through Gradle. And that a majority of Gradle users have a very limited grasp of Groovy as a consequence.
In my limited understanding of Groovy, the following sintax tokenA tokenB { tokenC }
where all tokens are not language keywords, tokenA
would be a method that we are calling with arguments tokenB
and the final argument is a closure. I would like to think I'm correct, but I know I'm wrong because there probably needs to be a comma after tokenB for that analysis to be correct.
I am by no means, as you can already tell, a Groovy dev, and I think using Gradle without learning the basics of Groovy is a bad thing to do, because it limits me from fully exploiting its capabilities. But my only viable option is to learn though examples without learning the theory unfortunately.
I did check out some similar questions like this one but no answers where clear or complete enough for me.
TL;DR
task myTask { doLast {} }
interpreted in Groovy?myTask
interpreted as an identifier when there is task
and not def
or a type behind it?myTask { dependsOn myOtherTask }
how does that get interpreted?In Gradle, Task is a single unit of work that a build performs. These tasks can be compiling classes, creating a JAR, Generating Javadoc, and publishing some archives to a repository and more. It can be considered as a single atomic piece of work for a build process.
gradle is a Groovy script. Thus it can execute arbitrary code and access any Java library, build-specific Gradle DSL and the Gradle API.
A custom task type is a simple Groovy class which extends DefaultTask – the class which defines standard task implementation. There are other task types which we can extend from, but in most cases, the DefaultTask class is the appropriate choice.
Gradle supports two types of task. One such type is the simple task, where you define the task with an action closure. We have seen these in Build Script Basics. For this type of task, the action closure determines the behaviour of the task. This type of task is good for implementing one-off tasks in your build script.
I believe its all groovy and nothing special to gradle. Here's the groovy concepts you need to know.
class MyClass {
void doStuff(String name, Closure c) {
c.call()
}
}
def o = new MyClass()
o.doStuff('x') {
println "hello"
}
class MyClass {
def methodMissing(String name, args) {
println "You invoked ${name}(${args})"
}
}
def o = new MyClass() {
o.thisMethodDoesNotExist('foo')
}
class MyBean {
void include(String pattern) {...}
void exclude(String pattern) {...}
}
class MyClass {
private MyBean myBean = new MyBean()
void doStuff(Closure c) {
c.setDelegate(myBean)
c.call()
}
}
def o = new MyClass()
o.doStuff {
include 'foo'
exclude 'bar'
}
These 3 groovy features pretty much explain the "magic" behaviour going on in a gradle script that have java developers scratching their heads.
So, let's break down your snippet
task myTask(type:Foo) {
doLast {...}
}
Let's add some brackets and also add the implicit project references. Let's also extract the closure into a variable
Closure c = {
doLast {...}
}
project.task(project.myTask([type: Foo.class], c))
The project.myTask(...)
method doesn't exist and the behavior is ultimately implemented via methodMissing
functionality. Gradle will set the delegate on the closure to the task instance. So any methods in the closure will delegate to the newly created task.
Ultimately, here's what's logically called
Action<? extends Task> action = { task ->
task.doLast {...}
}
project.tasks.create('myTask', Foo.class, action)
See TaskContainer.create(String, Class, Action)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With