问题
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
- How are the tokens
task myTask { doLast {} }
interpreted in Groovy? - Does Gradle use a standard Groovy interpreter?
- How is
myTask
interpreted as an identifier when there istask
and notdef
or a type behind it? - If later in the file I added
myTask { dependsOn myOtherTask }
how does that get interpreted?
回答1:
I believe its all groovy and nothing special to gradle. Here's the groovy concepts you need to know.
- If the last argument of a method is a closure, you can put the closure after the closing bracket for the method arguments.
class MyClass {
void doStuff(String name, Closure c) {
c.call()
}
}
def o = new MyClass()
o.doStuff('x') {
println "hello"
}
- You can implement method missing on your object. If someone tries to call a method that doesn't exist you can do stuff
class MyClass {
def methodMissing(String name, args) {
println "You invoked ${name}(${args})"
}
}
def o = new MyClass() {
o.thisMethodDoesNotExist('foo')
}
- You can set the delegate on a closure
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)
回答2:
(disclaimer, I am not a groovy developer)
When running a build (eg. gradle clean
), the contents of the build.gradle
are evaluated against a Project
object (created by the Gradle runner); see the Javadoc at API-Gradle Project; also read the entire summary as it contains a lot information. In that page they clarify that:
A project has 5 method 'scopes', which it searches for methods: The Project object itself ... The build file ... extensions added to the project by the plugins ... The tasks of the project .. a method is added for each task, using the name of the task as the method name ...
task myTask { }
should be equivalent to project.task('myTask')
. It creates a new task called "myTask" and adds the task to the current project (see the Javadoc). Then, a property is added to the project object, so that it can be accessed as project.myTask
. doLast {..}
invokes the doLast
method on that task object; see the Javadoc at Task-doLast
So for some of your points:
project.task('myTask').doLast(..)
(maybe with more about closures in here)- it does (try building from github); but there is additional processing; the
build.gradle
file is "injected" in a Project instance before running the build. Plus a lot of other steps project.task('myTask')
project.myTask.dependsOn(project.myOtherTask)
(probably with an additional closure, or Action instance involved) . This is due to tasks being added to the project as properties.
Also note that explicit statements, like project.myTask...
are valid and they can be used in the build.gradle
script; but the are verbose so rarely used.
来源:https://stackoverflow.com/questions/56906489/gradle-task-syntax-how-is-it-explained-from-a-groovy-perspective