Gradle Tips#2-語法

發表於2015-09-10

第一篇部落格中,我講解了關於tasks和構建過程中task的不同階段。在寫完這篇之後,我意識到我應該更詳盡的講述一下Gradle。弄懂語法很重要,免得我們碰到複雜的構建指令碼的時候直接暈菜。這篇文章我就會講解一些語法上的東西。

語法

Gradle指令碼是使用Groovy語言來寫的。Groovy的語法有點像Java,希望你能接受它。
如果你對Groovy已經很熟悉了,可以跳過這部分了。
Groovy中有一個很重要的概念你必要要弄懂–Closure(閉包)

Closures

Closure是我們弄懂Gradle的關鍵。Closure是一段單獨的程式碼塊,它可以接收引數,返回值,也可以被賦值給變數。和Java中的Callable介面,Future類似,也像函式指標,你自己怎麼方便理解都好。。。

關鍵是這塊程式碼會在你呼叫的時候執行,而不是在建立的時候。看一個Closure的例子:

下面是一個接收引數的Closure:

如果Closure只接收一個引數,可以使用it來引用這個引數:

接收多個引數的Closure:

另外,引數的型別是可選的,上面的例子可以簡寫成這樣:

很酷的是Closure中可以使用當前上下文中的變數。預設情況下,當前的上下文就是closure被建立時所在的類:

另外一個很酷的點是closure的上下文是可以改變的,通過Closure#setDelegate()。這個特性非常有用:

正如你鎖看見的,在建立closure的時候,myVar並不存在。這並沒有什麼問題,因為當我們執行closure的時候,在closure的上下文中,myVar是存在的。這個例子中。因為我在執行closure之前改變了它的上下文為m,因此myVar是存在的。

把closure當做引數傳遞

closure的好處就是可以傳遞給不同的方法,這樣可以幫助我們解耦執行邏輯。前面的例子中我已經展示瞭如何把closure傳遞給一個類的例項。下面我們將看一下各種接收closure作為引數的方法:

1.只接收一個引數,且引數是closure的方法: myMethod(myClosure)
2.如果方法只接收一個引數,括號可以省略: myMethod myClosure
3.可以使用內聯的closure: myMethod {println ‘Hello World’}
4.接收兩個引數的方法: myMethod(arg1, myClosure)
5.和4類似,單數closure是內聯的: myMethod(arg1, { println ‘Hello World’ })
6.如果最後一個引數是closure,它可以從小括號從拿出來: myMethod(arg1) { println ‘Hello World’ }

這裡我只想提醒你一下,3和6的寫法是不是看起來很眼熟?

Gradle

現在我們已經瞭解了基本的語法了,那麼如何在Gradle指令碼中使用呢?先看下面的例子吧:

知道了Groovy的語法,是不是上面的例子就很好理解了?

首先就是一個buildscript方法,它接收一個closure:

接著是allprojects方法,它也接收一個closure引數:

其他的都類似。。。

現在看起來容易多了,但是還有一點不明白,那就是這些方法是在哪裡定義的?答案就是Project

Project

這是理解Gradle指令碼的一個關鍵。

構建指令碼頂層的語句塊都會被委託給Project的例項

這就說明Project正是我要找得地方。
Project的文件頁面搜尋buildscript方法,會找到buildscript{} script block(指令碼塊).等等,script block是什麼鬼?根據文件

script block就是隻接收closure作為引數的方法

繼續閱讀buildscript的文件,文件上說Delegates to: ScriptHandler from buildscript。也就是說,我們傳遞給buildscript方法的closure,最終執行的上下文是ScriptHandler。在上面的例子中,我們的傳遞給buildscript的closure呼叫了repositories(closure)和dependencies(closure)方法。既然closure被委託給了ScriptHandler,那麼我們就去ScriptHandler中尋找dependencies方法。

找到了void dependencies(Closure configureClosure),根據文件,dependencies是用來配置指令碼的依賴的。而dependencies最終又是委託到了DependencyHandler

看到了Gradles是多麼廣泛的使用委託了吧。理解委託是很重要滴。

Script blocks

預設情況下,Project中預先定義了很多script block,但是Gradle外掛允許我們自己定義新的script blocks!
這就意味著,如果你在build指令碼頂層發了一些{…},但是你在Gradle的文件中卻找不到這個script blocks或者方法,絕大多情況下,這是一些來自外掛中定義的script block。

android Script block

我們來看看預設的Android app/build.gradle檔案:

As we can see, it seems like there should be android method which accepts Closure as a parameter. But if we try to search for such method in Project documentation – we won’t find any. And the reason for that is simple – there is no such method :)

If you look closely to the build script – you can see that before we execute android method – we apply com.android.application plugin! And that’s the answer! Android application plugin extends Project object with android script block (which is simply a method which accepts Closure and delegates it to AppExtension class1).

可以看到,這裡有一個android方法,它接收一個closure引數。但是如果我們在Project的文件中搜尋,是找不到這個方法的。原因很簡單,這不是在Project中定義的方法。
仔細檢視build指令碼,可以看到在抵用android方法之前,我們使用了com.android.application外掛。Android application外掛擴充套件了Project物件,新增了android Script block。
哪裡可以找到Android外掛的文件呢?Android Tools website或者這裡

如果我們開啟AppExtension的文件,我們就可以找打build script中使用的方法了屬性:
1.compileSdkVersion 22. 如果我們搜尋compileSdkVersion,將會找到這個屬性. 這裡我們給這個屬性賦值 “22”。
2.buildToolsVersion和1類似
3.defaultConfig – 是一個script block將會委託給ProductFlavor類來執行。
4.其他
現在我們已經能夠理解Gradle指令碼的語法了,也知道如何查詢文件了。

練習

來配置點東西練練手吧。
在AppExtension中,我找了一個script block testOptions,它會把closure引數委託給TestOptions類。根據文件,TestOptions類有兩個屬性:reportDir和resultsDir。reportDir是用來儲存test報告的。我們來改改這個屬性試試。

這裡我使用了Project中定義的rootDir屬性,該屬性值為專案的根目錄。

現在如果我執行./gradlew connectedCheck,test報告將會被儲存到[rootProject]/test_reports目錄中。

不要在真實的專案嘗試這個修改,所有的構建輸出都應該在build目錄中,這樣才不至於汙染你的專案目錄結構。