1. 程式人生 > >Gradle構建工具的學習與使用

Gradle構建工具的學習與使用

Gradle 是以 Groovy 語言為基礎,面向Java應用為主.基於DSL(領域特定語言)語法的自動化構建工具。
官網:http://www.gradle.org/

前提: 安裝Gradle。安裝過程非常簡單:
1. 下載Gradle
2. 將GRADLE_HOME/bin/gradle加入$PATH。

  1. 基本概念(Project 和 Task)

    Gradle中有兩個基本的概念:project和task。每個Gradle的構建由一個project構成,它代表著需要被構建的元件或者構建的整個專案。每個project由一個或者多個task組成。task代表著Gradle構建過程中可執行的最小單元。例如當構建一個元件時,可能需要先編譯、打包、然後再生成文件或者釋出等,這其中的每個步驟都可以定義成一個task。

  2. 構建第一個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