Gradle構建工具的學習與使用
Gradle 是以 Groovy 語言為基礎,面向Java應用為主.基於DSL(領域特定語言)語法的自動化構建工具。
官網:http://www.gradle.org/
前提: 安裝Gradle。安裝過程非常簡單:
1. 下載Gradle
2. 將GRADLE_HOME/bin/gradle加入$PATH。
-
基本概念(Project 和 Task)
Gradle中有兩個基本的概念:project和task。每個Gradle的構建由一個project構成,它代表著需要被構建的元件或者構建的整個專案。每個project由一個或者多個task組成。task代表著Gradle構建過程中可執行的最小單元。例如當構建一個元件時,可能需要先編譯、打包、然後再生成文件或者釋出等,這其中的每個步驟都可以定義成一個task。
-
構建第一個Task
和Ant執行時讀取build.xml類似,Gradle執行時預設會讀取build.gradle這個檔案, 當然你也可以使用引數"-b"來指定其他的xxx.gradle
下面,讓我們新建一個build.gradle檔案,然後輸入如下內容:
1 2 3 4 5 |
task hello { doLast{ println "hello world" } } |
這個構建的指令碼很簡單,就是輸出hello world。為了執行這個構建,我們應該在當前目錄下執行 "gradle hello",即gradle TaskName。
doLast意思是定義一個行為(對映Gradle中的Action類),放在當前task的最後,類似的,還有doFirst, 表示將定義的行為放在當前task最前面,例如
1 2 3 4 5 6 7 8 |
task hello { doLast{ println "Hello world" } doFirst{ println "I am xxx" } } |
執行gradle hello, 將輸出
"I am xxx"
"Hello world"
另外,你也可以使用如下更簡潔的方式來定義task:
1 2 3 |
task hello << { println "hello world" } |
這裡也許大家可能會覺得很奇怪,為什麼可以用"<<"來定義Task的執行內容呢,還是讓我們看看Gradle的程式碼是怎麼實現的吧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public abstract class AbstractTask implements TaskInternal, DynamicObjectAware { private List<Action<? super Task>> actions = new ArrayList<Action<? super Task>>(); public Task doFirst(Action<? super Task> action) { if (action == null) { throw new InvalidUserDataException("Action must not be null!"); } actions.add(0, wrap(action)); return this; } public Task doLast(Action<? super Task> action) { if (action == null) { throw new InvalidUserDataException("Action must not be null!"); } actions.add(wrap(action)); return this; } |
從上面的程式碼可以看出,Task類裡有個Action的集合actions,當使用doFirst或者doLast時,實際上是將定義的執行部分例項化成Action的物件,然後新增到actions集合裡。
明白了這一點,接下來讓我們看看為什麼可以使用<<定義Task--------Groovy作為強大的支援DSL的動態語言,早已經過載了 << 操作符,使得我們可以方便的使用<<向集合新增元素。
說道這,相信真相已經大白了:原來就是使用Groovy的特性,往集合裡新增Action而已。對,這就是Gradle的語法,利用Groovy的DSL特性,幫助我們更容易的定義我們的構建指令碼。
不過也許大家會覺得,這個例子實在是沒有什麼代表性,只是一個簡單的 hello world,說明不了什麼問題。好吧,彆著急,下次我們會繼續研究Gradle的其他部分,不過先記住:作為一個構建工具,Gradle真的很強大哦!
如何使用Task
1. Project和Task
對於build.gradle配置檔案,當執行Gradle 時,Gradle會為我們建立一個Project的物件,來對映build.gradle中的內容。其中呢,對於不屬於任何Task範疇的程式碼,Gradle會建立一個Script類的物件,來執行這些程式碼;對於Task的定義,Gradle會建立Task物件,並將它會作為project的屬性存在(實際上是通過getTaskName完成的)。ok,看一個簡單的例子:
新建檔案basic/build.gradle,然後加入如下部分程式碼:
1 2 3 4 5 |
println "the project name is $name" task hello << { println "the current task name is $name" println "hello world" } |
當執行這個例子時,首先Gradle會建立一個Porject的物件,然後將它和build.gradle的內容對映起來。在這個例子中,project包括兩部分:
1)可執行指令碼定義
按照之前提到的,可執行指令碼的定義將直接被建立成對應的Script類的物件
在這個例子中,Script對應的部分就是第一行println的部分,然後執行的結果就是打印出 "the project name is basic"。
預設的,Project的名字是當前build.gradle所在目錄的名字,在這個例子中,build.gradle放在basic目錄下,因此,project的name也就是basic.
2)Task定義
在這個例子中,Gradle將建立一個Task的例項,將其和定義的task內容關聯起來。另外,按照之前所說的,當Gradle執行時,我們可以使用訪問project屬性的方式去訪問它。
例如,這個例子中,我們可以使用project.hello來訪問這個task。因為這個task hello已經成為project的一個屬性,那麼當我們使用gradle properties(properties是gradle自帶的一個Task,它能列出當前project級別的所有屬性,可以使用gradle tasks檢視更多內建的Task)來獲取project級別的屬性列表時,也將會得到'hello'。
另外,有一點要注意的是,在這個例子中,task中使用的$name,並不是Project的name,它是當前Task的name,因為它被使用在Task的scope裡。
執行Gradle hello,輸出的結果將是:
1 2 3 |
current project name is test the current task name is hello hello world |
2. 定義屬性
在Gradle中,我們可以定義以下三種屬性並使用它們:
1)System Properties
System Properties 實際是指的JVM的system properties。我們知道,在執行java程式時,可以使用-D來設定Java的系統變數,在Gradle中,你也可以做同樣的事情。比如
gradle xxx -DmySystemProp=xxxx
同時,在build.gradle中,應該這樣使用這個變數:
1 2 3 |
task printSysProps << { println System.properties['system'] } |
2)Project Properties
Project Properties是Gradle專門為Project定義的屬性。它的最大優點在於當執行gradle的時候,我們可以使用-P來設定它的值。比如,
gradle xxx -PmyProjectProp=xxxxx
而在build.gradle中,可以這樣使用這個變數:
1 2 3 4 5 |
task printProps << { if (project.hasProperty('commandLineProjectProp')) { println commandLineProjectProp } } |
同時,當我們執行gradle properties檢視屬性列表時,這個變數的名稱以及值會顯示在結果中。
3)Ext(ra) Properties
1 |
另外,我們還可以為Project或者Task定義Ext屬性,也稱動態屬性,我們必須使用關鍵字ext(對應ExtraPropertiesExtension的例項)去定義動態屬性。從這點可以看出,Gradle已經為我們設計了很多不同的類,去做不同的事情,我們只需要遵循Convention,使用他們即可。如果忘記寫ext關鍵字,gradle執行時則會提示: |
"Dynamic properties are deprecated...."。這是因為以前版本的gradle定義動態屬性時,不需要加ext關鍵字的。
對於Project和Task而言,動態屬性定義的方式完全一樣,只是作用域不一樣。
當定義完成後,我們就可以使用project.prop 或者 task.prop來訪問這些動態屬性了。下面讓我們看一個例子:
1 2 3 4 5 6 7 8 9 10 |
ext.projectProperties="ext projectProperties-value" task printExtProps << { ext.taskProperties="ext.task.properties-value" if (project.hasProperty('projectProperties')){ println "ext.projectProperties values is " + projectProperties } if (printExtProps.hasProperty('taskProperties')){ println "task has defined ext.taskProperties value is " + taskProperties } } |
注意:,對於ext定義的動態屬性,並不能通過外部的方式修改它的值,只能在build.gradle中去設定或者修改它的值。
同時,如果是為project定義的ext動態屬性,也會顯示在gradle properties的結果中。
3. Task依賴
當構建一個複雜的專案時,不同task之間存在依賴是必然的。比如說,如果想執行'部署'的task,必然要先執行 編譯、打包、檢測伺服器等task,只有當這被些被依賴的task執行完成後,才會部署。對於這種行為之間的依賴,Ant、Maven都提供了宣告式的定義,非常簡單。同樣,使用Gradle定義task之間的依賴也是件很容易的事。
例如,定義如下兩個Task,並且在"intro"里加上"dependendsOn"的關鍵字,如下所示:
1 2 3 4 5 6 |
task hello << { println 'Hello world!' } task intro(dependsOn: hello) << { println "I'm Gradle" } |
執行 "gradle intro",結果將是:
1 2 |
Hello World I'm Gradle |
由此可見,當執行gradle intro時,intro依賴的task hello會先被執行。除此之外,dependensOn也支援定義多個task的依賴,使用[]括起來即可。例如
1 |
task A(dependensOn:['B','C','D']) <<{ xxx } |
除了使用dependensOn跟字串來定義依賴,我們也可以使用taskX.dependensOn taskY這種形式:
1 2 3 4 5 6 7 |
task taskX << { println 'taskX' } task taskY << { println 'taskY' } taskX.dependsOn taskY |
或者,也可以使用閉包:
1 2 3 4 5 6 7 8 9 |
task taskX << { println 'taskX' } taskX.dependsOn { tasks.findAll { task -> task.name.startsWith('lib') } } task lib1 << { println 'lib1' } |
除了之前講的task的部分,Gradle還為我們提供了很多可用的API,更多的細節大家可以參考下Gradle的文件。這裡我列出一個常用的方法onlyIf。在Gradle裡,我們可以使用“OnlyIf()”來決定當前task是否需要被執行,例如:新建build.gradle,加入如下程式碼:
1 2 3 4 |
task hello << { println 'hello world' } hello.onlyIf { !project.hasProperty('skipHello') } |
當我們直接執行"gradle hello"時,沒有任何結果,當我們執行"gradle hello -PskipHello=xxxx"時,會輸出"hello world"。
4. 總結
總體而言,用DSL的程式碼而不是xml來定義構建的過程,還是很fancy的。
多專案構建
對於多系統、多專案的情況,很多構建工具都已經提供了不錯的支援,像maven、ant。Gradle除了借鑑了ant或者maven的繼承的方式定義子專案,也提供了一種更為方便的集中配置的方式,大大減少了構建帶來的複雜度。除此之外,Gradle還提供了清晰的Project樹模型來對映多專案的組織結構。下面,讓我們瞭解一下如何使用Gradle構建多專案。
1. 多專案定義及結構
在Gradle中,使用檔案settings.gradle定義當前專案的子專案,格式如下所示:
1 |
include 'sub-project1', 'sub-project2', 'sub-project3', |
它表示在當前的專案下建立三個子專案,分別為'sub-project1', 'sub-project2', 'sub-project3'。預設情況下,每個子專案的名稱對應著當前作業系統目錄下的一個子目錄。
當Gradle執行時,會根據settings.gradle的配置情況,構建一個單根節點的專案樹。其中的每個子節點代表一個專案(Project),每個專案都有一個唯一的路徑表示它在當前樹中的位置,路徑的定義方式類似:
1 |
Root:<Level1-子節點>:<Level2-子節點>:<Level3-子節點> |
也可以簡寫成“:::”。藉助這種路徑的定義方式,我們可以在build.gradle去訪問不同的子專案。另外,對於單專案,實際上是一種特殊的、只存在根節點,沒有子節點的專案樹。
例如,我們有個產品A,包括以下幾個元件core,web,mobile。分別代表"核心邏輯"、"網站"、“手機客戶端”。 因為每個元件是獨立的部分,這個時候最好我們能定義多個子專案,讓每個子專案分別管理自己的構建。於是我們可以這樣定義A/settings.gradle
1 |
include 'core', 'web', 'mobile' |
按照之前描述的,core元件對應A/core目錄,web元件對應A/web目錄,mobile元件對應A/mobile目錄。接下來,我們就可以在每個元件內部,定義build.gradle負責管理當前元件的構建。
Gradle提供了一個內建的task 'gradle projects',可以 幫助我們檢視當前專案所包含的子專案,下面讓我們看看gradle projects的輸出結果:
1 2 3 4 5 6 7 8 9 |
$ gradle projects :projects ------------------------------------------------------------ Root project ------------------------------------------------------------ Root project 'A' +--- Project ':core' +--- Project ':mobile' --- Project ':web |
結果一目瞭然,首先是Root級別的專案A,然後是A下面的子專案'core', 'mobile', 'mobile'
最終的檔案以及目錄結構如下所示:
1 2 3 4 5 6 7 8 9 |
A --settings.gradle --build.gradle --core --build.gradle --web --build.gradle --mobile --build.gradle |
如果你不喜歡這種預設的結構,也可以按照如下方式定義子專案的名稱和物理目錄結構:
1 2 3 4 5 6 7 8 |
include(':core) project(':core').projectDir = new File(settingsDir, 'core-xxx') include(':web) project(':web').projectDir = new File(settingsDir, 'web-xxx') include(':mobile) project(':mobile').projectDir = new File(settingsDir, 'mobile-xxx') |
在這個例子中,子專案core實際上對應的物理目錄為A/core-xxx,web實際上對應的是A/web-xxx,mobile也類似。
雖然我們更改了子專案的物理目錄結構,不過由於我們在build.gradle中使用的是類似 “ :”的方式訪問對應的子專案,所以目錄結構的改變,對我們Gradle的構建指令碼並不會產生影響。
接下來,考慮一個更復雜的情況,隨著產品的發展,mobile這個元件慢慢的劃分成了Android和IOS兩個部分,這時我們只需要在目錄A/mobile下定義新的settings.gradle,並加入如下部分:
include 'android', 'ios'
現在,mobile元件下將存在兩個新的子專案 "android"和"ios"
於是,這時候'gradle projects'的目錄結構就變成
1 2 3 4 5 6 7 8 9 10 11 12 |
A --settings.gradle --core --build.gradle --web --build.gradle --mobile --settings.gradle --ios --build.gradle --android --build.gradle |
2. 多專案的集中配置
對於大多數構建工具,對於子專案的配置,都是基於繼承的方式。Gradle除了提供繼承的方式來設定子專案,還提供了另外一種集中的配置方式,方便我們統一管理子專案的資訊。下面看一個例子,開啟A/build.gradle,輸入如下部分:
1 2 3 4 5 6 7 8 9 |
allprojects { task hello << {task -> println "I'm $task.project.name" } } subprojects { hello << {println "- I am the sub project of A"} } project(':core').hello << { println "- I'm the core component and provide service for other parts." } |
對於上面所示的程式碼,已經很表意了:
allprojects{xxx} 這段程式碼表示,對於所有的project,Gradle都將定義一個名稱是hello的Task { println "I'm $task.project.name"} 。
subprojects{xxxx}的這段程式碼表示,對於所有的子project,將在名稱為hello的Task上追加Action {println "- I am the sub project of A"}
注意:關於Task和Action的關係,請看我之前寫的本系列的第一部分。
project(':core')的這段程式碼表示,對於名稱為core的project,將在名稱為hello的Task上追加Action { println "- I'm the core component and provide service for other parts." }
3. 多專案的Task執行
之前我們已經瞭解了多專案的結構以及如何通過路徑去訪問子專案。現在讓我們看看如何使用Gradle來執行多專案。
在Gradle中,當在當前專案上執行gradle 時,gradle會遍歷當前專案以及其所有的子專案,依次執行所有的同名Task,注意:子專案的遍歷順序並不是按照setting.gradle中的定義順序,而是按照子專案的首字母排列順序。
基於剛才的例子,如果我們在根目錄下,執行gradle hello,那麼所有子專案的“hello” Task都會被執行。如果我們在mobile目錄下執行gradle hello,那麼mobile、android以及IOS的“hello” Task都會被執行。關於該例子的執行結果,這裡就不貼出來了。大家如果有興趣的話可以試試。
4. 總結
這篇文章主要描述了使用Gradle管理多專案的知識。相比Ant或者Maven,Gradle提供了更靈活的配置方式。更重要的是,Gradle還提供了很多內建的Task幫助我們檢視或者管理專案。這次就先聊到這裡,下次我們來看看Gradle的生命週期。
from eoe.cn