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

探討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:

  1. 識別工程

    頂級settings.gradle加入include ':plugin'

  2. 添加依賴

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

    如果之前buildSrc中沒有分配id這裏的作法和buildSrc分配插件id是一樣的, 這裏重新進行配置。
    創建 plugin/src/main/resources/META-INF/gradle-plugins/org.samples.greeting.properties

    implementation-class=org.example.greeting.GreetingPlugin
    

    到這裏還不能使用插件。頂級build.gradle要到哪裏去加載插件jar包呢? 對了classpath沒有配置,還要繼續配置。

  4. 發佈插件
    發佈插件的方式常見的有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'
            }
        }
    }
    
  5. 註釋掉頂級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
    
  6. 添加插件成品到頂級build.gradle的classpath中

    buildscript {
        repositories {
            maven {
                url uri('repo')
            }
        }
        
        dependencies{
            classpath 'org.gradle:customPlugin:1.0'
        }
    }
    
  7. 新建一個模塊來應用插件

    $ mkdir test-plugin
    

    頂級build.gradle加入一行include ':test-plugin'
    test-plugin/build.gradle加入:apply plugin:'org.samples.greeting'
    執行:./gradlew hello

    $ ./gradlew hello
    Hello, World!
    

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章