最近打算學習下 gradle 在 Android 中的使用,結果百度出來的文章都是介紹性文章,沒啥乾貨。後來找到 gradle 官網教程,本身對着擼。html
Gradle 是一個基於 Apache Ant 和 Apache Maven 概念的項目自動化構建工具。它使用一種基於 Groovy 的特定領域語言來聲明項目設置,而不是傳統的 XML。Gradle 就是工程的管理,幫咱們作了依賴、打包、部署、發佈、各類渠道的差別管理等工做。java
一款最新的,功能最強大的構建工具,用它逼格更高android
使用程序代替傳統的XML配置,項目構建更靈活apache
豐富的第三方插件,讓你爲所欲爲使用編程
Maven、Ant能作的,Gradle都能作,可是Gradle能作的,Maven、Ant不必定能作。api
剛開始的時候,我對 Gradle 和 Groovy 傻傻分不清楚,覺得都是一種語言。後來才懂了,gradle 是一個構建工具,使用的語言是 Groovy。閉包
下面進入實戰。maven
首先爲了使用 gradle,你們能夠在 Android studio 新建一個 Android 工程。使用其餘 IDE 或者須要配置的環境的朋友,能夠本身百度相關文章。編程語言
文章示例基於 Gradle 5.1.1 構建的。 ide
Gradle 中的全部內容都基於兩個基本概念:項目和任務。
每一個 Gradle 構建都由一個或多個項目組成。項目表明什麼取決於您在 Gradle 中所作的事情。例如,一個項目可能表明一個 JAR 庫或一個 Web 應用程序。它可能表示從其餘項目產生的 JAR 組裝而成的發行版 ZIP。項目不必定表明要構建的事物。它可能表示要完成的事情,例如將應用程序部署到暫存或生產環境。暫時不要擔憂這彷佛還不清楚。Gradle 的按慣例構建支持爲項目的定義添加了更具體的定義。
每一個項目由一個或多個任務組成。任務表明構建執行的一些原子工做。這多是編譯某些類,建立 JAR,生成 Javadoc 或將一些存檔發佈到存儲庫。
如今,將研究在一個項目中構建一些簡單的任務。後面的章節將介紹處理多個項目,以及有關處理項目和任務的更多信息。
一樣,也是先從 hello world 入門。您可使用如下 gradle
命令運行 Gradle 構建。該 gradle
命令在當前目錄中查找名爲 build.gradle
的文件。這個 build.gradle
文件稱爲構建腳本,儘管嚴格來講,它是一個構建配置腳本,咱們將在後面看到。構建腳本定義項目及其任務。
要嘗試此操做,請建立如下名爲的構建腳本 build.gradle
。
// build.gradle task hello { doLast { println 'Hello world!' } }
在項目的移至包含的目錄並使用如下命令執行構建腳本:
./gradlew -q hello // Android 用戶在根目錄使用 ./gradlew
gradle -q hello // 非 Android 用戶使用 gradle
使用 -q
命令行選項運行。這將取消 Gradle 的日誌消息,所以僅顯示任務的輸出。這樣可使示例輸出更加清晰。若是不想,則不須要使用此選項。
後面直接將執行構建腳本的命令放在註釋前,不在單行做爲展現了。
在這裏,你將看到了如何使用字符串做爲任務名稱來定義任務。此樣式有一些變體,您可能須要在某些狀況下使用。
task('hello') { doLast { println "hello" } } task('copy', type: Copy) { from(file('srcDir')) into(buildDir) }
上面建立了兩個任務分別是 hello 和 copy。有一種定義任務的替代語法,您可能更喜歡使用:
tasks.create('hello') { doLast { println "hello" } } tasks.create('copy', Copy) { from(file('srcDir')) into(buildDir) }
上面一樣建立了兩個任務分別是 hello 和 copy。 最後,Groovy 和 Kotlin DSL 有特定於語言的語法:
// Using Groovy dynamic keywords task(hello) { doLast { println "hello" } } task(copy, type: Copy) { from(file('srcDir')) into(buildDir) }
Gradle 的構建腳本爲您提供了 Groovy 和 Kotlin 的所有功能。做爲一個開胃菜,看看這個:在Gradle的任務中使用 Groovy 或 Kotlin :
//
task upper { doLast { String someString = 'mY_nAmE' println "Original: $someString" println "Upper case: ${someString.toUpperCase()}" } }
gradle -q upper
// gradle -q count
task count { doLast { 4.times { print "$it " } } }gradle -q count
任務之間能夠具備依賴性,關鍵字 dependsOn :
// gradle -q intro task hello { doLast { println 'Hello world!' } } task intro { dependsOn hello doLast { println "I'm Gradle" } }
上面的依賴是依賴的任務先聲明,而後再進行依賴,還有一種依賴是懶惰性依賴,被依賴的任務能夠後面再聲明,可是若是不聲明的會報錯:
// gradle -q taskX task taskX { dependsOn 'taskY' doLast { println 'taskX' } } task taskY { doLast { println 'taskY' } }
任務 taskX 依賴的任務 taskY 是後聲明的。
您能夠經過多種方式定義任務的依賴關係。在「 任務依賴項」中,介紹了使用任務名稱定義依賴項。任務名稱能夠引用與該任務在同一項目中的任務,也能夠引用其餘項目中的任務。要引用另外一個項目中的任務,請在任務名稱前添加其所屬項目的路徑。如下是添加從 projectA:taskX
到的依賴的示例 projectB:taskY
:
project('projectA') { task taskX { dependsOn ':projectB:taskY' doLast { println 'taskX' } } } project('projectB') { task taskY { doLast { println 'taskY' } } }// gradle -q taskX
此處的 projectA,projectB 要改爲你項目中的名字,簡單來講,就是不一樣層級的任務也是能夠相互依賴的。
Groovy 或 Kotlin 的功能可用於定義任務之外的其餘功能。例如,您也可使用它來動態建立任務。
// gradle -q task1 4.times { counter -> task "task$counter" { doLast { println "I'm task number $counter" } } }
上述建立了 4 個 task,分別是 task0,task1,task2,task3。
任務建立後,就能夠經過 API 對其進行訪問。例如,您能夠在運行時爲任務動態添加依賴項。
// gradle -q task0 4.times { counter -> task "task$counter" { doLast { println "I'm task number $counter" } } } task0.dependsOn task2, task3
或者,您能夠將行爲添加到現有任務。
task hello { doLast { println 'Hello Earth' } } hello.doFirst { println 'Hello Venus' } hello.configure { doLast { println 'Hello Mars' } } hello.configure { doLast { println 'Hello Jupiter' } }// gradle -q hello
調用 doFirst
和 doLast
能夠執行屢次。他們將操做添加到任務操做列表的開頭或結尾。執行任務時,將按順序執行操做列表中的操做。
訪問現有任務有一種方便的表示法。每一個任務均可以做爲構建腳本的屬性來使用:
// gradle -q hello task hello { doLast { println 'Hello world!' } } hello.doLast { println "Greetings from the $hello.name task." }
例子中,經過獲取任務的名字能夠知道這個是來自於 task hello 的任務所作的事。這樣能夠提升代碼的可讀性,尤爲是在使用插件提供的任務(例如compile
任務)時。
您能夠將本身的屬性添加到任務上。要添加名爲的屬性 myProperty
,併爲 ext.myProperty 設置
初始值。就能夠像預約義的任務屬性同樣讀取和設置屬性。
// gradle -q printTaskProperties task myTask { ext.myProperty = "myValue" } task printTaskProperties { doLast { println myTask.myProperty } }
若是未指定其餘任務,則Gradle容許您定義一個或多個默認任務。
// gradle -q defaultTasks 'clean', 'run' task clean { doLast { println 'Default Cleaning!' } } task run { doLast { println 'Default Running!' } } task other { doLast { println "I'm not a default task!" } }
這等效於運行 gradle clean run
。在多項目構建中,每一個子項目均可以有其本身的特定默認任務。若是子項目未指定默認任務,則使用父項目的默認任務(若是已定義)
正如咱們稍後將詳細描述的(請參閱 Build Lifecycle),Gradle具備配置階段和執行階段。在配置階段以後,Gradle 知道應該執行的全部任務。Gradle 爲您提供了一個利用此信息的機會。一個用例是檢查發佈任務是否在要執行的任務中。以此爲基礎,您能夠爲某些變量分配不一樣的值。
在如下示例中,distribution
和 release
任務的執行致使 version
變量的值不一樣。
// gradle -q distribution // gradle -q release task distribution { doLast { println "We build the zip with version=$version" } } task release { dependsOn 'distribution' doLast { println 'We release now' } } gradle.taskGraph.whenReady { taskGraph -> if (taskGraph.hasTask(":release")) { version = '1.0' } else { version = '1.0-SNAPSHOT' } }
能夠發現,此處,執行不一樣的 task 具備不一樣結果。
若是構建腳本須要使用外部庫,則能夠將它們添加到構建腳本自己中的腳本的類路徑中。您可使用 buildscript()
方法執行此操做,並傳入一個聲明構建腳本類路徑的塊。
buildscript { repositories { mavenCentral() } dependencies { classpath group: 'commons-codec', name: 'commons-codec', version: '1.2' } }
buildscript()
方法中的代碼塊將構成 ScriptHandler 實例。您能夠經過向 classpath
配置添加依賴項來聲明構建腳本類路徑。這與您聲明 Java 編譯類路徑的方式相同。您可使用除項目依賴項之外的任何 依賴項類型。
聲明瞭構建腳本類路徑後,就能夠像在該類路徑上的任何其餘類同樣使用構建腳本中的類。如下示例將添加到前面的示例中,並使用構建腳本類路徑中的類。
//
import org.apache.commons.codec.binary.Base64 buildscript { repositories { mavenCentral() } dependencies { classpath group: 'commons-codec', name: 'commons-codec', version: '1.2' } } task encode { doLast { def byte[] encodedString = new Base64().encode('hello world\n'.getBytes()) println new String(encodedString) } }gradle -q encode
這裏先是添加了依賴項,而後再建立了一個任務,引用了依賴項中的類來實現對字符串的加密。
您一般須要找到在構建文件中定義的任務,例如,對其進行配置或將其用於依賴項。有不少方法能夠作到這一點。首先,就像定義任務同樣,Groovy和Kotlin DSL具備特定於語言的語法
task hello task copy(type: Copy) // Access tasks using Groovy dynamic properties on Project println hello.name println project.hello.name println copy.destinationDir println project.copy.destinationDir
任務也能夠經過 tasks
集合得到。
task hello task copy(type: Copy) println tasks.hello.name println tasks.named('hello').get().name println tasks.copy.destinationDir println tasks.named('copy').get().destinationDir
您可使用 tasks.getByPath()
方法使用任務的路徑從任何項目訪問任務。您能夠 getByPath()
使用任務名稱,相對路徑或絕對路徑來調用該方法。
project(':projectA') { task hello } task hello println tasks.getByPath('hello').path println tasks.getByPath(':hello').path println tasks.getByPath('projectA:hello').path println tasks.getByPath(':projectA:hello').path
與 Task
在建立後配置變量的可變屬性相反,您能夠將參數值傳遞給 Task
類的構造函數。爲了將值傳遞給 Task
構造函數,您必須使用註釋相關的構造函數 @javax.inject.Inject
。
class CustomTask extends DefaultTask { final String message final int number @Inject CustomTask(String message, int number) { this.message = message this.number = number } }
而後,您能夠建立一個任務,並在參數列表的末尾傳遞構造函數參數。
tasks.create('myTask', CustomTask, 'hello', 42)
task myTask(type: CustomTask, constructorArgs: ['hello', 42])
上述兩種方法均可以。在全部狀況下,做爲構造函數參數傳遞的值都必須爲非 null。若是您嘗試傳遞一個 null
值,Gradle 將拋出一個 NullPointerException
指示,指出哪一個運行時值是 null
。
您能夠在任務中添加描述。執行 gradle tasks 時將顯示此描述。
// gradle tasks task copy(type: Copy) { description 'Copies the resource directory to the target directory.' from 'resources' into 'target' include('**/*.txt', '**/*.xml', '**/*.properties') }
有時您想替換任務。例如,若是要將 Java 插件添加的任務與其餘類型的自定義任務交換。您可使用如下方法實現此目的:
// gradle -q copy task copy(type: Copy) task copy(overwrite: true) { doLast { println('I am the new one.') } }
定義新任務時,必須將 overwrite
屬性設置爲 true。不然,Gradle 會引起異常,說該名稱的任務已經存在。
Gradle 提供了多種方法來跳過任務的執行。
您可使用該 onlyIf()
方法將謂詞附加到任務。僅當謂詞評估爲 true 時,才執行任務的動做。您將謂詞實現爲閉包。閉包做爲參數傳遞給任務,若是任務應執行,則應返回 true;若是應跳過任務,則應返回 false。在即將執行任務以前就對謂詞進行評估。
//
task hello { doLast { println 'hello world' } } hello.onlyIf { !project.hasProperty('skipHello') }gradle hello -PskipHello
若是不能用謂詞來表示跳過任務的邏輯,則可使用 StopExecutionException。若是某個動做引起了此異常,則將跳過該動做的進一步執行以及該任務的任何後續動做的執行。構建繼續執行下一個任務。
// gradle -q myTask task compile { doLast { println 'We are doing the compile.' } } compile.doFirst { // Here you would put arbitrary conditions in real life. // But this is used in an integration test so we want defined behavior. if (true) { throw new StopExecutionException() } } task myTask { dependsOn('compile') doLast { println 'I am not affected' } }
每一個任務都有一個 enabled
默認爲的標誌 true
。將其設置爲 false
阻止執行任何任務動做。禁用的任務將標記爲「跳過」。
task disableMe { doLast { println 'This should not be printed if the task is disabled.' } } disableMe.enabled = false
每一個任務都有一個 timeout
可用於限制其執行時間的屬性。當任務達到超時時,其任務執行線程將被中斷。該任務將被標記爲失敗。終結器任務仍將運行。若是 --continue
使用,其餘任務能夠在此以後繼續運行。不響應中斷的任務沒法超時。Gradle 的全部內置任務均會及時響應超時
task hangingTask() { doLast { Thread.sleep(100000) } timeout = Duration.ofMillis(500) }
有時您想執行一個任務,該任務的行爲取決於較大或無限數量的參數值範圍。提供此類任務的一種很是好的表達方式是任務規則:
// gradle -q pingServer1 tasks.addRule("Pattern: ping<ID>") { String taskName -> if (taskName.startsWith("ping")) { task(taskName) { doLast { println "Pinging: " + (taskName - 'ping') } } } }
規則不只在從命令行調用任務時使用。您還能夠在基於規則的任務上建立 dependsOn 關係:
// gradle -q tasks.addRule("Pattern: ping<ID>") { String taskName -> if (taskName.startsWith("ping")) { task(taskName) { doLast { println "Pinging: " + (taskName - 'ping') } } } } task groupPing { dependsOn pingServer1, pingServer2 }groupPing
若是運行「 gradle -q tasks
」,將找不到名爲「 pingServer1
」或「 pingServer2
」 的任務,可是此腳本正在根據運行這些任務的請求執行邏輯。
當計劃運行終結任務時,Finalizer tasks 會自動添加到任務圖中。
//
task taskX { doLast { println 'taskX' } } task taskY { doLast { println 'taskY' } } taskX.finalizedBy taskYgradle -q taskX
即便終結任務失敗,也將執行 Finalizer tasks。
// gradle -q taskX task taskX { doLast { println 'taskX' throw new RuntimeException() } } task taskY { doLast { println 'taskY' } } taskX.finalizedBy taskY
運行結果:
Output of gradle -q taskX > gradle -q taskX taskX taskY FAILURE: Build failed with an exception. * Where: Build file '/home/user/gradle/samples/groovy/build.gradle' line: 4 * What went wrong: Execution failed for task ':taskX'. > java.lang.RuntimeException (no error message) * Try: Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights. * Get more help at https://help.gradle.org BUILD FAILED in 0s
在構建建立不管構建失敗仍是成功都必須清除的資源的狀況下,終結器任務頗有用。這種資源的一個示例是一個Web容器,它在集成測試任務以前啓動,而且即便某些測試失敗,也應始終將其關閉。
要指定終結器任務,請使用 Task.finalizedBy(java.lang.Object ...) 方法。此方法接受Task實例,任務名稱或 Task.dependsOn(java.lang.Object…) 接受的任何其餘輸入。
到此,關於 task 的講解到這裏就結束了。