1. 程式人生 > >Gradle指令碼基礎全攻略

Gradle指令碼基礎全攻略

1 背景

在開始Gradle之前請務必保證自己已經初步瞭解了Groovy指令碼,特別是閉包規則,如果還不瞭解Groovy則可以先看《Groovy指令碼基礎全攻略》這一篇部落格速成一下Groovy基礎,然後再看此文即可。關於Gradle速成乾貨基礎詳情也請參考Geadle官方網站,不好意思我太Low了。

這裡寫圖片描述

Gradle核心是基於Groovy的領域特定語言(DSL,具體概念參見《Groovy指令碼基礎全攻略》),具有非常好的擴充套件性,所以不管是簡單的獨立專案還是大型的多專案構建它都能高效的提高構建任務,尤其對多專案支援是非常牛逼的;Gradle還提供了局部構建功能,譬如構建一個單獨子專案時它會構建這個子專案依賴的所有子專案;當然了他對遠端倉庫和本地庫的支援也很到位;哎呀,總之後面你就明白他的牛逼之處了。

既然Gradle核心是Groovy,Groovy本質又是Java,所以很明顯可以發現Gradle環境必須依賴JDK與Groovy庫,具體如下:

  • JDK版本必須是JDK6以上;

  • 因為Gradle自帶Groovy庫, 所以已安裝的Groovy會被Gradle忽略;

具體Gradle環境配置好了以後如下圖:

這裡寫圖片描述

這裡寫圖片描述

2 Gradle DSL基礎

Gradle的實質是配置指令碼,執行一種型別的配置指令碼時就會建立一個關聯的物件,譬如執行Build script指令碼就會建立一個Project物件,這個物件其實就是Gradle的代理物件。下面給出來各種型別Gradle對應的物件型別:

指令碼型別 關聯物件型別
Build script Project
Init script Gradle
Settings script Settings


Gradle的三種主要物件解釋如下:

  • Project物件:每個build.gradle會轉換成一個Project物件。

  • Gradle物件:構建初始化時建立,整個構建執行過程中只有這麼一個物件,一般很少去修改這個預設配置指令碼。

  • Settings物件:每個settings.gradle會轉換成一個Settings物件。

可以看見,當我們編寫指定型別Gradle指令碼時我們可以直接使用關聯物件的屬性和方法;當然了,每個指令碼也都實現了Script介面,也就是說我們也可以直接使用Script介面的屬性與方法。

2-1 構建指令碼Build script(Project)

在Gradle中每個待編譯的工程都是一個Project(每個工程的build.gradle對應一個Project物件),每個Project在構建的時候都包含一系列Task,這些Task中很多又是Gradle的外掛預設支援的。

PS:所謂的我們編寫Gradle指令碼,實質大多數時候都是在編寫構建指令碼Build script,所以說Project和Script物件的屬性和方法等API非常重要。

每一個Project物件和build.gradle一一對應,一個專案在構建時都具備如下流程:

  1. 為當前專案建立一個Settings型別的例項。

  2. 如果當前專案存在settings.gradle檔案,則通過該檔案配置剛才建立的Settings例項。

  3. 通過Settings例項的配置建立專案層級結構的Project物件例項。

  4. 最後通過上面建立的專案層級結構Project物件例項去執行每個Project對應的build.gradle指令碼。

2-2 初始化指令碼Init script(Gradle)和設定指令碼Settings script(Settings)

Gradle物件:

初始化指令碼Init script(Gradle)類似於Gradle的其他型別指令碼,這種指令碼在構建開始之前執行,主要的用途是為接下來的Build script做一些準備工作。我們如果需要編寫初始化指令碼Init script,則可以把它按規則放置在USER_HOME/.gradle/相關目錄下。譬如:

這裡寫圖片描述

初始化指令碼的Gradle物件代表了Gradle的調運,我們可以通過呼叫Project物件的getGradle()方法獲得Gradle例項物件。

Settings物件:

在對工程進行配置(譬如多專案樹構建)時Settings例項與settings.gradle檔案一一對應,它用來進行一些專案設定的配置。這個檔案一般放置在工程的根目錄。譬如:

這裡寫圖片描述

2-3 Build生命週期

Gradle的構建指令碼生命週期具備三大步,如下:

這裡寫圖片描述

可以看見,生命週期其實和上面構建指令碼Build script的執行流程是可以關聯上的。有了這個流程圖我們接下里詳細看下每個過程。

settings.gradle檔案:

除了構建指令碼檔案,Gradle還定義了一個約定名稱的設定檔案(預設為settings.gradle)。該檔案在初始化階段被執行,對於多專案構建必須保證在根目錄下有settings.gradle檔案,對於單專案構建設定檔案是可選的,不過建議還是寫上。

如下是單專案構建的一個例子:

//settings.gradle
println 'This is executed during the initialization phase.'
//build.gradle
println 'This is executed during the configuration phase.'

task configured {
    println 'This is also executed during the configuration phase.'
}

task test << {
    println 'This is executed during the execution phase.'
}

task testBoth {
    doFirst {
      println 'This is executed first during the execution phase.'
    }
    doLast {
      println 'This is executed last during the execution phase.'
    }
    println 'This is executed during the configuration phase as well.'
}

執行構建結果:

> gradle test testBoth
This is executed during the initialization phase.
This is executed during the configuration phase.
This is also executed during the configuration phase.
This is executed during the configuration phase as well.
:test
This is executed during the execution phase.
:testBoth
This is executed first during the execution phase.
This is executed last during the execution phase.

BUILD SUCCESSFUL

Total time: 1 secs

Gradle多專案構建:

多專案構建總是需要指定一個樹根,樹中的每一個節點代表一個專案,每一個Project物件都指定有一個表示在樹中位置的路徑;在設定檔案中我們還可以使用一套方法來自定義構建專案樹。

//分層佈局的多專案構建settings.gradle檔案
include 'project1', 'project2:child', 'project3:child1'

上面例子中把project的路徑作為了include方法的引數,譬如上面的’project3:child1’引數就指定了物理路徑的project3/child1(project3/child1是相對於多專案根路徑的相對路徑),這也同時意味著會建立’project3’和’project3:child1’兩個project。

//平面佈局的多專案構建settings.gradle檔案
includeFlat 'project3', 'project4'

上面例子中includeFlat方法接受目錄名作為引數,但是特別注意,這些專案目錄必須是根目錄的兄弟目錄。

當然了,設定檔案中建立的多專案樹其實是由專案描述符來描述的,我們可以在設定檔案中隨時修改這些描述符。如下:

//settings.gradle
rootProject.name = 'main'
project(':projectA').projectDir = new File(settingsDir, '../my-project-a')
project(':projectA').buildFileName = 'projectA.gradle'

可以看見,如上例子通過描述符更改名稱和專案目錄,並且建立了一個專案的檔案。

Gradle構建初始化Initialization:

在初始化階段如果我們在根路徑下直接指明settings.gradle檔案和相關配置則構建初始化就會直接按照我們的設定去構建專案,如果我們沒指明settings.gradle檔案則Gradle會以一定的規則去尋找settings.gradle檔案,然後依據尋找結果的不同去決定如何構建專案。

3 Gradle構建基礎

通過上一章可以知道,每一個Gradle構建都是由一個或多個project構成,每一個project都是由一個或多個tasks構成,每個task的實質其實是一些更加細化的構建(譬如編譯class、建立jar檔案等)。

任務task基礎:

如下例子我們先來直觀感受一下task的概念,具體細節後面會探討:

//建立一個名為build.gradle的檔案
task hello {
    doLast {
        println 'Hello world!'
    }
}

//這是快捷寫法,用<<替換doLast,後面解釋
task hl << {
    println 'Hello world!'
}

//建立upper的task,使用Groovy語言編寫
task upper << {
    String someString = 'mY_nAmE'
    println "Original: " + someString
    println "Upper case: " + someString.toUpperCase()
}

通過如下命令執行構建上面名為hello的task,具體如下:

xxx@XXX:~/$ gradle hello
:hello
Hello world!

BUILD SUCCESSFUL

Total time: 1.037 secs

可以看見,gradle命令會在當前目錄中查詢一個叫build.gradle的構建指令碼檔案,這個構建指令碼定義了一個叫做hello的獨立task,並且添加了一個action,我們執行了這個task就得到了想要的結果。

在這裡再多嘴一句,我們看下task有無action的區別,如下:

//有Action的task
task actionTask << {  
    println 'I am actionTask'  
}  
//無Action的task
task noActionTask {  
    println 'I am noActionTask'  
}  

一定要記住,在上面這個例子中如果task沒有加<<則這個任務在指令碼初始化initialization階段(即無論執行啥task都被執行,具體參見上一章的第一個例子)被執行,如果加了<<則在gradle actionTask後才執行。因為沒有加<<則閉包在task函式返回前會執行,而加了<<則變成呼叫actionTask.doLast(),所以會等到gradle actionTask時執行。

任務task依賴:

我們通過上面task基礎感受的例子可以發現,一個build.gradle檔案中定義多個task互相沒有關係,決定執行的是我們gradle命令後面跟的task名字;那我們要是讓他們之間有依賴關係咋辦呢?如下:

task taskX(dependsOn: 'taskY') << {
    println 'taskX'
}
task taskY << {
    println 'taskY'
}

執行結果如下:

xxx@XXX:~/$ gradle taskX
:taskY
taskY
:taskX
taskX

BUILD SUCCESSFUL

Total time: 1.039 secs

動態任務task:

我們還可以在Gradle中使用Groovy來建立動態task,如下:

4.times { counter ->
    task "task$counter" << {
        println "I'm task number $counter"
    }
}

執行結果如下:

xxx@XXX:~/$ gradle task1
:task1
I'm task number 1

BUILD SUCCESSFUL

Total time: 1.397 secs

使用已存在任務task:

我們除過在上面定義任務task時指明依賴以外還可以通過API為任務加入一個依賴,如下:

4.times { counter ->
    task "task$counter" << {
        println "I'm task number $counter"
    }
}
task0.dependsOn task2, task3

執行結果如下:

xxx@XXX:~/$ gradle task0
:task0
I'm task number 2
I'm task number 3
I'm task number 0

BUILD SUCCESSFUL

Total time: 1.397 secs

或者我們還可以通過API為任務加入一些新行為,如下:

task hello << {
    println 'Hello Earth'
}
hello.doFirst {
    println 'Hello Venus'
}
hello.doLast {
    println 'Hello Mars'
}
hello << {
    println 'Hello Jupiter'
}

執行結果如下:

xxx@XXX:~/$ gradle hello
:hello
Hello Venus
Hello Earth
Hello Mars
Hello Jupiter

BUILD SUCCESSFUL

Total time: 1.397 secs

可以發現,doFirst和doLast可以被執行多次,<<操作符實質就是doLast。

任務task短標記:

我們可以通過美元符將一個task作為另一個task的屬性,如下:

task hello << {
    println 'Hello world!'
}
hello.doLast {
    println "Greetings from the $hello.name task."
}

執行結果如下:

xxx@XXX:~/$ gradle hello
:hello
Hello world!
Greetings from the hello task.

BUILD SUCCESSFUL

Total time: 1.397 secs

可以看見,上面指令碼中使用的name其實是任務的預設屬性, 代表當前任務的名稱。

自定義任務task屬性:

我們還可以給任務task加入自定義的屬性,如下例子:

task myTask {
    ext.myProperty = "myValue"
}

task printTaskProperties << {
    println myTask.myProperty
}

執行結果如下:

xxx@XXX:~/$ gradle printTaskProperties
:printTaskProperties
myValue

BUILD SUCCESSFUL

Total time: 1.397 secs

定義預設任務task:

Gradle允許在指令碼中定義一個或多個預設任務,如下:

defaultTasks 'clean', 'run'

task clean << {
    println 'Default Cleaning!'
}

task run << {
    println 'Default Running!'
}

task other << {
    println "I'm not a default task!"
}

執行結果如下:

xxx@XXX:~/$ gradle
:clean,run
Default Cleaning!
Default Running!

BUILD SUCCESSFUL

Total time: 1.397 secs

4 Gradle依賴管理基礎

大多數專案都不是完全獨立的,它們需要依賴其他專案進行編譯等,Gradle允許你告訴它你專案的依賴關係,以便找到這些依賴關係,並在你的構建中維護這些依賴關係,依賴關係可能需要從遠端的Maven等倉庫中下載,也可能是在本地檔案系統中,或者是通過多專案構建另一個構建,我們稱這個過程為依賴解析。

Gradle依賴宣告:

關於依賴宣告不解釋,直接給個例子,如下:

apply plugin: 'java'

repositories {
    mavenCentral()
}

dependencies {
    compile group: 'org.hibernate', name: 'hibernate-core', version: '3.6.7.Final'
    testCompile group: 'junit', name: 'junit', version: '4.+'
}

Gradle依賴配置:

在Gradle中依賴可以組合成configurations(配置),一個配置簡單地說就是一系列的依賴,通俗說也就是依賴配置;我們可以使用它們宣告專案的外部依賴,也可以被用來宣告專案的釋出。下面我們給出幾種Java外掛中常見的配置,如下:

  • compile
    用來編譯專案原始碼的依賴;

  • runtime
    在執行時被生成的類需要的依賴,預設項,包含編譯時的依賴;

  • testCompile

    編譯測試程式碼依賴,預設項,包含生成的類執行所需的依賴和編譯原始碼的依賴;

  • testRuntime

    執行測試所需要的依賴,預設項,包含上面三個依賴;

各種各樣的外掛支援許多標準的配置,我們還可以定義自己的配置。

Gradle外部依賴:

我們可以用Gradle宣告許多種依賴,其中有一種是外部依賴(external dependency),它是在當前構建之外的一種依賴,一般存放在遠端(譬如Maven)或本地的倉庫裡。如下是一個外部依賴的例子:

dependencies {
    compile group: 'org.hibernate', name: 'hibernate-core', version: '3.6.7.Final'
}

可以看見,引用一個外部依賴需要用到group、name、version屬性。上面的寫法還有一種簡寫,如下規則:

group:name:version

這是一個簡寫的例子:

dependencies {
    compile 'org.hibernate:hibernate-core:3.6.7.Final'
}

Gradle倉庫:

有了上面的外部依賴,你指定會想Gradle是咋找到那些外部依賴檔案的。其實Gradle會在一個倉庫(repository)裡找這些依賴檔案,倉庫其實就是很多依賴檔案的集合伺服器, 他們通過group、name、version進行歸類儲存,好在Gradle可以解析好幾種不同的倉庫形式(譬如Maven等),但是Gradle預設不提前定義任何倉庫,我們必須手動在使用外部依賴之前定義自己的倉庫。

下面是一個使用MavenCentral倉庫的例子:

repositories {
    mavenCentral()
}

這是一個使用遠端Maven倉庫的例子:

repositories {
    maven {
        url "http://repo.mycompany.com/maven2"
    }
}

這是一個使用本地檔案系統裡庫的例子:

repositories {
    ivy {
        // URL can refer to a local directory
        url "../local-repo"
    }
}

當然了,一個專案可以有好幾個庫,Gradle會根據依賴定義的順序在各個庫裡尋找它們,在第一個庫裡找到了就不會再在第二個庫裡找它了,否則在第二個庫找。

Gradle釋出artifacts:

依賴配置也可以用來發布檔案,我們可以通過在uploadArchives任務里加入倉庫來完成。下面是一個釋出到Maven 庫的例子,Gradle將生成和上傳pom.xml,如下:

apply plugin: 'maven'

uploadArchives {
    repositories {
        mavenDeployer {
            repository(url: "file://localhost/tmp/myRepo/")
        }
    }
}

5 Gradle命令

多工呼叫命令:

gradle task1 task2 [...]

排除任務命令:

gradle -x task1 task2 [...]

失敗後繼續執行構建命令:

只要有任務呼叫失敗Gradle預設就會中斷執行,我們可以使用–continue選項在一次呼叫中不中斷執行,然後發現所有失敗原因。

簡化任務名命令:

當我們呼叫某個任務時如果名字太長我們可以採用簡化操作,但是必須保證可以唯一區分出該任務的字元,譬如:

//簡寫
gradle -x t1
//替換
gradle -x task1

選擇執行構建命令:

呼叫gradle命令預設會構建當前目錄下的build.gradle檔案,我們可以使用-b引數選擇其他目錄的構建檔案且當使用此引數時settings.gradle將不會生效。如下:

//選擇檔案構建subdir/myproject.gradle
task hello << {
    println "using build file '$buildFile.name' in '$buildFile.parentFile.name'."
}

執行過程:

xxx@XXX:~/$ gradle -b subdir/myproject.gradle hello
:hello
using build file 'myproject.gradle' in 'subdir'.

BUILD SUCCESSFUL

Total time: 1.397 secs

此外我們還可以使用-p引數來指定構建的目錄,譬如在多專案構建中可以用-p替代-b引數。如下執行過程:

xxx@XXX:~/$ gradle -p subdir hello
:hello
using build file 'build.gradle' in 'subdir'.

BUILD SUCCESSFUL

Total time: 1.397 secs

獲取構建資訊:

  • gradle projects命令:列出子專案名稱列表。
  • gradle tasks命令:列出專案中所有任務。
  • gradle help –task someTask命令:可以顯示指定任務的詳細資訊。
  • gradle dependencies命令:列出專案的依賴列表,所有依賴會根據任務區分,以樹型結構展示。

6 編寫Gradle指令碼

Gradle是以Groovy語言為基礎,基於DSL語法的自動化構建工具,一個構建指令碼能夠包含任何Groovy語言元素,每個指令碼都是UTF-8編碼的檔案。

6-1 Project物件API

前面我們說過,Gradle在構建指令碼中定義了一個project,對於構建指令碼中每個project其實Gradle都建立了一個 Project型別的物件來關聯,當構建指令碼執行時它會去配置所關聯的Project物件;構建指令碼中每個被呼叫的方法和屬性都委託給了當前Project物件。

如下我們看一個使用Project屬性的例子:

println name
println project.name

上面兩個println語句的輸出是一樣的;由於name屬性沒有在當前指令碼中定義,所以可以像第一個那樣使用自動委託 ,通常我們使用第二中寫法。

Project物件提供了一些標準的屬性,我們可以在構建指令碼中很方便的使用他們,如下:

Name Type Default Value
project Project Project例項物件
name String 專案目錄的名稱
path String 專案的絕對路徑
description String 專案描述
projectDir File 包含構建指令碼的目錄
build File projectDir/build
group Object 未具體說明
version Object 未具體說明
ant AntBuilder Ant例項物件

具體關於Project的方法詳情參閱Project的API文件。這裡我們給出Project的apply方法的一個例子,如下:

//載入一個gradle檔案
apply from: rootProject.getRootDir().getAbsolutePath() + "/common.gradle"  

6-2 Script物件API

當Gradle執行一個指令碼時它會將這個指令碼編譯為實現了Script的類(在上篇部落格《Groovy指令碼基礎全攻略》Groovy的本質編譯class程式碼那塊有介紹),也就是說所有的屬性和方法都是在Script的介面中宣告。

6-3 Gradle物件API

關於Gradle物件的詳細屬性和API介紹點我即可。這裡直接給出一個使用Gradle物件的例子,如下:

這裡寫圖片描述

6-4 Gradle變數宣告

在Gradle指令碼中有兩種型別的變數可以宣告,如下:

  • 區域性變數
  • 擴充套件變數

區域性變數使用關鍵字def宣告,它只在宣告的地方可見,如下:

    def dest = "dest"

    task copy(type: Copy) {
          form "source"
          into dest

    }

在Gradle中所有被增強的物件可以擁有自定義屬性(譬如projects、tasks、source sets等),使用ext擴充套件塊可以一次新增多個屬性。如下:

apply plugin: "java"

ext {
    springVersion = "3.1.0.RELEASE"
    emailNotification = "[email protected]"
}

sourceSets.all { ext.purpose = null }

sourceSets {
    main {
        purpose = "production"
    }
    test {
        purpose = "test"
        }
    plugin {
        purpose = "production"
    }
}

task printProperties << {
    println springVersion
    println emailNotification
    sourceSets.matching { it.purpose == "production" }.each { println it.name}
} 

上面我們用一個ext擴充套件塊向Project物件新增兩個擴充套件屬性,當這些擴充套件屬性被新增後,它們就像預定義的屬性一樣可以被讀寫。

6-5 Gradle中Groovy使用

這個沒啥說的,具體可以參考《Groovy指令碼基礎全攻略》這篇部落格,裡面有詳細介紹。我們這裡粗略總結回憶一下即可:

  • Groovy會自動將一個屬性的引用轉換為相應的getter/setter方法。

  • Groovy呼叫方法時圓括號可有可無。

  • Groovy為List和Map集合提供了一些操作捷徑,譬如apply plugin:’java’中的plugin:’java’其實就是Groovy中的Map,apply是一個方法,省略了括弧而已。

哎呀,詳細的還是去看前一篇部落格吧。

7 Gradle檔案操作基礎

實際使用Gradle過程中大多數時候需要操作檔案,好在Gradle給我們提供了一些API來快捷處理。

定位檔案:

我們可以使用Project.file()方法來定位一個檔案獲取File物件(詳情參考Project的API),如下:

//相對路徑
File configFile = file('src/config.xml')
//絕對路徑
File configFile = file(configFile.absolutePath)
//專案路徑的檔案物件 
File configFile = file(new File('src/config.xml'))

可以從Project的API發現file()方法能夠接收任何形式的物件引數,它會將引數值轉換為一個絕對檔案物件,通常我們可以傳一個String或File例項;如果傳的路徑是絕對路徑,則會被直接構造為一個檔案例項,否則會被構造為專案目錄加上傳遞目錄的檔案物件;當然了,file()方法還能識別URL(譬如file:/some/path.xml等)。

檔案集合:

檔案集合其實是一組檔案,Gradle使用FileCollection介面表示檔案集合,Gradle API中許多類都實現了這個介面,譬如dependency configurations等。獲取FileCollection例項的一種方法是Project.files(),我們可以傳遞任何數量的物件引數。如下:

FileCollection collection = files('src/file1.txt',
                                  new File('src/file2.txt'),
                                  ['src/file3.txt', 'src/file4.txt'])

使用迭代操作還能將其轉換為其他的一些型別,同時我們還可以使用+操作將兩個檔案集合合併,使用-操作對一個檔案集合做減法。如下例子:

// 對檔案集合進行迭代
collection.each {File file ->
    println file.name
}

// 轉換檔案集合為其他型別
Set set = collection.files
Set set2 = collection as Set
List list = collection as List
String path = collection.asPath
File file = collection.singleFile
File file2 = collection as File

// 增加和減少檔案集合
def union = collection + files('src/file3.txt')
def different = collection - files('src/file3.txt')

我們也可以向files()方法傳遞閉包或者可回撥的例項引數,當查詢集合的內容時就會呼叫它,然後將返回值轉換為一些檔案例項,返回值可以是files()方法支援的任何型別的物件。如下例子:

task list << {
    File srcDir

    // 使用閉合建立一個檔案集合
    collection = files { srcDir.listFiles() }

    srcDir = file('src')
    println "Contents of $srcDir.name"
    collection.collect { relativePath(it) }.sort().each { println it }

    srcDir = file('src2')
    println "Contents of $srcDir.name"
    collection.collect { relativePath(it) }.sort().each { println it }
}

檔案樹:

檔案樹可以代表一個目錄樹結構或一個ZIP壓縮檔案的內容,FileTree繼承自FileCollection,所以我們可以像處理檔案集合一樣處理檔案樹,使用Project.fileTree()方法可以得到FileTree例項,它會建立一個基於基準目錄的物件。如下:

/以一個基準目錄建立一個檔案樹
FileTree tree = fileTree(dir: 'src/main')

// 新增包含和排除規則
tree.include '**/*.java'
tree.exclude '**/Abstract*'

// 使用路徑建立一個樹
tree = fileTree('src').include('**/*.java')

// 使用閉合建立一個數
tree = fileTree('src') {
    include '**/*.java'
}

// 使用map建立一個樹
tree = fileTree(dir: 'src', include: '**/*.java')
tree = fileTree(dir: 'src', includes: ['**/*.java', '**/*.xml'])
tree = fileTree(dir: 'src', include: '**/*.java', exclude: '**/*test*/**')

// 遍歷檔案樹
tree.each {File file ->
    println file
}

// 過濾檔案樹
FileTree filtered = tree.matching {
    include 'org/gradle/api/**'
}

// 合併檔案樹A
FileTree sum = tree + fileTree(dir: 'src/test')

// 訪問檔案數的元素
tree.visit {element ->
    println "$element.relativePath => $element.file"
}

我們還可以使用ZIP或TAR等壓縮檔案的內容作為檔案樹,Project.zipTree()和Project.tarTree()方法可以返回一個FileTree例項。如下:

// 使用路徑建立一個ZIP檔案
FileTree zip = zipTree('someFile.zip')

// 使用路徑建立一個TAR檔案
FileTree tar = tarTree('someFile.tar')

//TarTree可以根據副檔名得到壓縮方式,如果我們想明確的指定壓縮方式則可以如下操作
FileTree someTar = tarTree(resources.gzip('someTar.ext'))

指定輸入檔案:

Gradle中有些物件的屬性可以接收一組輸入檔案,譬如JavaComplile任務的source屬性(定義編譯的原始檔)。如下:

//使用一個File物件設定源目錄
compile {
    source = file('src/main/java')
}

//使用一個字元路徑設定源目錄
compile {
    source = 'src/main/java'
}

//使用一個集合設定多個源目錄
compile {
    source = ['src/main/java', '../shared/java']
}

//使用FileCollection或者FileTree設定源目錄
compile {
    source = fileTree(dir: 'src/main/java').matching {include 'org/gradle/api/**'}
}

//使用閉包設定源目錄
compile {
    source = {
        // Use the contents of each zip file in the src dir
        file('src').listFiles().findAll {it.name.endsWith('.zip')}.collect { zipTree(it) }
    }
}

compile {
    //使用字元路徑新增源目錄
    source 'src/main/java', 'src/main/groovy'
    //使用File物件新增源目錄
    source file('../shared/java')
    //使用閉包新增源目錄
    source { file('src/test/').listFiles() }
}

複製檔案:

我們可以使用複製任務(Copy)進行檔案複製操作,複製任務擴充套件性很強,它可以過濾複製檔案的內容,使用複製任務要提供想要複製的原始檔和一個目標目錄,如果要指定檔案被複制時的轉換方式則可以使用複製規則,複製規則是一個CopySpec介面的實現,我們使用CopySpec.from()方法指定原始檔,CopySpec.into()方法指定目標目錄即可。如下:

task copyTask(type: Copy) {
    from 'src/main/webapp'
    into 'build/explodedWar'
}

task anotherCopyTask(type: Copy) {
    //複製src/main/webapp目錄下的所有檔案
    from 'src/main/webapp'
    //複製一個單獨檔案
    from 'src/staging/index.html'
    //複製一個任務輸出的檔案
    from copyTask
    //顯式使用任務的outputs屬性複製任務的輸出檔案
    from copyTaskWithPatterns.outputs
    //複製一個ZIP壓縮檔案的內容
    from zipTree('src/main/assets.zip')
    //指定目標目錄
    into { getDestDir() }
}

task copyTaskWithPatterns(type: Copy) {
    from 'src/main/webapp'
    into 'build/explodedWar'
    include '**/*.html'
    include '**/*.jsp'
    exclude { details -> details.file.name.endsWith('.html') &&
                         details.file.text.contains('staging') }
}

task copyMethod << {
    copy {
        from 'src/main/webapp'
        into 'build/explodedWar'
        include '**/*.html'
        include '**/*.jsp'
    }
}

//在複製時重新命名檔案
task rename(type: Copy) {
    from 'src/main/webapp'
    into 'build/explodedWar'
    //使用閉包對映檔名
    rename { String fileName ->
        fileName.replace('-staging-', '')
    }
    // 使用正則表示式對映檔名
    rename '(.+)-staging-(.+)', '$1$2'
    rename(/(.+)-staging-(.+)/, '$1$2')
}

檔案同步任務:

同步任務(Sync)繼承自複製任務(Copy),當執行時會複製原始檔到目標目錄,然後從目標目錄刪除所有非複製檔案。如下:

task libs(type: Sync) {
    from configurations.runtime
    into "$buildDir/libs"
}

建立歸檔檔案:

使用歸檔任務可以建立Zip、Tar、Jar、War、Ear等歸檔檔案,如下:

apply plugin: 'java'

task zip(type: Zip) {
    from 'src/dist'
    into('libs') {
        from configurations.runtime
    }
}

關於檔案操作的其他請參考API文件。

8 Gradle外掛

8-1 Gradle外掛概述

外掛基礎:

關於Gradle支援的外掛可以點我搜索。其實Gradle的核心只是一個空空的框架,所謂的Gradle構建便捷指令碼其實都是由外掛提供支援的,外掛添加了新的任務。在Gradle中一般有兩種型別的外掛,如下:

  • 指令碼外掛
    是額外的構建指令碼,它會進一步配置構建,通常會在構建內部使用。指令碼外掛可以從本地檔案系統或遠端獲取,如果從檔案系統獲取則是相對於專案目錄,如果是遠端獲取則是由HTTP URL指定。

  • 二進位制外掛
    是實現了Plugin介面的類,並且採用程式設計的方式來操縱構建。

外掛需要通過Project.apply()方法完成宣告應用,相同的外掛可以應用多次。如下例子:

//指令碼外掛
apply from: 'other.gradle'

//二進位制外掛
apply plugin: 'java'

外掛還可以使用外掛ID,外掛的id作為給定外掛的唯一識別符號,我們可以給外掛註冊一個縮寫字元的id。譬如下面例子:

//通過Java外掛的id進行引用
apply plugin: JavaPlugin

使用構建指令碼塊應用外掛:

我們可以向構建指令碼中加入外掛的類路徑然後再應用外掛和使用外掛的任務,如下:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:0.4.1"
    }
}

apply plugin: "com.jfrog.bintray"

Gradle外掛拓展:

可以看見,Gradle其實是依託於各種外掛壯大的,譬如Java外掛用來構建Java工程,Android外掛用來構建打包Android工程,我們只需要選擇合適的外掛即可,外掛會為我們提供豐富的任務用來快捷處理構建,具體詳情參考各外掛API即可。

8-2 Gradle的Java外掛構建例項

上面說了,外掛是Gradle的擴充套件,它會通過某種方式配置我們的專案(譬如加入一些task);Gradle自帶許多外掛,我們也可以編寫自己的外掛然後開源.,Java 外掛就是這樣的一個外掛,該外掛已經給專案定義了預設的引數(譬如Java原始檔位置),所以通常我們不需要在指令碼中加入太多東西。

單個基礎Java專案構建:

//把Java外掛加入到專案中,也就是許多預定製的任務被自動加入到了專案裡
apply plugin: 'java'

加入上面外掛以後Gradle預設希望能在src/main/java路徑下找到原始碼,在 src/test/java路徑下找到測試程式碼,任何src/main/resources路徑的檔案都會被包含在JAR檔案裡,任何src/test/resources路徑的檔案都會被加入到classpath中以執行測試程式碼,所有的輸出檔案將會被建立在構建目錄裡,JAR檔案存放在 build/libs資料夾裡。

加入Java外掛後我們可以通過gradle tasks命令來列出專案的所有任務,這樣就可以知道Java外掛添加了哪些task。常用的task如下:

  • build task
    當執行gradle build命令時Gradle將會編譯和測試你的程式碼,並且建立一個包含類和資源的JAR檔案。

  • clean task
    當執行gradle clean命令時Gradle將會刪除build生成的目錄和所有生成的檔案。

  • assemble task
    當執行gradle assemble命令時Gradle將會編譯並打包程式碼,但是並不執行單元測試。

  • check task
    當執行gradle check命令時Gradle將會編譯並測試你的程式碼,其他的外掛會加入更多的檢查步驟。

單個具有外部依賴的Java專案構建:

當然了,一個Java專案可能會有許多外部依賴(即呼叫第三方JAR),為了在專案裡引用這些 JAR包,我們需要告訴Gradle去哪裡找他們,好在Gradle支援許多倉庫,這些倉庫可以被用來提取或者放置依賴,我們可以很方便的從這些倉庫中取得第三方Jar包。如下:

//加入Maven倉庫
repositories {
    mavenCentral()
}

接著加入一些編譯階段來自於mavenCentral倉庫的依賴,如下:

dependencies {
    //編譯階段
    compile group: 'commons-collections', name: 'commons-collections', version: '3.2'
    //測試編譯階段
    testCompile group: 'junit', name: 'junit', version: '4.+'
}

定製構建專案:

Java外掛給專案加入了一些屬性,這些屬性已經被賦予了預設的值且已經夠我們日常使用了,如果我們覺得這些預設屬性不好也可以自己修改。如下:

//定製 MANIFEST.MF 檔案
sourceCompatibility = 1.5
version = '1.0'
jar {
    manifest {
        attributes 'Implementation-Title': 'Gradle Quickstart', 'Implementation-Version': version
    }
}

預設Java外掛加入的任務是常規性的任務,但是我們可以定製任務,譬如我們可以設定一個任務的屬性、在任務中加入行為、改變任務的依賴、完全重寫一個任務等。如下:

//測試階段加入一個系統屬性
test {
    systemProperties 'property': 'value'
}

關於哪些屬性是可用的問題,我們可以使用gradle properties命令列出專案的所有屬性。

釋出JAR檔案:

通常JAR檔案需要在某個地方釋出,我們可以通過Gradle方便的進行釋出,譬如下面例子將釋出到一個本地的目錄,如下:

//uploadArchives task
uploadArchives {
    repositories {
       flatDir {
           dirs 'repos'
       }
    }
}

多Java專案構建:

在Gradle中為了定義一個多專案構建我們需要建立一個設定檔案(settings.gradle),設定檔案放在原始碼的根目錄,它用來指定要包含哪個專案且名字必須叫做settings.gradle。如下例子:

//多專案工程結構樹:
multiproject/
  api/
  services/webservice/
  shared/
//多專案構建settings.gradle檔案
include "shared", "api", "services:webservice", "services:shared"

對於大多數多專案構建有一些配置對所有專案都是通用的,所以我們將在根專案裡定義一個這樣的通用配置(配置注入技術 configuration injection)。 根專案就像一個容器,subprojects方法遍歷這個容器的所有元素並且注入指定的配置。如下:

//多專案構建通用配置
subprojects {
    apply plugin: 'java'
    apply plugin: 'eclipse-wtp'

    repositories {
       mavenCentral()
    }

    dependencies {
        testCompile 'junit:junit:4.11'
    }

    version = '1.0'

    jar {
        manifest.attributes provider: 'gradle'
    }
}

可以看見,上面通用配置把Java外掛應用到了每一個子專案中。

我們還可以在同一個構建里加入專案之間的依賴,這樣可以保證他們的先後關係。如下:

//api/build.gradle
dependencies {
    compile project(':shared')
}

至此基礎的Java外掛使用就OK了,深入的請自行檢視API。

4 Gradle基礎總結

到此Gradle的基礎知識就完全介紹完了,我們對Gradle的框架也有了一個直觀的認識。其實編寫Gradle無非也就是對類的屬性和方法進行調運操作,至於如何調運操作依據具體外掛而異,核心的生命週期和幾個物件例項搞明白基本上就能駕馭Gradle指令碼了,其他的無非就是熟練度和API查詢。

這裡寫圖片描述