延續上一篇文章
上篇文章末尾有一段腳本,定義了一個Task:
task 'myTask' {
doLast {
println 'hello world!'
}
}
我們已經知道,這段腳本其實是調用Project的task方法,並且傳入兩個參數:一個是Task的名字,另外一個是閉包,用來配置Task(在這段腳本中,給Task添加了一個Action)。
Task名的引號去哪兒了?
但是Gradle官方推薦的寫法,是像下面這樣定義Task:
task myTask { // <--
doLast {
println 'hello world!'
}
}
那麼這個語法怎麼解釋呢?
用GroovyConsole觀察AST
所謂AST,就是抽象語法樹,簡單的說,就是編譯器分析完代碼之後,生成的一個樹形數據結構。我們用Groovy自帶的GroovyConsole就可以觀察AST。打開GroovyConsole,把腳本複製到編輯器中,然後按ctrl+t就可以看到AST,如下圖所示:
可以看出來,上面的腳本會被編譯器解釋成下面這樣:
task(myTask({
doLast({
println('hello world!')
})
}))
也就是說,先調用myTask方法,然後再把返回結果當成參數,調用task方法。那麼到底是不是這樣呢?
AST轉換
沒有定義過myTask方法?這個好說,因爲Groovy提供了強大的元編程機制,比如methodMissing方法,可以做到這一點。那麼到底是不是這樣呢?一番搜索之後,我認爲我找到了正確答案:Gradle是利用AST轉換來實現這種特殊語法的。爲了給出確鑿的證據,我找出了相關的Gradle源代碼。最主要的兩個類是org.gradle.groovy.scripts.internal.TaskDefinitionScriptTransformer 和org.gradle.groovy.scripts.internal.AstUtils。下面列出TaskDefinitionScriptTransformer的transformVariableExpression方法以供參考:
private void transformVariableExpression(MethodCallExpression call, int index) {
ArgumentListExpression args = (ArgumentListExpression) call.getArguments();
VariableExpression arg = (VariableExpression) args.getExpression(index);
if (!isDynamicVar(arg)) {
return;
}
// Matches: task args?, <identifier>, args? or task(args?, <identifier>, args?)
// Map to: task(args?, '<identifier>', args?)
String taskName = arg.getText();
call.setMethod(new ConstantExpression("task"));
args.getExpressions().set(index, new ConstantExpression(taskName));
}
結論
Gradle是通過AST轉換來實現task定義特殊語法的,下面這三行代碼完全等價:
task myTask {...}
task 'myTask' {...}
task('myTask', {...})