探討Gradle插件的自定義有助更好的理解Gradle,本文綜合了userguide中的幾篇相關文檔,將不單獨給出鏈接(除非很有必要),主要有:
Gradle插件中創建可重用構建邏輯的過程,然後可以將其應用於其他Gradle構建。Gradle的核心提供了構建任何東西的基礎架構,但插件允許構建腳本作者以最少的努力完成工作,使他們能夠專注於構建的內容,而不是如何構建。插件可以應用約定,添加新任務類型,與第三方工具和庫集成等。例如,java插件提供標準的源目錄佈局和任務,例如compileJava和compileTestJava。
Gradle插件打包可重用的構建邏輯片段,可用於許多不同的項目和構建。Gradle允許自定義插件,可以重用構建邏輯,並與其他人分享。可以使用任何語言實現Gradle插件,前提是實現最終能編譯爲字節碼。示例中,我們將使用Groovy作爲實現語言。Groovy,Java或Kotlin都是用於實現插件的語言的不錯選擇,因爲Gradle API已經設計爲可以很好地使用這些語言。通常,使用Java或Kotlin實現的靜態類型的插件將比使用Groovy實現的相同插件執行得更好。
打包插件
可以放置Gradle插件代碼的幾個地方:
構建腳本
直接在build.gradle中包含代碼, 自動編譯. 對外不可見,不可重用.buildSrc
工程
插件代碼放在rootProjectDir/buildSrc/src/main/groovy
目錄. Gradle 編譯、測試插件,並添加到在build.gradle的classpath中. 對每個構建腳本可見. 對外不可見,不可重用.獨立工程
獨立的插件程序, 生成發佈一個可供多個構建共享的JAR. 一般 JAR可以包含一些插件或任務.
簡單插件
要創建Gradle插件,需要編寫一個實現Plugin
接口的類。當插件應用於項目時,Gradle會創建插件類的實例並調用實例的Plugin.apply()
方法。項目對象作爲參數傳遞,插件可以使用它來自由配置項目。以下示例包含greeting插件,該插件向項目添加hello任務。
build.gradle
class GreetingPlugin implements Plugin<Project> {
void apply(Project project) {
project.task('hello') {
doLast {
println 'Hello from the GreetingPlugin'
}
}
}
}
// Apply the plugin
apply plugin: GreetingPlugin
gradle -q hello輸出:
> gradle -q hello
Hello from the GreetingPlugin
需要注意的一點是,應用它的每個項目都會創建一個新的插件實例。Plugin是泛型類型,示例使其接收Project類型作爲類型參數。插件可以改爲接收類型爲應用於設置腳本中的Settings或初始化腳本中的Gradle類型的參數。
插件可配置
大多數插件需要從構建腳本中獲取一些配置,一種作法是使用擴展對象。Gradle的Project關聯一個包含已應用於項目的插件的所有設置和屬性ExtensionContainer對象,通過向該容器添加擴展對象來爲插件提供配置。擴展對象只是一個Java Bean兼容類。Groovy是實現擴展對象的很好選擇,因爲普通的舊Groovy對象包含Java Bean所需的所有getter和setter方法,Java和Kotlin也是不錯的選擇。讓我們爲項目添加一個簡單的擴展對象。這裏添加一個可以配置問候語的 greeting
擴展對象。
build.gradle
class GreetingPluginExtension {
String message = 'Hello from GreetingPlugin'
}
class GreetingPlugin implements Plugin<Project> {
void apply(Project project) {
// 添加 'greeting' 擴展對象
def extension = project.extensions.create('greeting', GreetingPluginExtension)
// 添加一個使用擴展對象配置的task
project.task('hello') {
doLast {
println extension.message
}
}
}
}
apply plugin: GreetingPlugin
// 配置擴展
greeting.message = 'Hi from Gradle'
gradle -q hello輸出
> gradle -q hello
Hi from Gradle
示例中,GreetingPluginExtension是一箇舊的Groovy對象,有一個message的屬性。擴展對象被添加到greeting的插件列表中,而後該對象成爲項目的一個與擴展對象同名的有效屬性。以上面爲例,Project中添加一個greeting擴展就相當於給Project配置了一個greeting屬性。通常需要在單個插件上指定幾個相關屬性。Gradle爲每個擴展對象添加了一個配置閉包塊(Closure block),因此您可以將設置組合在一起。以下示例顯示了它的工作原理。
build.gradle
class GreetingPluginExtension {
String message
String greeter
}
...
println "${extension.message} from ${extension.greeter}"
...
apply plugin: GreetingPlugin
// 使用DSL塊配置擴展
greeting {
message = 'Hi'
greeter = 'Gradle'
}
gradle -q hello輸出
> gradle -q hello
Hi from Gradle
這裏在greeting
閉包內將多個設置組合在一起。構建腳本中閉包塊名稱(greeting
)需要與擴展對象名稱匹配。當閉包執行時,擴展對象上的字段將根據標準Groovy閉包委託功能映射到閉包內的變量。
buildSrc工程
複雜的構建邏輯通常封裝爲自定義任務或二進制插件。自定義任務和插件實現不應該存在於構建腳本中。只要代碼不需要在多個獨立項目之間共享,就可以非常方便地使用buildSrc實現。目錄buildSrc被視爲已包含的構建。一旦發現該目錄,Gradle會自動編譯並測試代碼並將其放入構建腳本的classpath中。對於多模塊構建,只能有一個buildSrc目錄,且必須位於根項目目錄下。buildSrc應該優先於腳本插件,因爲它更容易維護,重構和測試代碼。buildSrc適用於Java和Groovy項目的相同源代碼約定,還提供對Gradle API的直接訪問。可以在buildSrc下的build.gradle中聲明其他依賴項。
$ mkdir gradle-plugin-buildsrc
$ cd gradle-plugin-buildsrc/
$ gradle wrapper --gradle-version 4.6 --distribution-type all #gradle-4.6-all初始化Wrapper
$ ./gradlew init #初始化根工程
$ mkdir buildSrc
$ cd buildSrc/
$ ../gradlew init --type groovy-library #buildSrc初始化爲groovy-library
$ rm -r gradle* sett* src/*/*/*.groovy #刪除無用文件
添加buildSrc/src/main/groovy/org/example/greeting/GreetingPlugin.groovy
package org.example.greeting
import org.gradle.api.Plugin
import org.gradle.api.Project
class GreetingPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
project.tasks.create("hello", Greeting.class) { task ->
task.setMessage("Hello")
task.setRecipient("World")
}
}
}
添加buildSrc/src/main/groovy/org/example/greeting/Greeting.groovy
package org.example.greeting
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction
class Greeting extends DefaultTask {
private String message
private String recipient
String getMessage() { return message }
void setMessage(String message) { this.message = message }
String getRecipient() { return recipient }
void setRecipient(String recipient) { this.recipient = recipient }
@TaskAction
void sayGreeting() {
println "$message, $recipient!"
}
}
根工程build.gradle加入
apply plugin: org.example.greeting.GreetingPlugin
後執行
$ ./gradlew hello
Hello, World!
我這裏執行的時候拋出了一個buildSrc下的異常:
Caused by: groovy.lang.GroovyRuntimeException: Conflicting module versions. Module [groovy-all is loaded in version 2.4.12 and you are trying to load version 2.4.13
將
groovy-all
版本改成2.4.12
就可以了另外由於buildSrc工程能被構建任務自動識別, 因此build.gradle中的內容完全可以刪除,只保留額外的依賴和配置, 這樣也不會有上面版本衝突的問題
分配插件id
大多數情況使用id以其容易記憶會比使用全類名來的方便,還會使構建文件更整潔。下面聲明插件的id。
創建buildSrc/src/main/resources/META-INF/gradle-plugins/org.example.greeting.properties
implementation-class=org.example.greeting.GreetingPlugin
Gradle使用這個文件來確定哪個類實現了Plugin接口。文件名去掉.properties
後綴就是插件的id。根工程build.gradle改成
apply plugin: 'org.example.greeting'
或
plugins {
id 'org.example.greeting'
}
注意上面是如何將org.example.greeting.properties
文件映射到插件id的。
獨立工程
現在將我們的插件移到一個獨立的項目中,可以發佈它並與其他人分享。這是一個Groovy項目,它生成一個包含插件類的JAR。這是項目的簡單構建腳本。它應用Groovy插件,並將Gradle API添加爲編譯時依賴項。
$ mv buildSrc plugin
此時plugin
模塊不再享有buildSrc
時也有的自動被構建腳本識別、自動添加gradle/groovy等依賴、自動添加到classpath中這些“特權”, 因此這些工作需要插件作者來做。編輯plugin/build.gradle:
-
識別工程
頂級settings.gradle加入
include ':plugin'
-
添加依賴
plugins { id 'groovy' } dependencies { compile gradleApi() compile localGroovy() }
-
分配一個id
如果之前buildSrc中沒有分配id這裏的作法和buildSrc分配插件id是一樣的, 這裏重新進行配置。
創建 plugin/src/main/resources/META-INF/gradle-plugins/org.samples.greeting.propertiesimplementation-class=org.example.greeting.GreetingPlugin
到這裏還不能使用插件。頂級
build.gradle
要到哪裏去加載插件jar包呢? 對了classpath沒有配置,還要繼續配置。 -
發佈插件
發佈插件的方式常見的有maven、ivy和本地這三種倉庫, 前兩個屬於中央倉庫,提交產品需要審覈,這裏採用便捷的本地maven倉庫。編輯plugin/build.gradle加入plugins { id 'maven' } uploadArchives{ repositories.mavenDeployer{ repository(url:uri('../repo')) //保存到上一級目錄的repo下 pom.project{ groupId 'org.gradle' artifactId 'customPlugin' version '1.0' } } }
-
註釋掉頂級build.gradle下應用的插件, 根目錄執行
./gradlew uploadArchives
執行完畢後工程頂級目錄下有個
repo
目錄:gradle-plugin-buildsrc/ ├── build.gradle ... ├── plugin ├── repo └── settings.gradle repo/ └── org └── gradle └── customPlugin ├── 1.0 │ ├── customPlugin-1.0.jar │ ├── customPlugin-1.0.jar.md5 │ ├── customPlugin-1.0.jar.sha1 │ ├── customPlugin-1.0.pom │ ├── customPlugin-1.0.pom.md5 │ └── customPlugin-1.0.pom.sha1 ├── maven-metadata.xml ├── maven-metadata.xml.md5 └── maven-metadata.xml.sha1
-
添加插件成品到頂級build.gradle的classpath中
buildscript { repositories { maven { url uri('repo') } } dependencies{ classpath 'org.gradle:customPlugin:1.0' } }
-
新建一個模塊來應用插件
$ mkdir test-plugin
頂級build.gradle加入一行
include ':test-plugin'
test-plugin/build.gradle加入:apply plugin:'org.samples.greeting'
執行:./gradlew hello
$ ./gradlew hello Hello, World!