Gradle系列5--自定義Gradle插件

探討Gradle插件的自定義有助更好的理解Gradle厚棵,本文綜合了userguide中的幾篇相關(guān)文檔吁伺,將不單獨(dú)給出鏈接(除非很有必要),主要有:

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:

  1. 識(shí)別工程

    頂級(jí)settings.gradle加入include ':plugin'

  2. 添加依賴

    plugins {
        id 'groovy'
    }
    
    dependencies {
        compile gradleApi()
        compile localGroovy()
    }
    
  3. 分配一個(gè)id

    如果之前buildSrc中沒有分配id這里的作法和buildSrc分配插件id是一樣的, 這里重新進(jìn)行配置驶臊。
    創(chuàng)建 plugin/src/main/resources/META-INF/gradle-plugins/org.samples.greeting.properties

    implementation-class=org.example.greeting.GreetingPlugin
    

    到這里還不能使用插件挪挤。頂級(jí)build.gradle要到哪里去加載插件jar包呢? 對(duì)了classpath沒有配置,還要繼續(xù)配置关翎。

  4. 發(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'
            }
        }
    }
    
  5. 注釋掉頂級(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
    
  6. 添加插件成品到頂級(jí)build.gradle的classpath中

    buildscript {
        repositories {
            maven {
                url uri('repo')
            }
        }
        
        dependencies{
            classpath 'org.gradle:customPlugin:1.0'
        }
    }
    
  7. 新建一個(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!
    

    ?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末爽茴,一起剝皮案震驚了整個(gè)濱河市葬凳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌室奏,老刑警劉巖火焰,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異胧沫,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門称近,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人谦疾,你說(shuō)我怎么就攤上這事∪穑” “怎么了餐蔬?”我有些...
    開封第一講書人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)佑附。 經(jīng)常有香客問(wèn)我,道長(zhǎng)仗考,這世上最難降的妖魔是什么音同? 我笑而不...
    開封第一講書人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮秃嗜,結(jié)果婚禮上权均,老公的妹妹穿的比我還像新娘。我一直安慰自己锅锨,他們只是感情好叽赊,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著必搞,像睡著了一般必指。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上恕洲,一...
    開封第一講書人閱讀 49,772評(píng)論 1 290
  • 那天塔橡,我揣著相機(jī)與錄音,去河邊找鬼霜第。 笑死葛家,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的泌类。 我是一名探鬼主播癞谒,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼刃榨!你這毒婦竟也來(lái)了弹砚?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤枢希,失蹤者是張志新(化名)和其女友劉穎迅栅,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體晴玖,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡读存,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年为流,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片让簿。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡敬察,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出尔当,到底是詐尸還是另有隱情莲祸,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布椭迎,位于F島的核電站锐帜,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏畜号。R本人自食惡果不足惜缴阎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望简软。 院中可真熱鬧蛮拔,春花似錦、人聲如沸痹升。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)疼蛾。三九已至肛跌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間察郁,已是汗流浹背惋砂。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留绳锅,地道東北人西饵。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像鳞芙,于是被迫代替她去往敵國(guó)和親眷柔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容