探討Gradle插件的自定義有助更好的理解Gradle厚棵,本文綜合了userguide中的幾篇相關(guān)文檔吁伺,將不單獨(dú)給出鏈接(除非很有必要),主要有:
- Writing Custom Plugins (總體結(jié)構(gòu)按這篇文檔)
- Writing Gradle Plugins
- Organizing Gradle Projects
Gradle插件中創(chuàng)建可重用構(gòu)建邏輯的過(guò)程,然后可以將其應(yīng)用于其他Gradle構(gòu)建。Gradle的核心提供了構(gòu)建任何東西的基礎(chǔ)架構(gòu)界斜,但插件允許構(gòu)建腳本作者以最少的努力完成工作,使他們能夠?qū)W⒂跇?gòu)建的內(nèi)容合冀,而不是如何構(gòu)建各薇。插件可以應(yīng)用約定,添加新任務(wù)類型君躺,與第三方工具和庫(kù)集成等峭判。例如,java插件提供標(biāo)準(zhǔn)的源目錄布局和任務(wù)晰洒,例如compileJava和compileTestJava朝抖。
Gradle插件打包可重用的構(gòu)建邏輯片段,可用于許多不同的項(xiàng)目和構(gòu)建谍珊。Gradle允許自定義插件治宣,可以重用構(gòu)建邏輯,并與其他人分享砌滞∥暄可以使用任何語(yǔ)言實(shí)現(xiàn)Gradle插件,前提是實(shí)現(xiàn)最終能編譯為字節(jié)碼贝润。示例中绊茧,我們將使用Groovy作為實(shí)現(xiàn)語(yǔ)言。Groovy打掘,Java或Kotlin都是用于實(shí)現(xiàn)插件的語(yǔ)言的不錯(cuò)選擇华畏,因?yàn)镚radle API已經(jīng)設(shè)計(jì)為可以很好地使用這些語(yǔ)言。通常尊蚁,使用Java或Kotlin實(shí)現(xiàn)的靜態(tài)類型的插件將比使用Groovy實(shí)現(xiàn)的相同插件執(zhí)行得更好亡笑。
打包插件
可以放置Gradle插件代碼的幾個(gè)地方:
構(gòu)建腳本
直接在build.gradle中包含代碼, 自動(dòng)編譯. 對(duì)外不可見,不可重用.buildSrc
工程
插件代碼放在rootProjectDir/buildSrc/src/main/groovy
目錄. Gradle 編譯、測(cè)試插件,并添加到在build.gradle的classpath中. 對(duì)每個(gè)構(gòu)建腳本可見. 對(duì)外不可見,不可重用.獨(dú)立工程
獨(dú)立的插件程序, 生成發(fā)布一個(gè)可供多個(gè)構(gòu)建共享的JAR. 一般 JAR可以包含一些插件或任務(wù).
簡(jiǎn)單插件
要?jiǎng)?chuàng)建Gradle插件横朋,需要編寫一個(gè)實(shí)現(xiàn)Plugin
接口的類仑乌。當(dāng)插件應(yīng)用于項(xiàng)目時(shí),Gradle會(huì)創(chuàng)建插件類的實(shí)例并調(diào)用實(shí)例的Plugin.apply()
方法琴锭。項(xiàng)目對(duì)象作為參數(shù)傳遞晰甚,插件可以使用它來(lái)自由配置項(xiàng)目。以下示例包含greeting插件决帖,該插件向項(xiàng)目添加hello任務(wù)厕九。
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
需要注意的一點(diǎn)是,應(yīng)用它的每個(gè)項(xiàng)目都會(huì)創(chuàng)建一個(gè)新的插件實(shí)例地回。Plugin是泛型類型扁远,示例使其接收Project類型作為類型參數(shù)腺阳。插件可以改為接收類型為應(yīng)用于設(shè)置腳本中的Settings或初始化腳本中的Gradle類型的參數(shù)。
插件可配置
大多數(shù)插件需要從構(gòu)建腳本中獲取一些配置穿香,一種作法是使用擴(kuò)展對(duì)象。Gradle的Project關(guān)聯(lián)一個(gè)包含已應(yīng)用于項(xiàng)目的插件的所有設(shè)置和屬性ExtensionContainer對(duì)象绎速,通過(guò)向該容器添加擴(kuò)展對(duì)象來(lái)為插件提供配置皮获。擴(kuò)展對(duì)象只是一個(gè)Java Bean兼容類。Groovy是實(shí)現(xiàn)擴(kuò)展對(duì)象的很好選擇纹冤,因?yàn)槠胀ǖ呐fGroovy對(duì)象包含Java Bean所需的所有g(shù)etter和setter方法洒宝,Java和Kotlin也是不錯(cuò)的選擇。讓我們?yōu)轫?xiàng)目添加一個(gè)簡(jiǎn)單的擴(kuò)展對(duì)象萌京。這里添加一個(gè)可以配置問(wèn)候語(yǔ)的 greeting
擴(kuò)展對(duì)象雁歌。
build.gradle
class GreetingPluginExtension {
String message = 'Hello from GreetingPlugin'
}
class GreetingPlugin implements Plugin<Project> {
void apply(Project project) {
// 添加 'greeting' 擴(kuò)展對(duì)象
def extension = project.extensions.create('greeting', GreetingPluginExtension)
// 添加一個(gè)使用擴(kuò)展對(duì)象配置的task
project.task('hello') {
doLast {
println extension.message
}
}
}
}
apply plugin: GreetingPlugin
// 配置擴(kuò)展
greeting.message = 'Hi from Gradle'
gradle -q hello輸出
> gradle -q hello
Hi from Gradle
示例中,GreetingPluginExtension是一個(gè)舊的Groovy對(duì)象知残,有一個(gè)message的屬性靠瞎。擴(kuò)展對(duì)象被添加到greeting的插件列表中,而后該對(duì)象成為項(xiàng)目的一個(gè)與擴(kuò)展對(duì)象同名的有效屬性求妹。以上面為例乏盐,Project中添加一個(gè)greeting擴(kuò)展就相當(dāng)于給Project配置了一個(gè)greeting屬性。通常需要在單個(gè)插件上指定幾個(gè)相關(guān)屬性制恍。Gradle為每個(gè)擴(kuò)展對(duì)象添加了一個(gè)配置閉包塊(Closure block)父能,因此您可以將設(shè)置組合在一起。以下示例顯示了它的工作原理净神。
build.gradle
class GreetingPluginExtension {
String message
String greeter
}
...
println "${extension.message} from ${extension.greeter}"
...
apply plugin: GreetingPlugin
// 使用DSL塊配置擴(kuò)展
greeting {
message = 'Hi'
greeter = 'Gradle'
}
gradle -q hello輸出
> gradle -q hello
Hi from Gradle
這里在greeting
閉包內(nèi)將多個(gè)設(shè)置組合在一起何吝。構(gòu)建腳本中閉包塊名稱(greeting
)需要與擴(kuò)展對(duì)象名稱匹配。當(dāng)閉包執(zhí)行時(shí)鹃唯,擴(kuò)展對(duì)象上的字段將根據(jù)標(biāo)準(zhǔn)Groovy閉包委托功能映射到閉包內(nèi)的變量爱榕。
buildSrc工程
復(fù)雜的構(gòu)建邏輯通常封裝為自定義任務(wù)或二進(jìn)制插件。自定義任務(wù)和插件實(shí)現(xiàn)不應(yīng)該存在于構(gòu)建腳本中俯渤。只要代碼不需要在多個(gè)獨(dú)立項(xiàng)目之間共享呆细,就可以非常方便地使用buildSrc實(shí)現(xiàn)。目錄buildSrc被視為已包含的構(gòu)建八匠。一旦發(fā)現(xiàn)該目錄絮爷,Gradle會(huì)自動(dòng)編譯并測(cè)試代碼并將其放入構(gòu)建腳本的classpath中。對(duì)于多模塊構(gòu)建梨树,只能有一個(gè)buildSrc目錄坑夯,且必須位于根項(xiàng)目目錄下。buildSrc應(yīng)該優(yōu)先于腳本插件抡四,因?yàn)樗菀拙S護(hù)柜蜈,重構(gòu)和測(cè)試代碼仗谆。buildSrc適用于Java和Groovy項(xiàng)目的相同源代碼約定,還提供對(duì)Gradle API的直接訪問(wèn)淑履×タ澹可以在buildSrc下的build.gradle中聲明其他依賴項(xiàng)。
$ 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 #刪除無(wú)用文件
添加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
后執(zhí)行
$ ./gradlew hello
Hello, World!
我這里執(zhí)行的時(shí)候拋出了一個(gè)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工程能被構(gòu)建任務(wù)自動(dòng)識(shí)別秘噪, 因此build.gradle中的內(nèi)容完全可以刪除,只保留額外的依賴和配置, 這樣也不會(huì)有上面版本沖突的問(wèn)題
分配插件id
大多數(shù)情況使用id以其容易記憶會(huì)比使用全類名來(lái)的方便狸吞,還會(huì)使構(gòu)建文件更整潔。下面聲明插件的id指煎。
創(chuàng)建buildSrc/src/main/resources/META-INF/gradle-plugins/org.example.greeting.properties
implementation-class=org.example.greeting.GreetingPlugin
Gradle使用這個(gè)文件來(lái)確定哪個(gè)類實(shí)現(xiàn)了Plugin接口蹋偏。文件名去掉.properties
后綴就是插件的id。根工程build.gradle改成
apply plugin: 'org.example.greeting'
或
plugins {
id 'org.example.greeting'
}
注意上面是如何將org.example.greeting.properties
文件映射到插件id的至壤。
獨(dú)立工程
現(xiàn)在將我們的插件移到一個(gè)獨(dú)立的項(xiàng)目中威始,可以發(fā)布它并與其他人分享。這是一個(gè)Groovy項(xiàng)目像街,它生成一個(gè)包含插件類的JAR黎棠。這是項(xiàng)目的簡(jiǎn)單構(gòu)建腳本。它應(yīng)用Groovy插件镰绎,并將Gradle API添加為編譯時(shí)依賴項(xiàng)葫掉。
$ mv buildSrc plugin
此時(shí)plugin
模塊不再享有buildSrc
時(shí)也有的自動(dòng)被構(gòu)建腳本識(shí)別、自動(dòng)添加gradle/groovy等依賴跟狱、自動(dòng)添加到classpath中這些“特權(quán)”俭厚, 因此這些工作需要插件作者來(lái)做。編輯plugin/build.gradle:
-
識(shí)別工程
頂級(jí)settings.gradle加入
include ':plugin'
-
添加依賴
plugins { id 'groovy' } dependencies { compile gradleApi() compile localGroovy() }
-
分配一個(gè)id
如果之前buildSrc中沒有分配id這里的作法和buildSrc分配插件id是一樣的, 這里重新進(jìn)行配置驶臊。
創(chuàng)建 plugin/src/main/resources/META-INF/gradle-plugins/org.samples.greeting.propertiesimplementation-class=org.example.greeting.GreetingPlugin
到這里還不能使用插件挪挤。頂級(jí)
build.gradle
要到哪里去加載插件jar包呢? 對(duì)了classpath沒有配置,還要繼續(xù)配置关翎。 -
發(fā)布插件
發(fā)布插件的方式常見的有maven扛门、ivy和本地這三種倉(cāng)庫(kù), 前兩個(gè)屬于中央倉(cāng)庫(kù)纵寝,提交產(chǎn)品需要審核论寨,這里采用便捷的本地maven倉(cāng)庫(kù)。編輯plugin/build.gradle加入plugins { id 'maven' } uploadArchives{ repositories.mavenDeployer{ repository(url:uri('../repo')) //保存到上一級(jí)目錄的repo下 pom.project{ groupId 'org.gradle' artifactId 'customPlugin' version '1.0' } } }
-
注釋掉頂級(jí)build.gradle下應(yīng)用的插件, 根目錄執(zhí)行
./gradlew uploadArchives
執(zhí)行完畢后工程頂級(jí)目錄下有個(gè)
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
-
添加插件成品到頂級(jí)build.gradle的classpath中
buildscript { repositories { maven { url uri('repo') } } dependencies{ classpath 'org.gradle:customPlugin:1.0' } }
-
新建一個(gè)模塊來(lái)應(yīng)用插件
$ mkdir test-plugin
頂級(jí)build.gradle加入一行
include ':test-plugin'
test-plugin/build.gradle加入:apply plugin:'org.samples.greeting'
執(zhí)行:./gradlew hello
$ ./gradlew hello Hello, World!
?