Gradle是一個靈活和高效自動化構建工具,它的構建腳本採用Groovy或kotlin語言編寫,Groovy或Kotlin都是基於JVM的語言,它們的語法和java的語法有不少的相似而且兼容java的語法,因此對於java開發者,只需不多的學習成本就能快速上手Gradle開發,同時Gradle也是Android官方的構建工具,學習Gradle,可以幫助咱們更好的瞭解Android項目的構建過程,當項目構建出現問題時,咱們也能更好的排查問題,因此Gradle的學習能幫助咱們更好的管理Android項目,Gradle的官方地址以下:html
Gradle官網java
Github地址linux
一、Gradle構建腳本採用Groovy或Kotlin語言編寫,若是採用Groovy編寫,構建腳本後綴爲.gradle,在裏面可使用Groovy語法,若是採用Kotlin編寫,構建腳本後綴爲.gradle.kts,在裏面可使用Kotlin語法;android
二、由於Groovy或Kotlin都是面嚮對象語言,因此在Gradle中到處皆對象,Gradle的.gradle或.gradle.kts腳本本質上是一個Project對象,在腳本中一些帶名字的配置項如buildscript、allprojects等本質上就是對象中的方法,而配置項後面的閉包{}就是參數,因此咱們在使用這個配置項時本質上是在調用對象中的一個方法;git
三、在Groovy或Kotlin中,函數和類同樣都是一等公民,它們都提供了很好的閉包{}支持,因此它們很容易的編寫出具備DSL風格的代碼,用DSL編寫構建腳本的Gradle比其餘採用xml編寫構建腳本的構建工具如maven、Ant等的可讀性更強,動態性更好,總體更簡潔;github
四、Gradle中主要有Project和Task對象,Project是Gradle中構建腳本的表示,一個構建腳本對應一個Project對象,Task是Gradle中最小的執行單元,它表示一個獨立的任務,Project爲Task提供了執行的上下文。api
本文的全部示例都是採用Groovy語言編寫,在閱讀本文前先簡單的入門Groovy:bash
Groovy 使用徹底解析markdown
下面主要講Groovy與java的主要區別:閉包
一、Groovy語句後面的分號能夠忽略
int num1 = 1
int num2 = 2
int result = add(num1, num2)
int add(int a, int b){
return a + b
}
複製代碼
二、Groovy支持動態類型推導,使用def來定義變量和方法時能夠不指定變量的類型和方法的返回值類型,同時定義方法時參數能夠不指定類型
def num1 = 1
def num2 = 2
def result = add(num1, num2)
def add(a, b){
return a + b
}
複製代碼
三、Groovy的方法調用傳參時能夠不添加括號,方法不指定return語句時,最後一行默認爲返回值
def result = add 1, 2
def add(a, b){
a + b
}
複製代碼
四、Groovy能夠用單、雙、三引號來表示字符串,其中單引號表示普通字符串,雙引號表示的字符串可使用取值運算符${},而$在單引號只只是表示一個普通的字符,三引號表示的字符串又稱爲模版字符串,它能夠保留文本的換行和縮進格式,三引號一樣不支持$
def world = 'world'
def str1 = 'hello ${world}'
def str2 = "hello ${world}"
def str3 = '''hello &{world}'''
//打印輸出:
//hello ${world}
//hello world
//hello
//&{world}
複製代碼
五、Groovy會爲類中每一個沒有可見性修飾符的字段生成get/set方法,咱們訪問這個字段實際上是調用它的get/set方法,同時若是類中的方法以get/set開頭,咱們也能夠像普通字段同樣訪問這個方法
class Person{
def name
private def country
def setNation(nation){
this.country = nation
}
def getNation(){
return country
}
}
def person = new Person()
//訪問字段
person.name = 'rain9155'
println person.name
//像字段同樣訪問這個以get/set開頭的方法
person.nation = "china"
println person.nation
複製代碼
六、Groovy中的閉包是用{參數列表 -> 代碼體}表示的代碼塊,當參數 <= 1個時,-> 箭頭能夠省略,同時當只有一個參數時,若是不指定參數名默認以it爲參數名,在Groovy中閉包對應的類型是Closure,因此閉包能夠做爲參數傳遞,同時閉包有一個delegate字段, 經過delegate能夠把閉包中的執行代碼委託給任意對象來執行
class Person{
def name
def age
}
def person = new Person()
//定義一個閉包
def closure = {
name = 'rain9155'
age = 21
}
//把閉包委託給person對象執行
closure.delegate = person
//執行閉包,或者調用closure.call()也能夠執行閉包
closure()
//以上代碼把閉包委託給person對象執行,因此閉包就執行時就處於person對象上下文
//因此閉包就能夠訪問到person對象中的name和age字段,完成賦值
println person.name//輸出:rain9155
println person.age//輸出:21
複製代碼
在Gradle中不少地方都使用了閉包的委託機制,經過閉包完成一些特定對象的配置,在Gradle中,若是你沒有指定閉包的delegate,delegate默認爲當前項目的Project對象。
以上Groovy知識,認爲對於有java基礎的人來講,用於學習Gradle足夠了,固然對於一些集合操做、文件操做等,等之後使用到時能夠到Groovy官網來查漏補缺。
官方教程:Installing Gradle
安裝Gradle前須要確保你的電腦已經配置好JDK,JDK的版本要求是8或更高,能夠經過包管理器自動安裝Gradle或手動下載Gradle安裝兩種方式,在Window平臺下我推薦使用手動安裝,在Mac平臺下我推薦使用Homebrew包管理器自動安裝:
gradle -v
校驗是否配置成功,輸出如下相似信息則配置成功.C:\Users\HY>gradle -v
------------------------------------------------------------
Gradle 6.5
------------------------------------------------------------
Build time: 2020-06-02 20:46:21 UTC
Revision: a27f41e4ae5e8a41ab9b19f8dd6d86d7b384dad4
Kotlin: 1.3.72
Groovy: 2.5.11
Ant: Apache Ant(TM) version 1.10.7 compiled on September 1 2019
JVM: 10.0.2 ("Oracle Corporation" 10.0.2+13)
OS: Windows 10 10.0 amd64
複製代碼
brew install gradle
,它默認會下載安裝binary-only版本;gradle -v
校驗是否安裝成功.Gradle項目可使用Android Studio、IntelliJ IDEA等IDE工具或文本編輯器來編寫,這裏我以Mac平臺爲例採用文本編輯器配合命令行來編寫,Window平臺相似。
新建一個目錄,如我這裏爲:~/GradleDemo,打開命令行,輸入cd ~/GradleDemo
切換到這個目錄,而後輸入gradle init
,接着gradle會執行init
這個task任務,它會讓你選擇生成的項目模版、編寫腳本的語言、項目名稱等,我選擇了basic模版(即原始的Gradle項目)、Groovy語言、項目名稱爲GradleDemo,以下:
這樣會init
任務就會自動替你生成相應的項目模版,以下:
忽略掉以.開頭的隱藏文件或目錄,gradle init爲咱們自動生成了如下文件或目錄:
它表示Gradle的項目構建腳本,在裏面咱們能夠經過Groovy來編寫腳本,在Gradle中,一個build.gradle就對應一個項目,build.gradle放在Gradle項目的根目錄下,表示它對應的是根項目,build.gradle放在Gradle項目的其餘子目錄下,表示它對應的是子項目,Gradle構建時會爲每個build.gradle建立一個對應的Project對象,這樣編寫build.gradle時就可使用Project接口中的方法。
它表示Gradle的多項目配置腳本,存放在Gradle項目的根目錄下,在裏面能夠經過include來決定哪些子項目會參與構建,Gradle構建時會爲settings.gradle建立一個對應的Settings對象,include也只是Settings接口中的一個方法。
gradle init
執行時會同時執行wrapper
任務,wrapper
任務會建立gradle/wrapper目錄,並建立gradle/wrapper目錄下的gradle-wrapper.jar、gradle-wrapper.properties這兩個文件,還同時建立gradlew、gradlew.bat這兩個腳本,它們統稱爲Gradle Wrapper,是對Gradle的一層包裝。
Gradle Wrapper的做用就是可讓你的電腦在不安裝配置Gradle環境的前提下運行Gradle項目,例如當你的Gradle項目被用戶A clone下來時,而用戶A的電腦上沒有安裝配置Gradle環境,用戶A經過Gradle構建項目時,Gradle Wrapper就會從指定下載位置下載Gradle,並解壓到電腦的指定位置,而後用戶A就能夠在不配置Gradle系統變量的前提下在Gradle項目的命令行中運行gradlew或gradlew.bat腳原本使用gradle命令,假設用戶A要運行gradle -v
命令,在linux平臺下只須要運行./gradlew -v
,在window平臺下只須要運行gradlew -v
,只是把gradle
替換成gradlew
。
Gradle Wrapper的每一個文件含義以下:
一、gradlew:用於在linux平臺下執行gradle命令的腳本;
二、gradlew.bat:用於在window平臺下執行gradle命令的腳本;
三、gradle-wrapper.jar:包含Gradle Wrapper運行時的邏輯代碼;
四、gradle-wrapper.properties:用於指定Gradle的下載位置和解壓位置;
gradle-wrapper.properties中各個字段解釋以下:
字段名 | 解釋 |
---|---|
distributionBase | 下載的Gradle的壓縮包解壓後的主目錄,爲GRADLE_USER_HOME,在window中它表示C:/用戶/你電腦登陸的用戶名/.gradle/,在mac中它表示~/.gradle/ |
distributionPath | 相對於distributionBase的解壓後的Gradle的路徑,爲wrapper/dists |
distributionUrl | Grade壓縮包的下載地址,在這裏能夠修改下載的Gradle的版本和版本類型(binary或complete),例如gradle-6.5-all.zip表示Gradle 6.5的complete版本,gradle-6.5-bin.zip表示Gradle 6.5的binary版本 |
zipStoreBase | 同distributionBase,不過是表示存放下載的Gradle的壓縮包的主目錄 |
zipStorePath | 同distributionPath,不過是表示存放下載的Gradle的壓縮包的路徑 |
使用Gradle Wrapper後,就能夠統一項目在不一樣用戶電腦上的Gradle版本,同時沒必要讓運行這個Gradle項目的人安裝配置Gradle環境,提升了開發效率。
如今咱們建立的Gradle項目默認已經有一個根項目了,它的build.gradle文件就處於Gradle項目的根目錄下,若是咱們想要添加多個子項目,這時就須要經過settings.gradle進行配置。
首先咱們在GradleDemo中建立多個文件夾,這裏我建立了4個文件夾,分別爲:subproject_一、subproject_2,subproject_3,subproject_4,而後在每一個新建的文件夾下建立build.gradle文件,以下:
接着打開settings.gradle,添加以下:
include ':subproject_1', ':subproject_2', ':subproject_3', ':subproject_4'
複製代碼
這樣就完成了子項目的添加,打開命令行,切換到GradleDemo目錄處,輸入gradle projects
,執行projects任務展現全部項目之間的依賴關係,以下:
# chenjianyu @ FVFCG2HJL414 in ~/GradleDemo
$ gradle projects
> Task :projects
Root project 'GradleDemo'
+--- Project ':subproject_1'
+--- Project ':subproject_2'
+--- Project ':subproject_3'
\--- Project ':subproject_4'
BUILD SUCCESSFUL in 540ms
1 actionable task: 1 executed
複製代碼
能夠看到,4個子項目依賴於根項目,接下來咱們來配置項目,配置項目通常在當前項目的build.gradle中進行,能夠經過buildscript方法、repositories方法、dependencies方法等Project接口提供的方法進行配置,可是若是有多個項目時,而每一個項目的某些配置又同樣,那麼在每一個項目的build.gradle進行相同的配置是很浪費時間,而Gradle的Project接口爲咱們提供了allprojects和subprojects方法,在根項目的build.gradle中使用這兩個方法能夠全局的爲全部子項目進行配置,allprojects和subprojects的區別是:allprojects的配置包括根項目而subprojects的配置不包括根項目,例如:
//根項目的build.gradle
//爲全部項目添加maven repo地址
allprojects {
repositories {
mavenCentral()
}
}
//爲全部子項目添加groovy插件
subprojects {
apply plugin: 'groovy'
}
複製代碼
當在命令行輸入gradle build
構建整個項目或gradle task名稱
執行某個任務時就會進行Gradle的構建,它的構建過程分爲3個階段:
init(初始化階段) -> configure(配置階段) -> execute(執行階段)
Gradle在上面3個階段中每個階段的開始和結束都會hook一些監聽,暴露給開發者使用,方便開發者在Gradle的不一樣生命週期階段作一些事情。
settings.gradle和build.gradle分別表明Settings對象和Project對象,它們都有一個Gradle對象,咱們能夠在Gradle項目根目錄的settings.gradle或build.gradle中獲取到Gradle對象,而後進行生命週期監聽,以下:
//build.gradle或settings.gradle
this.gradle.buildStarted {
println "Gradle構建開始"
}
//---------------------init開始--------------------------------
this.gradle.settingsEvaluated {
println "settings.gradle解析完成"
}
this.gradle.projectsLoaded {
println "全部項目從settings加載完成"
}
//---------------------init結束--------------------------------
//-------------------configure開始-----------------------------
this.gradle.beforeProject {project ->
//每個項目構建以前被調用
println "${project.name}項目開始構建"
}
this.gradle.afterProject {project ->
//每個項目構建完成被調用
println "${project.name}項目構建完成"
}
this.gradle.projectsEvaluated {
println "全部項目構建完成"
}
this.gradle.taskGraph.whenReady {
println("task圖構建完成")
}
//-------------------configure結束-----------------------------
//-------------------execute開始-----------------------------
this.gradle.taskGraph.beforeTask {task ->
//每一個task開始執行時會調用這個方法
println("${task.name}task開始執行")
}
this.gradle.taskGraph.afterTask {task ->
//每一個task執行結束時會調用這個方法
println("${task.name}task執行完成")
}
//-------------------execute結束-----------------------------
this.gradle.buildFinished {
println "Gradle構建結束"
}
複製代碼
上面監聽方法的放置順序就是整個Gradle構建的順序,可是要注意的是Gradle的buildStarted方法永遠不會被回調,由於咱們註冊監聽的時機太晚了,當解析settings.gradle或build.gradle時,Gradle就已經構建開始了,因此這個方法也被Gradle標記爲廢棄的了,由於咱們沒有機會監聽到Gradle構建開始,同時若是你是在build.gradle中添加上面的全部監聽,那麼Gradle的settingsEvaluated和projectsLoaded方法也不會被回調,由於settings.gradle的解析是在build.gradle以前,在build.gradle中監聽這兩個方法的時機也太晚了。
也能夠經過Gradle對象的addBuildListener方法添加BuildListener來監聽Gradle的整個生命週期回調
上面是監聽整個Gradle構建的生命週期回調監聽,那麼我怎麼監聽個人當前單個項目的構建開始和結束呢?只須要在你當前項目的build.gradle中添加:
//build.gradle
this.beforeEvaluate {
println '項目開始構建'
}
this.afterEvaluate {
println '項目構建結束'
}
複製代碼
可是要注意的是在根項目的build.gradle添加上述方法,其beforeEvaluate方法是沒法被回調的,由於註冊時機太晚,解析根項目的的build.gradle時根項目已經開始構建了,可是子項目的build.gradle添加上述方法是能夠監聽到項目構建的開始和結束,由於根項目構建完成後纔會輪到子項目的構建。
Task是Gradle中最小執行單元,它是一個接口,默認實現類爲DefaultTask,在Project中提供了task方法來建立Task,因此Task的建立必需要處於Project上下文中,這裏我在subproject_1/build.gradle中建立Task,以下:
//subproject_1/build.gradle
//經過Project的task方法建立一個Task
task task1{
doFirst{
println 'one'
}
doLast{
println 'two'
}
}
複製代碼
上述代碼經過task方法建立了一個名爲task1的Task,在建立Task的同時能夠經過閉包配置它的doFirst和doLast動做,doFirst和doLast都是Task中的方法,其中doFirst方法會在Task的action執行前執行,doLast方法會在Task的action執行後執行,而action就是Task的執行單元,在後面自定義Task會介紹到,除此以外還能夠在建立Task以後再指定它的doFirst和doLast動做,以下:
//subproject_1/build.gradle
//經過Project的task方法建立一個Task
def t = task task2
t.doFirst {
println 'one'
}
t.doLast{
println 'two'
}
複製代碼
上面經過Project的task方法建立的Task默認被放在Project的TaskContainer類型的容器中,咱們能夠經過Project的getTasks方法獲取到這個容器,而TaskContainer提供了create方法來建立Task,以下:
//subproject_1/build.gradle
//經過TaskContainer的create方法建立一個Task
tasks.create(name: 'task3'){
doFirst{
println 'one'
}
doLast{
println 'two'
}
}
複製代碼
以上就是建立Task最經常使用的幾種方式,建立Task以後,就能夠執行它,執行一個Task只須要把task名稱接在gradle
命令後,以下我在命令行輸入gradle task1
執行了task1:
# chenjianyu @ FVFCG2HJL414 in ~/GradleDemo
$ gradle task1
> Task :subproject_1:task1
one
two
BUILD SUCCESSFUL in 463ms
1 actionable task: 1 executed
複製代碼
若是你想精簡輸出的信息,只須要添加-q
參數,如:gradle -q task1
,這樣輸出就只包含task1的輸出:
# chenjianyu @ FVFCG2HJL414 in ~/GradleDemo
$ gradle -q task1
one
two
複製代碼
若是要執行多個Task,多個task名稱接在gradle
命令後用空格隔開就行,如:gradle task1 task2 task3
。
Gradle爲每一個Task定義了默認的屬性(Property), 好比description、group、dependsOn、inputs、outputs等, 咱們能夠配置這些Property,以下配置Task的描述和分組:
//subproject_2/build.gradle
//咱們能夠在定義Task時對這些Property進行賦值
task task1{
group = 'MyGroup'
description = 'Hello World'
doLast{
println "task分組:${group}"
println "task描述:${description}"
}
}
複製代碼
Gradle在執行一個Task以前,會先配置這個Task的Property,而後再執行這個Task的執行代碼塊,因此配置Task的代碼塊放在哪裏都無所謂,以下:
//subproject_2/build.gradle
//在定義Task以後纔對Task進行配置
task task2{
doLast{
println "task分組:${group}"
println "task描述:${description}"
}
}
task2{
group = 'MyGroup'
description = 'Hello World'
}
//等效於task2
task task3{
doLast{
println "task分組:${group}"
println "task描述:${description}"
}
}
task3.description = 'Hello World!'
task3.group = "MyGroup"
//等效於task3
task task4{
doLast{
println "task分組:${group}"
println "task描述:${description}"
}
}
task4.configure{
group = 'MyGroup'
description = 'Hello World'
}
複製代碼
咱們能夠經過Task的dependsOn屬性指定Task之間的依賴關係,以下:
//subproject_2/build.gradle
//建立Task時經過dependsOn聲明Task之間的依賴關係
task task5(dependsOn: task4){
doLast{
println 'Hello World'
}
}
//或者在建立Task以後再聲明task之間的依賴關係
task4.dependsOn task3
複製代碼
上述的依賴關係是task3 -> task4 -> task5,依賴的Task先執行,因此當我在命令行輸入gradle task5
執行task5時,輸出:
# chenjianyu @ FVFCG2HJL414 in ~/GradleDemo
$ gradle task5
> Task :subproject_2:task3
task分組:MyGroup
task描述:Hello World!
> Task :subproject_2:task4
task分組:MyGroup
task描述:Hello World
> Task :subproject_2:task5
Hello World
task5task執行完成
BUILD SUCCESSFUL in 2s
3 actionable tasks: 3 executed
複製代碼
依次執行task三、 task4 、task5。
前面建立的Task默認都是DefaultTask類型,咱們能夠經過繼承DefaultTask來自定義Task類型,Gradle中也內置了不少具備特定功能的Task,它們都間接繼承自DefaultTask,如Copy(複製文件)、Delete(文件清理)等,咱們能夠直接在build.gradle中自定義Task,以下:
//subproject_3/build.gradle
class MyTask extends DefaultTask{
def message = 'hello world from myCustomTask'
@TaskAction
def println1(){
println "println1: $message"
}
@TaskAction
def println2(){
println "println2: $message"
}
}
複製代碼
在MyTask中,經過@TaskAction註解的方法就是該Task的action,action是Task最主要的組成,它表示Task的一個執行動做,當Task中有多個action時,多個action的執行順序按照@TaskAction註解的方法的放置逆順序,因此執行一個Task的過程就是:doFirst方法 -> action方法 -> doLast方法,在MyTask中定義了兩個action,接下來咱們使用這個Task,以下:
//subproject_3/build.gradle
//在定義Task時經過type指定Task的類型
task myTask(type: MyTask){
message = 'custom message'
}
複製代碼
在定義Task時經過type指定Task的類型,同時還能夠經過閉包配置MyTask中的message參數,在命令行輸入gradle myTask
執行這個Task,以下:
# chenjianyu @ FVFCG2HJL414 in ~
$ gradle myTask
> Task :subproject_3:myTask
println2: custom message
println1: custom message
BUILD SUCCESSFUL in 611ms
1 actionable task: 1 executed
複製代碼
咱們自定義的Task本質上就是一個類,除了直接在build.gradle文件中編寫自定義Task,還能夠在Gradle項目的根目錄下新建一個buildSrc目錄,在buildSrc/src/main/[java/kotlin/groovy]目錄中定義編寫自定義Task,能夠採用java、kotlin、groovy三種語句之一,或者在一個獨立的項目中編寫自定義Task,在後面自定義Plugin時會講到這幾種方式。
上述咱們自定義的Task每次執行時,它的action都會被執行,進行全量構建,其實Gradle支持增量式構建的Task,增量式構建就是當Task的輸入和輸出沒有變化時,跳過action的執行,當Task輸入或輸出發生變化時,在action中只對發生變化的輸入或輸出進行處理,這樣就能夠避免一個沒有變化的Task被反覆構建,還有當Task發生變化時只處理變化部分,這樣就會提升整個Gradle的構建效率,大大縮短整個Gradle的構建時間,因此當咱們編寫複雜的Task時,讓Task支持增量式構建是頗有必要的,讓Task支持增量式構建只須要作到兩步:
一、讓Task的inputs和outputs參與Gradle的Up-to-date檢查;
二、讓Task的action支持增量式構建;
下面咱們經過這兩步自定義一個簡單的、支持增量式構建的Copy任務,這個Copy任務的做用是把輸入的文件複製到輸出的位置中:
首先咱們要讓Copy任務的inputs和outputs參與Gradle的Up-to-date檢查,每個Task都有inputs和outputs屬性,它們的類型分別爲TaskInputs和TaskOutputs,Task的inputs和outputs主要有如下三種類型:
咱們能夠在自定義Task時經過註解指定Task的inputs和outputs,經過註解指定的inputs和outputs會參與Gradle的Up-to-date檢查,它是編寫增量式Task的前提,Up-to-date檢查是指Gradle每次執行Task前都會檢查Task的輸入和輸出,若是一個Task的輸入和輸出自上一次構建以來沒有發生變化,Gradle就斷定這個Task是能夠跳過執行的,這時你就會看到Task構建旁邊會有一個UP-TO-DATE文本,Gradle提供了不少註解讓咱們指定Task的inputs和outputs,經常使用的以下:
註解 | 對應的類型 | 含義 |
---|---|---|
@Input | 可序列化類型 | 指單個輸入可序列化的值,如基本類型int、string或者實現了Serializable的類 |
@InputFile | 文件類型 | 指單個輸入文件,不表示文件夾,如File、RegularFile等 |
@InputDirectory | 文件類型 | 指單個輸入文件夾,不表示文件,如File、Directory等 |
@InputFiles | 文件類型 | 指多個輸入的文件或文件夾,如FileCollection、FileTree等 |
@OutputFile | 文件類型 | 指單個輸出文件,不表示文件夾,如File、RegularFile等 |
@OutputDirectory | 文件類型 | 指單個輸出文件夾,不表示文件,如File、Directory等 |
@OutputFiles | 文件類型 | 指多個輸出的文件,如FileCollection、Map<String, File>等 |
@OutputDirectories | 文件類型 | 指多個輸出的文件夾,如FileCollection、Map<String, File>等 |
@Nested | 自定義類型 | 指一種自定義的類,這個類它可能沒有實現Serializable,但這個類裏面至少有一個屬性使用本表中的一個註解標記,即這個類會含有Task的輸入或輸出 |
@Internal | 任何類型 | 它能夠用在可序列化類型、文件類型、還有自定義類型上,它指該屬性只在Task的內部使用,即不是Task的輸入也不是Task的輸出,經過@Internal註解的屬性不參與Up-to-date檢查 |
@Optional | 任何類型 | 它能夠用在可序列化類型、文件類型、還有自定義類型上,它指該屬性是可選的,經過@Optional註解的屬性能夠不爲它賦值,關閉校驗 |
@Incremental | Provider<FileSystemLocation> 或者 FileCollection | 它和@InputFiles或@InputDirectory一塊兒使用,它用來指示Gradle跟蹤文件屬性的更改,經過@Incremental註解的文件屬性能夠經過InputChanges的getFileChanges方法查詢文件的更改,幫助實現增量構建Task |
@SkipWhenEmpty | 文件類型 | 它和@InputFiles或@InputDirectory一塊兒使用,它用來告訴Gradle若是相應的文件或文件夾爲空就跳過該Task,經過@SkipWhenEmpty註解的全部輸入屬性若是都爲空,就會致使Gradle跳過這個Task,從而在控制檯產出一個NO-SOURCE輸出結果 |
咱們自定義Task時可使用表中的註解來指定輸入和輸出,其中@InputXX是用來指定輸入屬性,@OuputXX是用來指定輸出屬性,@Nested是用來指定自定義類,這個類裏面至少含有一個使用@InputXX或@OuputXX指定的屬性,而@Internal和@Optional是能夠用來指定輸入或輸出的,最後的@Incremental和@SkipWhenEmpty是用來與@InputFiles或@InputDirectory一塊兒使用的,用於支持增量式構建任務,後面會講,還有一點要注意的是這些註解只有聲明在屬性的get方法中才有效果,前面講過groovy的字段默認都生成了get/set方法,而若是你是用java自定義Task的,要記得聲明在屬性的get方法中,咱們來看Copy任務的實現,以下:
//subproject_3/build.gradle
class CopyTask extends DefaultTask{
//使用@InputFiles註解指定輸入
@InputFiles
FileCollection from
//使用@OutputDirectory註解指定輸出
@OutputDirectory
Directory to
//複製過程:把from的文件複製到to文件夾
@TaskAction
void execute(){
File file = from.getSingleFile()
if(file.isDirectory()){
from.getAsFileTree().each {
copyFileToDir(it, to)
}
}else{
copyFileToDir(from, to)
}
}
private static void copyFileToDir(File src, Directory dir){
File dest = new File("${dir.getAsFile().path}/${src.name}")
if(!dest.exists()){
dest.createNewFile()
}
dest.withOutputStream {
it.write(new FileInputStream(src).getBytes())
}
}
}
複製代碼
這裏Copy任務只使用了@InputFiles和@OutputDirectory,經過@InputFiles指定Copy任務複製的來源文件,經過@OutputDirectory指定Copy任務複製的目標文件夾,而後在action方法中執行復制步驟,而後咱們來使用這個Copy任務,以下:
//subproject_3/build.gradle
task copyTask(type: CopyTask){
from = files('from')
to = layout.projectDirectory.dir('to')
}
複製代碼
爲了使用這個Copy任務,我在subproject_3目錄下建立了一個from文件夾,裏面只有一個名爲text1.txt的文本文件,而後把from文件夾指定爲Copy任務的輸入,to文件夾指定爲Copy任務的輸出,在命令行輸入gradle copyTask
執行這個Task,以下:
# in ~/GradleDemo
$ gradle copyTask
> Task :subproject_3:copyTask
BUILD SUCCESSFUL in 2s
1 actionable task: 1 executed
複製代碼
任務執行成功後就能夠把from文件夾中的文件複製到to文件夾,此時文件結構以下:
subproject_3
|_ build.gradle
|_ from
| |_ text1.txt
|_ to
|_ text1.txt
複製代碼
接着咱們再次在命令行輸入gradle copyTask
執行這個Task,以下:
# in ~/GradleDemo
$ gradle copyTask
> Task :subproject_3:copyTask UP-TO-DATE
BUILD SUCCESSFUL in 634ms
1 actionable task: 1 up-to-date
複製代碼
再次執行時因爲Copy任務的輸入和輸出都沒有變化,因此Gradle斷定爲UP-TO-DATE,跳過執行。
目前Copy任務已經支持Up-to-date檢查,但還不支持增量構建,即若是此時咱們往from文件夾新增一個text2.txt文件,因爲Copy任務的輸入發生變化,這時從新執行Copy任務時就會從新執行action方法,進行全量構建,把text1.txt、text2.txt文件複製到to文件夾中,你會發現text1.txt被重複複製了,咱們但願的是每次from中新增或修改文件時,只對新增或修改的文件進行復制,而以前沒有變化的文件不進行復制,因此要作到這一步,還要讓Task的action方法支持增量構建,要讓Task的action方法支持增量式構建,只須要讓action方法帶一個InputChanges類型的參數就能夠,帶InputChanges類型參數的action方法表示這是一個增量任務操做方法,該參數告訴Gradle,該action方法僅須要處理更改的輸入,此外,Task還須要經過使用 @Incremental或@SkipWhenEmpty來指定至少一個增量文件輸入屬性,咱們繼續看Copy任務的實現,以下:
//subproject_3/build.gradle
class CopyTask extends DefaultTask{
//新增@Incremental註解
@Incremental
@InputFiles
FileCollection from
@OutputDirectory
Directory to
// @TaskAction
// void execute(){
// File file = from.getSingleFile()
// if(file.isDirectory()){
// from.getAsFileTree().each {
// copyFileToDir(it, to)
// }
// }else{
// copyFileToDir(from, to)
// }
// }
//帶有InputChanges類型參數的action方法
@TaskAction
void executeIncremental(InputChanges inputChanges) {
println "execute: isIncremental = ${inputChanges.isIncremental()}"
inputChanges.getFileChanges(from).each {change ->
if(change.fileType != FileType.DIRECTORY){
println "changeType = ${change.changeType}, changeFile = ${change.file.name}"
if(change.changeType != ChangeType.REMOVED){
copyFileToDir(change.file, to)
}
}
}
}
private static void copyFileToDir(File src, Directory dir){
File dest = new File("${dir.getAsFile().path}/${src.name}")
if(!dest.exists()){
dest.createNewFile()
}
dest.withOutputStream {
it.write(new FileInputStream(src).getBytes())
}
}
}
複製代碼
Copy任務中經過@Incremental指定了須要增量處理的輸入,而後在action方法中經過InputChanges進行增量複製文件,咱們能夠經過InputChanges的getFileChanges方法獲取變化的文件,該方法接收一個FileCollection類型的參數,傳入的參數必需要經過@Incremental或@SkipWhenEmpty註解,getFileChanges方法返回的是一個FileChange列表,FileChange持有變化的文件File、文件類型FileType和文件的變化類型ChangeType,這樣咱們就能夠根據變化的文件、ChangeType、FileType進行增量輸出,ChangeType有三種取值:
同時並非每次執行都是增量構建,咱們能夠經過InputChanges的isIncremental方法判斷本次構建是不是增量構建,當處於如下狀況時,Task會以非增量形式即全量執行:
當Task處於非增量構建時,即InputChanges的isIncremental方法返回false時,經過InputChanges的getFileChanges方法能獲取到全部的輸入文件,而且每一個文件的ChangeType都爲ADDED,當Task處於增量構建時,即InputChanges的isIncremental方法返回true時,經過InputChanges的getFileChanges方法能獲取到只發生變化的輸入文件。
接下來讓咱們在上面的基礎下執行Copy任務,在命令行輸入gradle copyTask
執行這個Task,以下:
# in ~/GradleDemo
$ gradle copyTask
> Task :subproject_3:copyTask
execute: isIncremental = false
changeType = ADDED, changeFile = text1.txt
BUILD SUCCESSFUL in 22s
1 actionable task: 1 executed
複製代碼
能夠看到第一次執行Task,以非增量方式執行,把from/text1.txt文件複製到了to文件夾,接下來,咱們在from文件夾下新增text2.txt、text3.text,而後再次在命令行輸入gradle copyTask
執行Copy任務,以下:
# in ~/GradleDemo
$ gradle copyTask
> Task :subproject_3:copyTask
execute: isIncremental = true
changeType = ADDED, changeFile = text2.txt
changeType = ADDED, changeFile = text3.txt
BUILD SUCCESSFUL in 2s
1 actionable task: 1 executed
複製代碼
能夠看到當咱們新增文件後第二次執行Task,以增量方式執行,只把新增的text2.txt、 text3.txt複製到to文件夾,而text1.txt沒有被重複複製,此時文件結構以下:
subproject_3
|_ build.gradle
|_ from
| |_ text1.txt
| |_ text2.txt
| |_ text3.txt
|_ to
|_ text1.txt
|_ text2.txt
|_ text3.txt
複製代碼
接下來咱們把text1.txt修改在裏面添加一行hello world,而後再次在命令行輸入gradle copyTask
執行Copy任務,以下:
# in ~/GradleDemo
$ gradle copyTask
> Task :subproject_3:copyTask
execute: isIncremental = true
changeType = MODIFIED, changeFile = text1.txt
BUILD SUCCESSFUL in 2s
1 actionable task: 1 executed
複製代碼
能夠看到當咱們修改文件後第三次執行Task,以增量方式執行,只把修改的text1.txt複製到to文件夾,而其餘文件沒有被重複複製,接下來咱們把to文件夾裏的text1.txt刪除,而後再次在命令行輸入gradle copyTask
執行Copy任務,以下:
# in ~/GradleDemo
$ gradle copyTask
> Task :subproject_3:copyTask
execute: isIncremental = false
changeType = ADDED, changeFile = text1.txt
changeType = ADDED, changeFile = text2.txt
changeType = ADDED, changeFile = text3.txt
BUILD SUCCESSFUL in 2s
1 actionable task: 1 executed
複製代碼
能夠看到當咱們刪除某個輸出文件後第四次執行Task,以非增量方式執行,把from文件夾下的文件從新複製到to文件夾,而且全部的文件狀態都是ADDED,此時若是咱們不作任何操做,再次在命令行輸入gradle copyTask
執行Copy任務會輸出UP-TO-DATE。
到如今咱們就實現了一個支持增量式構建的Copy任務,經過Gradle提供的註解和InputChanges,咱們很容易就自定義一個支持增量構建的Task,當你編寫的Task支持增量構建後,你還能夠考慮更進一步,讓你的Task的支持延遲配置 - Lazy Configuration,這是Gradle提供的對屬性的一種狀態管理,就不在本文展開了。
Plugin能夠理解爲一系列Task的集合,經過實現Plugin接口的apply方法就能夠自定義Plugin,自定義的Plugin本質上也是一個類,因此和Task相似,在Gradle中也提供了3種方式來編寫自定義Plugin:
因爲在上面自定義Task的介紹中已經講過了如何在build.gradle中直接編寫,自定義Plugin也相似,因此下面就主要介紹二、3兩種方式,並且這兩種方式也是平時開發中自定義Plugin最經常使用的方式。
在GradleDemo中新建一個buildSrc目錄,而後在buildSrc目錄新建src/main/groovy目錄,若是你要使用java或kotlin,則新建src/main/java或src/main/kotlin,src/main/groovy目錄下你還能夠繼續建立package,這裏個人package爲com.example.plugin,而後在該package下新建一個類MyPlugin.groovy,該類繼承自Plugin接口,以下:
class MyPlugin implements Plugin<Project>{
@Override
void apply(Project project){}
}
複製代碼
如今MyPlugin中沒有任何邏輯,咱們平時是在build.gradle中經過apply plugin: 'Plugin名' 來引用一個Plugin,而apply plugin中的apply就是指apply方法中的邏輯,而apply方法的參數project指的就是引用該Plugin的build.gradle對應的Project對象,接下來咱們讓咱們在apply方法中編寫邏輯,以下:
package com.example.plugin
import org.gradle.api.*
class MyPlugin implements Plugin<Project>{
@Override
void apply(Project project){
//經過project的ExtensionContainer的create方法建立一個名爲outerExt的擴展,擴展對應的類爲OuterExt
OuterExt outerExt = project.extensions.create('outerExt', OuterExt.class)
//經過project的task方法建立一個名爲showExt的Task
project.task('showExt'){
doLast{
//使用OuterExt實例
println "outerExt = ${outerExt}"
}
}
}
/** * 自定義插件的擴展對應的類 */
static class OuterExt{
String message
@Override
String toString(){
return "[message = ${message}]"
}
}
}
複製代碼
上述我在apply方法中建立了一個擴展和一個Task,其中Task好理解,那麼擴展是什麼?咱們平時引用android插件時,必定見過這樣相似於android這樣的命名空間,以下:
apply plugin: 'com.android.application'
android {
compileSdkVersion 29
buildToolsVersion "29.0.3"
//...
}
複製代碼
它並非一個名爲android的方法,它而是android插件中名爲android的擴展,該擴展對應一個bean類,該bean類中有compileSdkVersion、buildToolsVersion等方法,因此配置android就是在配置andorid對應的bean類,如今回到咱們的MyPlugin中,MyPlugin也定義了一個bean類:OuterExt,該bean類有messag字段,Groovy會自動爲咱們生成messag的get/set方法,而apply方法中經過project實例的ExtensionContainer的create方法建立一個名爲outerExt的擴展,擴展對應的bean類爲OuterExt,擴展的名字能夠隨便起,其中ExtensionContainer相似於TaskContainer,它也是Project中的一個容器,這個容器存放Project中全部的擴展,經過ExtensionContainer的create方法能夠建立一個擴展,create方法返回的是擴展對應的類的實例,這樣咱們使用MyPlugin就能夠這樣使用,以下:
//subproject_4/build.gradle
apply plugin: com.example.plugin.MyPlugin
outerExt {
message 'hello'
}
//執行gradle showExt, 輸出:
//outerExt = [message = hello]
複製代碼
擴展的特色就是能夠經過閉包來配置擴展對應的類,這樣就能夠經過擴展outerExt來配置咱們的Plugin,不少自定義Plugin都是都經過添加擴展這種方式來配置自定義的Plugin,不少人就問了,那麼相似於android的嵌套DSL如何實現,以下:
apply plugin: 'com.android.application'
android {
compileSdkVersion 29
buildToolsVersion "29.0.3"
defaultConfig {
applicationId "com.example.myapplication"
minSdkVersion 16
targetSdkVersion 29
//...
}
//...
}
複製代碼
android{}中有嵌套了一個defaultConfig{},可是defaultConfig並非一個擴展,而是一個名爲defaultConfig的方法,參數爲Action類型,它是一個接口,裏面只有一個execute方法,這裏就我參考android 插件的內部實現實現了嵌套DSL,嵌套DSL能夠簡單的理解爲擴展對應的類中再定義一個類,MyPlugin的實現以下:
package com.example.plugin
import org.gradle.api.*
import org.gradle.api.model.* //新引入
import javax.inject.Inject //新引入
class MyPlugin implements Plugin<Project>{
@Override
void apply(Project project){
OuterExt outerExt = project.extensions.create('outerExt', OuterExt.class)
project.task('showExt'){
doLast{
//使用OuterExt實例和InnerExt實例
println "outerExt = ${outerExt}, innerExt = ${outerExt.innerExt}"
}
}
}
static abstract class OuterExt{
String message
//嵌套類
InnerExt innerExt
//定義一個使用@Inject註解的、抽象的獲取ObjectFactory實例的get方法
@Inject
abstract ObjectFactory getObjectFactory()
OuterExt(){
//經過ObjectFactory的newInstance方法建立嵌套類innerExt實例
this.innerExt = getObjectFactory().newInstance(InnerExt.class)
}
//定義一個方法,方法名爲能夠隨意起,方法的參數類型爲Action,泛型類型爲嵌套類InnerExt
void inner(Action<InnerExt> action){
//調用Action的execute方法,傳入InnerExt實例
action.execute(innerExt)
}
@Override
String toString(){
return "[message = ${message}]"
}
static class InnerExt{
String message
@Override
String toString(){
return "[message = $message]"
}
}
}
}
複製代碼
使用MyPlugin就能夠這樣使用,以下:
//subproject_4/build.gradle
apply plugin: com.example.plugin.MyPlugin
outerExt {
message 'hello'
//使用inner方法
inner{
message 'word'
}
}
//執行gradle showExt, 輸出:
//outerExt = [message = hello], innerExt = [message = word]
複製代碼
outerExt {}中嵌套了inner{},其中inner是一個方法,參數類型爲Action,Gradle內部會把inner方法後面的閉包配置給InnerExt類,這是Gradle中的一種轉換機制,總的來講,定義嵌套DSL的大概步驟以下:
上面4步就是定義嵌套DSL時須要在自定義Plugin中作的事,還有一點要注意的是,對於擴展對應的bean類,若是你把它定義在自定義的Plugin的類文件中,必定要用static修飾,如這裏的OuterExt類、InnerExt類使用了static修飾,或者把它們定義在單獨的類文件中。
除了以上這種爲單個對象配置的方式,Gradle還爲咱們提供了更爲靈活地對多個相同類型對象進行配置的方式,又名命名對象容器,它相似於android中buildType{}, 以下:
android {
//...
buildTypes {
release {
//..
}
debug {
//...
}
}
}
複製代碼
上面buildTypes中定義了2個命名空間,分別爲:release、debug,每一個命名空間都會生成一個BuildType配置,在不一樣的場景下使用,而且我還能夠根據使用場景定義更多的命名空間如:test、testDebug等,buildTypes{}中的命名空間的數量不定、名字不定,這是由於buildTypes是經過NamedDomainObjectContainer容器實現的,下面在前面的基礎上實現OuterExt對應的命名對象容器:
package com.example.plugin
import org.gradle.api.*
import org.gradle.api.model.*
import javax.inject.Inject
class MyPlugin implements Plugin<Project>{
@Override
void apply(Project project){
//經過project的ObjectFactory的domainObjectContainer方法建立OuterExt的Container實例
NamedDomainObjectContainer<OuterExt> outerExtContainer = project.objects.domainObjectContainer(OuterExt.class)
//而後再經過project的ExtensionContainer的add方法添加名稱和OuterExt的Container實例的映射
project.extensions.add('outerExts', outerExtContainer)
//經過project的task方法建立一個名爲showExts的Task
project.task('showExts'){
doLast{
//遍歷OuterExt的Container實例,逐個輸出配置的值
outerExtContainer.each{ext ->
println "${ext.name}: outerExt = ${ext}, innerExt = ${ext.innerExt}"
}
}
}
}
static abstract class OuterExt{
String message
InnerExt innerExt
@Inject
abstract ObjectFactory getObjectFactory()
//NamedDomainObjectContainer要求它的元素必需要有一個只可讀的、名爲name的常量字符串
private final String name
//只可讀的name表示name要私有的,而且提供一個get方法,name的值在構造函數中注入
String getName(){
return this.name
}
//經過@Inject註解帶有String類型參數的構造
@Inject
OuterExt(String name){
//在構造中爲name賦值
this.name = name
this.innerExt = getObjectFactory().newInstance(InnerExt.class)
}
void inner(Action<InnerExt> action){
action.execute(innerExt)
}
@Override
String toString(){
return "[message = ${message}]"
}
static class InnerExt{
String message
@Override
String toString(){
return "[message = ${message}]"
}
}
}
}
複製代碼
使用MyPlugin就能夠這樣使用,以下:
//subproject_4/build.gradle
apply plugin: com.example.plugin.MyPlugin
outerExts{
//定義名爲ext1的命名空間
ext1{
message 'hello'
inner{
message 'word'
}
}
//定義名爲ext2的命名空間
ext2{
message 'hello'
inner{
message 'word'
}
}
}
//執行gradle showExts, 輸出:
//ext1: outerExt = [message = hello], innerExt = [message = word]
//ext2: outerExt = [message = hello], innerExt = [message = word]
複製代碼
outerExts能夠想象爲一個容器,而後容器的元素就是OuterExt,放進容器中的元素都要有一個名字,這個名字是任意的,上面咱們經過DSL在outerExts擴展中配置了兩個OuterExt,一個名爲ext1,一個名爲ext2,而後咱們在Plugin中就能夠經過遍歷outerExts獲取每一個元素對應的配置,因此總的來講,定義命名對象容器做爲擴展的大概步驟以下:
一、首先要肯定NamedDomainObjectContainer容器的元素類型,如我這裏爲OuterExt類,容器中的每一個元素必需要有一個只可讀的、名爲name的常量字符串,而後提供一個使用@Inject註解的帶有String類型參數的構造,在構造中爲name賦值,這個name就對應你在DSL中填寫的命名,如我這裏爲ext一、ext2,當咱們在DSL中添加命名空間時,其實就是在往容器中添加元素,這時Gradle會自動調用元素帶有String類型參數的構造來實例化它,並在構造中傳入命名字符串;
二、經過Project的getObjects方法獲取ObjectFactory實例,調用ObjectFactory的domainObjectContainer方法建立容器實例,前面講過經過ObjectFactory實例化的對象能夠被閉包配置,如我這裏建立的容器爲NamedDomainObjectContainer<OuterExt>;
三、而後再經過Project的getExtensions方法獲取ExtensionContainer實例,調用ExtensionContainer的add方法添加擴展名和容器實例的映射,如我這裏添加了名爲outerExts的擴展,擴展對應的類爲NamedDomainObjectContainer<OuterExt>.
上面3步就是定義命名對象容器做爲擴展時須要在自定義Plugin中作的事,一、2步驟是如何定義一個命名對象容器,2步驟是把命名對象容器添加爲一個擴展,可是每每命名對象容器只是做爲某個擴展中的一個嵌套DSL,而不是直接做爲擴展,這時咱們只須要結合前面講過的定義擴展步驟、定義嵌套DSL步驟和這裏的一、2步驟就行,這時咱們就能夠實現下面的DSL寫法:
//擴展
outerExt{
message 'hello'
//經過命名對象容器配置
exts{
ext1{
message 'word'
}
ext2{
message 'word'
}
}
}
複製代碼
這時咱們已經很接近android gradle plugin提供的android{}擴展的DSL寫法了,在自定義Plugin中如何實現就留給你們本身實現了,只要結合一下前面所講過的方法就行,上面所介紹的擴展、嵌套DSL、命名對象容器已經能知足自定義Plugin開發時獲取配置的大部分場景了。
在獨立項目中編寫和在buildSrc目錄下編寫是同樣的,只是多了一個發佈過程,這裏我爲了方便就不新建一個獨立項目了,而是在GradleDemo中新增一個名爲gradle_plugin的子項目,而後在gradle_plugin下新建一個src/main/groovy和src/main/resources目錄,接着把剛剛在buildSrc編寫的com.example.plugin.MyPlugin複製到src/main/groovy下,最後在GradleDemo新建一個repo目錄,看成待會發布插件時的倉庫,此時GradleDemo結構以下:
在Gradle中有兩種方式在腳本中引入一個Plugin,一種是在buildScript中引入類路徑,而後經過apply plugin引入插件,以下
//build.gradle
buildscript {
repositories {
//定義插件所屬倉庫
}
dependencies {
classpath '插件類路徑'
}
}
apply plugin: '插件id'
複製代碼
一種是直接經過plugins DSL引入,以下:
//setting.gradle
pluginManagement{
repositories{
//定義插件所屬倉庫
}
}
//build.gradle
plugins{
id '插件id' version '插件版本'
}
複製代碼
兩種方式分別對應着兩種類型的發佈方式,下面分別介紹:
由於gradle_plugin項目中的MyPlugin.groovy使用了Gradle的相關api,如Project等,因此你要在gradle_plugin/build.gradle中引進Gradle api,打開gradle_plugin/build.gradle,添加以下:
//gradle_plugin/build.gradle
dependencies {
//這樣gradle_plugin/src/main/groovy/中就可使用Gradle和Groovy語法
implementation gradleApi()
}
複製代碼
如今MyPlugin已經有了,咱們須要給插件起一個名字,在gradle_plugin/src/main/resources目錄下新建META-INF/gradle-plugins目錄,而後在該目錄下新建一個XX.properties文件,XX就是你想要給插件起的名字,就是apply plugin後填寫的插件名字,也稱爲插件id,例如andorid gradle plugin的插件id叫com.android.application,因此它的properties文件爲com.android.application.properties,這裏我給個人MyPlugin插件起名爲myplugin,因此我新建myplugin.properties,以下:
打開myplugin.properties文件,添加以下:
# 在這裏指明自定義插件的實現類
implementation-class=com.example.plugin.MyPlugin 複製代碼
經過implementation-class指明你要發佈的插件的實現類,如這裏爲com.example.plugin.MyPlugin,接下來咱們就來發布插件。
發佈插件你能夠選擇你要發佈到的倉庫,如maven、lvy,咱們最經常使用的就是maven了,因此這裏我選擇maven,Gradle提供了maven-publish插件來幫助咱們發佈到maven倉庫,在gradle_plugin/build.gradle添加以下:
//gradle_plugin/build.gradle
plugins{
//引入maven-publish, maven-publish屬於Gradle核心插件,核心插件能夠省略version
id 'maven-publish'
}
//publishing是maven-publish提供的擴展,經過repositories定義發佈的maven倉庫位置,能夠指定本地目錄地址或遠端repo地址
publishing.repositories {
//這裏我指定了項目根目錄下的repo目錄
maven {
url '../repo'
}
//能夠指定遠端repo地址
//maven{
// url 'https://xxx'
// credentials {
// username 'xxx'
// password xxx
// }
//}
}
//經過publications定義發佈的組件
publishing.publications {
//相似於命令容器對象,添加名爲myplugin的的發佈
myplugin(MavenPublication){
//配置自定義插件的groupId、artifactId和version
groupId = 'com.example.customplugin'
artifactId = 'myplugin'
version = '1.0'
//經過from引入打包jar的components
from components.java
}
}
複製代碼
咱們首先經過repositories{}定義了發佈倉庫,這裏爲本地目錄,而後在publications{}中定義了名爲myplugin的發佈,它會被maven-publish插件用來生成發佈任務,在其中經過groupId、artifactId和version配置了插件的GAV座標,這樣發佈後咱們就能夠經過GAV座標引用插件,即在dependencies{}中使用groupId:artifactId:version的形式來引用咱們發佈的插件,同時還引入了components.java,它是java插件爲咱們提供的把代碼打包成jar的能力,當你作完這一切後,同步Gradle項目,maven-publish插件會爲咱們生成兩種類型的發佈任務:publishXXPublicationToMavenRepository和publishXXPublicationToMavenLocal,publishXXPublicationToMavenRepository任務表示把插件發佈到maven遠程倉庫中,publishXXPublicationToMavenLocal任務表示把插件發佈到maven本地倉庫,其中XX就是在publications{}定義的發佈名字,如我這裏爲myplugin。
接着在Gradle項目所處目錄的命令行輸入gradle publishMypluginPublicationToMavenLocal
把myplugin插件發佈到maven本地倉庫,任務執行成功後,在maven本地倉庫(mac下爲~/.m/repository/,在window下爲:C:/用戶/用戶名/.m/repository/)中會看到發佈的插件jar和pom文件,以下:
接着在Gradle項目所處目錄的命令行輸入gradle publishMypluginPublicationToMavenRepository
把myplugin插件發佈到maven遠程倉庫,任務執行成功後,在GradleDemo/repo/中會看到發佈的插件jar和pom文件,以下:
如今MyPlugin 1.0已經發布成功了,接下來讓咱們使用這個插件,在subproject_4/build.gradle中添加以下:
//subproject_4/build.gradle
buildscript {
repositories {
//添加maven本地倉庫
mavenLocal()
//添加發布時指定的maven遠程倉庫
maven {
url uri('../repo')
}
//能夠添加maven中央倉庫
//mavenCentral()
}
dependencies {
//classpath填寫插件的GAV座標,gradle編譯時會掃描該classpath下的全部jar文件並引入
classpath 'com.example.customplugin:myplugin:1.0'
}
}
複製代碼
這裏使用了Project的buildscript方法,經過該方法能夠爲當前項目的build.gradle腳本導入外部依賴,在該方法中可使用repositories方法和dependencies方法指定外部依賴的所在倉庫和類路徑,當前項目在構建時會去repositories定義的倉庫地址尋找classpath指定路徑下的全部jar文件並引入,如我這裏repositories方法中指定了mavenLocal()和maven {url uri('../repo')},其中mavenLocal()對應maven的本地倉庫即/.m/repository,maven {url uri('../repo')}就是剛剛發佈時指定的maven遠程倉庫,若是你經過發佈任務發佈插件到了maven中央倉庫,則能夠新增mavenCentral(),它表明maven中央倉庫的遠端地址,而dependencies方法中則經過classpath指定了myplugin插件在倉庫下的類路徑即GAV座標: com.example.customplugin:myplugin:1.0,如今咱們能夠經過apply plugin使用myplugin了,在subproject_4/build.gradle中添加以下:
//subproject_4/build.gradle
//經過插件id引用插件
apply plugin: 'myplugin'
//使用DSL配置插件的屬性
outerExt{
message 'hello'
inner{
message 'word'
}
}
//執行gradle showExt,輸出:
//outerExt = [message = hello], innerExt = [message = word]
複製代碼
其中apply plugin後面的插件id就是咱們在gradle_plugin/src/main/resources/META-INF/gradle-plugins目錄下編寫的properties文件的名稱。
如今已經成功發佈了一個插件並引入使用,可是上面的發佈過程和引入插件過程難免有點繁瑣,Gradle爲咱們提供了java-gradle-plugin用於簡化咱們的發佈過程,同時根據java-gradle-plugin發佈的插件使用時只須要直接引入插件id就行,而無需在buildscript中指定classpath。
仍是上面MyPlugin的例子,咱們在gradle_plugin/build.gradle中引入java-gradle-plugin插件,經過java-gradle-plugin提供的gradlePlugin{}擴展配置插件的META-INF信息,同時經過Project的setGroup方法和setVersion方法定義插件發佈的groupId和version,咱們還要引入maven-publish插件,由於要依賴它提供的發佈能力,以下:
//gradle_plugin/build.gradle
plugins{
//用來生成插件元數據(META-INF)和插件標記工件(Plugin Marker Artifact)
id 'java-gradle-plugin'
//用來生成發佈插件任務
id 'maven-publish'
}
//經過maven-publish提供的publishing.repositories{}定義發佈的maven倉庫位置
publishing.repositories{
maven {
url '../repo'
}
}
//插件發佈的groupId
group = 'com.example.customplugin'
//這裏沒有定義artifactId,maven-publish會自動取當前項目的名字做爲artifactId,這裏爲gradle_plugin
//插件發佈的version
version = '2.0'
//經過java-gradle-plugin提供的gradlePlugin{}配置插件
gradlePlugin {
plugins {
//配置myplugin插件
myplugin{
//插件id
id = 'com.example.customplugin.myplugin'
//插件實現類
implementationClass = 'com.example.plugin.MyPlugin'
}
//myplugin2{
// id = 'xxx'
// implementationClass = 'xxx'
//}
//以此類推能夠配置多個插件
}
}
複製代碼
經過在gradlePlugin{}中的配置,執行發佈任務時java-gradle-plugin會自動爲咱們生成插件的META-INF信息並打包進插件的jar中,同時java-gradle-plugin會自動爲咱們在dependencies引入gradleApi(),除此以外,執行發佈任務時java-gradle-plugin還爲插件生成了一個插件標記工件 - Plugin Marker Artifact,它的做用就是用來定位插件的位置,咱們在plugins DSL中經過插件id來引用插件時並不須要定義插件的classpath即GAV座標就能夠直接使用插件,那麼Gradle是如何根據插件id定位到插件的呢?答案就是插件標記工件,它跟插件發佈時一塊兒發佈,它裏面只有一個pom文件,pom文件裏面依賴了插件真正的GAV座標,這樣經過插件id來引用插件時就會先下載這個pom文件,從而解析到插件的GAV座標,再根據插件的GAV座標把插件的jar文件引入,那麼Gradle又是如何定位到插件標記工件的呢?答案就是使用插件id按必定的規則生成插件標記工件的GAV座標,插件標記工件的GAV生成規則爲pluginId:pluginId.gradle.plugin:pluginVersion,如這裏myplugin的插件標記工件生成的GAV爲com.example.customplugin.myplugin:com.example.customplugin.myplugin.gradle.plugin:2.0,發佈插件時會同時把插件標記工件發佈到生成的GAV處,而後經過插件id引用時又根據插件id拼接出相同的GAV從而定位到插件標記工件,這麼說有點繞,讓咱們執行發佈任務看看生成的產物就懂了。
咱們在Gradle項目所處目錄的命令行輸入gradle publishPluginMavenPublicationToMavenRepository publishMypluginPluginMarkerMavenPublicationToMavenRepository
來執行插件發佈任務和插件標記工件發佈任務,這兩個任務都是maven-publish根據java-gradle-plugin定義的發佈過程爲咱們生成好的,除此以外還生成了發佈到maven本地倉庫的任務,這裏只以發佈到maven遠程倉庫任務作示例,兩個任務執行成功後,在GradleDemo/repo/中會看到發佈的插件和插件標識工件,以下:
能夠看到在repo下發布了兩個組件,一個爲插件,artifactId爲gradle_plugin,一個爲插件標識工件,artifactId爲com.example.customplugin.myplugin.gradle.plugin,它們都處於com.example.customplugin空間下,版本都爲2.0,咱們看一下插件標識工件裏面的內容,以下:
能夠看到插件標識工件中並無jar文件只有pom文件,咱們打開它的pom文件,以下:
能夠看到依賴了真正的插件的GAV座標,爲com.example.customplugin:gradle_plugin:2.0,這樣經過插件id引用時就能夠根據插件標識工件定位到插件,經過java-gradle-plugin這種方式發佈插件時還有一點要注意的是,插件的id最好以groupId爲前綴,這樣才能保證插件標識工件和插件發佈時都處於同一個倉庫路徑。
如今MyPlugin 2.0已經發布成功了,接下來讓咱們使用這個插件,須要經過plugins DSL引用的插件不須要定義classpath,但仍是要定義倉庫位置,咱們在settings.gradle中添加以下:
//settings.gradle
//必定要放在settings.gradle中的第一行
pluginManagement{
repositories{
//添加發布時指定的maven遠程倉庫
maven {
url uri('repo')
}
}
}
複製代碼
plugins DSL經過pluginManagement{} 管理插件倉庫還有插件,並且pluginManagement{}必需要放在settings.gradle腳本的頭部,而後咱們能夠經過plugins DSL使用myplugin了,在subproject_4/build.gradle中添加以下:
//subproject_4/build.gradle
//經過插件id引用插件
plugins{
id 'com.example.customplugin.myplugin' version '2.0'
}
//使用DSL配置插件的屬性
outerExt{
message 'hello'
inner{
message 'word'
}
}
//執行gradle showExt,輸出:
//outerExt = [message = hello], innerExt = [message = word]
複製代碼
plugins DSL根據插件id尋找插件時默認會先從Gradle中央倉庫 - Gradle Plugin Portal找,找不到再從pluginManagement中指定的倉庫找。
上面發佈時爲了講解原理把發佈任務逐個執行,其實maven-publish還爲咱們生成了更爲方便的publishAllPublicationsToMavenRepository 、publishToMavenLocal、publish發佈任務,只須要執行一個任務就能夠把多個發佈任務同時執行:
執行publishAllPublicationsToMavenRepository任務表示執行全部發布到maven遠程倉庫的任務;
執行publishToMavenLocal任務表示執行全部發布到maven本地倉庫的任務;
執行publish任務表示執行publishAllPublicationsToMavenRepository和publishToMavenLocal任務
本文經過Gradle的特色、項目結構、生命週期、Task、自定義Plugin等來全面的學習Gradle,掌握上面的知識已經足夠讓你入門Gradle,可是若是你想要更進一步的學習Gradle,掌握本文的知識點是徹底不足的,你可能還須要熟悉掌握Project中各個api的使用、能獨立的自定義一個有完整功能的Plugin、能熟練地編寫Gradle腳原本管理項目等,下面有一份Gradle DSL Reference,你能夠在須要來查找Gradle相關配置項的含義和用法:
對於android開發者,咱們引入的android插件中也有不少配置項,下面的Android Plugin DSL Reference,你能夠在須要來查找android 插件相關配置項的含義和用法:
本文的源碼位置:
以上就是本文的所有內容,但願你們有所收穫!
參考內容: