Gradle 自定義插件

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=插件被用來封裝構建邏輯和一些通用配置。將可重複使用的構建邏輯和默認約定封裝到插件裏,以便於其餘項目使用。 你可使用你喜歡的語言開發插件,可是最終是要編譯成字節碼在 JVM 運行的。 Gradle 有兩種插件,腳本插件和二進制插件。java

思惟導圖

使用版本 5.6.2git

插件被用來封裝構建邏輯和一些通用配置。將可重複使用的構建邏輯和默認約定封裝到插件裏,以便於其餘項目使用。github

你可使用你喜歡的語言開發插件,可是最終是要編譯成字節碼在 JVM 運行的。api

Gradle 有兩種插件,腳本插件和二進制插件。緩存

關於插件的介紹,能夠參考個人另外一篇文章 Gradle 插件閉包

這裏講的自定義插件是二進制插件,二進制插件能夠打包發佈,有利於分享。app

能夠在三個地方定義插件

  • 在腳本里
  • 在 buildSrc 下
  • 在單獨的項目裏

三個地方的插件的用途目的不一樣。maven

在腳本里的插件

其餘項目沒法使用,只能在本腳本里使用。ide

在 buildSrc 下

在項目的 buildSrc 目錄下的插件,這個項目裏的全部(子)項目均可以使用。函數

在單獨的項目裏

你能夠爲你的插件建立一個項目,這個項目能夠打包發佈 JAR,提供給其餘任何項目使用。

建立一個插件

建議使用靜態語言,例如 Java ,Kotlin,開發工具建議使用 IntelliJ IDEA 。

一個插件就是個實現了 Plugin

當插件被應用到項目時,Gradle 會實例化這個插件並調用 Plugin.apply() 方法,並將這個項目的實例當作參數傳遞進去。插件就能夠對這個項目進行各類配置了。

CustomPLugin.java

// 定義一個插件
class CustomPLugin implements Plugin{

    @Override
    void apply(Project target) {
        // do something
    }
}

前面說到能夠在三個地方建立插件,如今來一一實現下。

在腳本里建立一個插件

能夠在 build.gradle 腳本里任意地方定義。

build.gradle

// 定義一個插件
class CustomPLugin implements Plugin{

    @Override
    void apply(Project target) {
      //添加一個任務
     target.task('hello', group: 'util') {
         doLast {
             logger.quiet("Hello Plugin.")
         }
     }
    }
}

//直接在腳本里應用
apply plugin:CustomPLugin

在 gradle 窗口就能夠看到應用插件後的添加的任務
添加的任務

雙擊任務或者命令行輸入均可以執行 hello 任務

gradle hello

在項目的 buildSrc 目錄下建立項目

這裏使用的是 Groovy 。

在這個目錄下建立項目會被 Gradle 自動識別的。

結構以下

buildSrc 目錄結構

  1. 在項目根目錄下建立目錄 buildSrc
  2. 在 buildSrc 下按照 java 工程或者 groovy 工程(這取決於你用什麼語言)新建目錄

$projectDir/buildSrc/src/main/groovy

  1. 在 groovy 建立你的包 (可能如今還不能被識別爲項目,那就建立目錄),例如 com.github.skymxc
  2. 在包裏建立插件,也就是建立一個實現了 Plugin 的類。

這裏作簡單的示範:

在插件裏爲 jar 任務添加一個操做:生成記錄文件

JarLogPlugin.groovy

/**
 * 輸出 生成記錄到指定文件
 */
class JarLogPlugin implements Plugin{
    @Override
    void apply(Project target) {
        //增長一個擴展配置用來接收參數
        target.extensions.create("log", LogExtension)

        //添加一個任務
        target.task(type: Jar,group:'util','jarWithLog',{
            doLast {
                //使用配置
                def file = target.log.outputPath;
                if (file==null){
                    file = new File(target.projectDir,"/log/jarlog.txt").getPath()
                }
                println "存儲目錄是 ${file}"
                def content = "${getArchiveFileName().get()}---${getNow()}\n"
                writeFile(file,content)
            }
        })

        //爲 jar 任務添加一個 操做,
        target.tasks.jar.doLast {
            println "當前時間是 ${getNow()},打了一個 jar-> ${version}"
            //存到指定文件記錄
            def file = new File(target.projectDir,"/log/jarlog.txt");
            def content = "${version}---${getNow()}\n"
            writeFile(file.getAbsolutePath(),content)
        }
    }

    def String getNow(){
        def dateFormat = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss.SSS");
        return dateFormat.format(Calendar.getInstance().getTime());
    }

    def void writeFile(String path,String content){
        def file = new File(path);
        if (!file.exists()){
            if (!file.getParentFile().exists()){
                file.getParentFile().mkdirs();
            }
            file.createNewFile();
        }
        FileWriter writer = new FileWriter(file.getAbsolutePath(),true);
        BufferedWriter bufferedWriter = new BufferedWriter(writer);
        bufferedWriter.write(content);
        bufferedWriter.close();
    }
}

配置 DSL

上面使用了一個擴展來接收參數, 普通的對象就能夠,例如

LogExtension.groovy

class LogExtension {
    String outputPath;
}

擴展在這裏就是用來爲插件配置 DSL 用的。

//爲 項目添加了一個 LogExtension 類型的屬性 名字是 log
project.extensions.create("log", LogExtension)

插件可使用 DSL 接收參數,在插件或者任務裏直接經過 Project 實例訪問便可。

def file = project.log.outputPath;

插件建立完成後,在項目的裏就可使用了。

如今可使用類名應用插件了。

build.gradle

import com.github.skymxc.JarLogPlugin

apply plugin: JarLogPlugin

插件應用成功後就可使用 DSL 爲插件配置參數。

配置記錄文件地址:

build.gradle

log {
    outputPath rootProject.projectDir.getPath()+"\\record\\jar.txt"
}

爲插件建立 ID

  1. 在 main 目錄下建立 resources 文件夾
  2. 在 resources 目錄下建立 META-INF 文件夾
  3. 在 META-INF 目錄下建立 gradle-plugins 文件夾
  4. 在 gradle-plugins 目錄下建立 properties 文件,名字就是你的插件 ID。
  5. 在 id.properties 文件裏經過  implementation-class 指向你的實現類。

例如

src / main / resources / META-INF / gradle-plugins / com.github.skymxc.sample.properties

implementation-class= com.github.skymxc.JarLogPlugin

而後就可使用插件 ID 了

plugins {
    id 'com.github.skymxc.sample'
}

關於插件 id 的規範:

  • 能夠包含任何字母數字字符 「 . 」和 「 - 」。
  • 必須至少包含一個 「 . 」 。
  • 通常使用小寫的反向域名。(相似包名)
  • 不能以 「 . 」 結尾。
  • 不能包含連續的 「 . 」 。

關於 Groovy 的語法,能夠參考 Groovy 語法。

在單獨的項目裏建立插件

此次仍然是使用 Groovy 語言。

這裏的插件項目其實就是一個 Groovy 項目,固然了你若是使用 Java 語言就是一個 Java 工程。

建立一個工程

建立出來的項目就是這樣子,標準的 Groovy 工程目錄

建立Groovy工程

更改 build.gradle 腳本,配置項目

  1. 應用 maven-publih 插件
  2. 添加 Gradle 和 Groovy 的依賴
  3. 配置上傳任務

最後就是這樣子

plugins {
    id 'groovy'
    id 'maven-publish'
}

group 'com.github.skymxc'
version '1.0.0'

sourceCompatibility = 1.8

repositories {
    mavenCentral()
}

//使用 groovy 和 gradle 依賴
dependencies {
    compile gradleApi()
    compile localGroovy()
}
publishing {
    repositories {
        maven {
            name 'local'
            url 'file://E:/libs/localMaven'
        }
    }
    publications {
        maven(MavenPublication) {
            groupId = 'com.github.skymxc'
            artifactId = 'plugin'
            version = '1.0.0'
            from components.java
        }
    }

}

建立兩個插件:

一個是上面建立的那個,就不重複粘貼了。

另外一個插件 Greet,配置一個任務,簡單的輸出一句話。

class Greet implements Plugin{
    @Override
    void apply(Project target) {
        target.extensions.create("hello", Hello)
        target.task("hello") {
            doLast {
                println "message -> ${target.hello.message}"
            }
        }
    }
}

Hello.groovy

class Hello {
    String message
}

插件 ID 的配置是跟上面同樣的。

目錄結構圖

執行 maven-publish 的 publish 任務,將插件發佈到指定倉庫。

gradlew -p plugin publish

發佈成功後的倉庫

發佈成功的圖片

插件建立完成了,也發佈了,下面就是使用這個插件了。

這裏對插件的使用就簡單介紹一下,詳細的能夠查看以前的這篇介紹:Gradle 插件

  1. 在根項目的 build.gradle 配置倉庫,添加依賴
buildscript {
    repositories {
        maven {
            url 'file://E:/libs/localMaven'
        }
    }
    dependencies {
        classpath 'com.github.skymxc:plugin:1.0.2'
    }
}
  1. 應用插件

我分別在兩個 Java 項目裏使用了插件:

  • 一個是使用 id 的方式
  • 一個是使用類名的方式

lib_2/ build.gradle 使用 類名的方式

······

apply plugin:'com.github.skymxc.greet'

hello{
    message '使用了 com.github.skymxc.greet 插件'
}

······

lib_1/ build.gradle 使用 id 的方式

plugins {
    id 'java'
    id 'com.github.skymxc.jarlog'
}

······

logConfigure {
    outputPath rootProject.projectDir.getPath()+"\\record\\jar.txt"
}

應用插件後的 gradle 視圖,能夠看到已經添加的任務。

gradle 視圖-任務

使用 java-gradle-plugin 開發插件

像上面同樣建立一個項目,不過此次是一個 java 項目,而後應用這個插件。

java-gradle-plugin 能夠減小重複代碼,它自動的應用 java 插件,添加 gradleApi() 依賴。

plugins {
    id 'java-gradle-plugin'
}

使用 gradlePlugin {} 配置塊能夠配置開發的每個插件,不用手動建立對應的屬性文件了。

gradlePlugin {
    plugins {
        greetPlugin {
            id = 'com.github.skymxc.greet'
            implementationClass = 'com.github.skymxc.GreetPlugin'
        }

        jarWithLogPlugin {
            id = 'com.github.skymxc.jar-log'
            implementationClass = 'com.github.skymxc.JarWithLogPlugin'
        }
    }
}

插件會在 jar 文件裏自動生成對應的 META-INF 目錄。

配合 maven-publish 能夠爲每一個插件建立對應的發佈任務。

在發佈時也會爲每一個插件發佈對應的 「插件標記工件」 。

插件標記工件

關於 插件標記工件這裏插一下:

每一個 maven 工件都是由三部分標識的

  • groupId
  • artifactId
  • version

日常咱們添加依賴的這樣的:

implementation 'groupId:artifactId:version'

而咱們的插件是經過 id 應用的,怎麼經過 id 找到對應的工件呢,這就有了「插件標記工件」。
應用插件時會把 id 映射成這樣:plugin.id: plugin.id.gradle.plugin:plugin.version

即:

  • plugin.id
  • plugin.id.gradle.plugin
  • plugin.version

舉個上面的例子:com.github.skymxc.greet 插件對應的工件就是:

com.github.skymxc.greet:com.github.skymxc.greet.gradle.plugin:1.0.0

部分代碼:

plugins {
    id 'java-gradle-plugin'
    id 'maven-publish'
}

group 'com.github.skymxc'
version '1.0.0'


gradlePlugin {
    plugins {
        greetPlugin {
            id = 'com.github.skymxc.greet'
            implementationClass = 'com.github.skymxc.GreetPlugin'
        }

        jarWithLogPlugin {
            id = 'com.github.skymxc.jar-log'
            implementationClass = 'com.github.skymxc.JarWithLogPlugin'
        }
    }
}

publishing {
    repositories {
        maven {
            name 'local'
            url 'file://E:/libs/localMaven'
        }
    }
}

maven-publish 的任務

簡單介紹一下 maven-publish 的發佈任務

  • generatePomFileFor${PubName}Publication

    爲名字爲 PubName 的的發佈建立一個 POM 文件,填充已知的元數據,例如項目名稱,項目版本和依賴項。POM文件的默認位置是build / publications / $ pubName / pom-default.xml。

  • publish${PubName}PublicationTo${RepoName}Repository

    將 PubName 發佈 發佈到名爲 RepoName 的倉庫。
    若是倉庫定義沒有明確的名稱,則 RepoName 默認爲 「 Maven」。

  • publish${PubName}PublicationToMavenLocal

    將 PubName 發佈以及本地發佈的 POM 文件和其餘元數據複製到本地Maven緩存中
    (一般爲$USER_HOME / .m2 / repository)。

  • publish

    依賴於:全部的 publish${PubName}PublicationTo${RepoName}Repository 任務
    將全部定義的發佈發佈到全部定義的倉庫的聚合任務。不包括複製到本地 Maven 緩存的任務。

  • publishToMavenLocal

    依賴於:全部的 publish${PubName}PublicationToMavenLocal 任務

    將全部定義的發佈(包括它們的元數據(POM文件等))複製到本地Maven緩存。

這張圖列出了爲每一個插件生成的對應的任務。
插件對應的發佈任務

執行發佈任務 publish 後能夠在對應的倉庫查看

發佈後的倉庫圖1
發佈後的倉庫圖2

發佈插件後的使用

  1. 配置倉庫,此次在 settings.gradle 裏配置
pluginManagement {
    repositories {
        maven {
            url 'file://E:/libs/localMaven'
        }
    }
}
  1. 使用插件
plugins {
    id 'java'
    id 'com.github.skymxc.greet' version '1.0.13'
    id 'com.github.skymxc.jar-log' version '1.0.0'
}

應用插件後就能夠在 Gradle 的窗口看到對應的任務了。

而後能夠根據須要配置 DSL 。

爲插件配置 DSL

和插件的交互就是經過 DSL 配置塊進行的。

那怎麼爲插件配置 DSL 呢,答案是隨便一個普通類均可以的。

經過 Gradle 的 API 能夠將一個普通的類添加爲 Project 的擴展,即 Project 的屬性。

舉個例子,插件裏的任務須要兩個參數:文件地址,文件名字,就要經過 DSL 配置的方式解決。

JarLogExtension.java 一個普通的類,有兩個屬性,分別是 name , path

package com.github.skymxc.extension;

public class JarLogExtension {
    private String name;
    private String path;

    //省略 setter/getter
}

在插件裏將這個類添加爲項目的擴展。

public class JarWithLogPlugin implements Plugin{

    @Override
    public void apply(Project target) {
        //添加擴展
        target.getExtensions().add("jarLog", JarLogExtension.class);
        //建立任務
        target.getTasks().create("jarWithLog", JarWithLogTask.class);
    }
}

應用插件後就能夠在腳本里使用這個 DSL 配置了。

build.gradle

······

/**
 * 爲 jarWithLog 配置的 DSL
 */
jarLog {
    path getBuildDir().path+"/libs"
    name "record.txt"
}

······

接下來就是在插件或者任務裏獲取 DSL 配置的參數了。

能夠經過名字或者類型獲取到這個擴展對象。

public class JarWithLogTask extends Jar {

    @TaskAction
    private void writeLog() throws IOException {
      //獲取到配置
        JarLogExtension extension = getProject().getExtensions().getByType(JarLogExtension.class);

        File file = new File(extension.getPath(),extension.getName());
        String s = file.getAbsolutePath();
        String content = getNow()+" --- "+getArchiveFileName().get();
        System.out.println("path --> "+s);
        writeFile(s,content);
    }
}

嵌套 DSL

在咱們平常的使用中,嵌套 DSL 很常見,那怎麼實現的呢。

hello {
    message '使用 pluginManagement 管理插件'
    user {
        name 'mxc'
        age 18
    }
}

如今我來實現下:

首先是建立裏面的嵌套對象,須要注意的是要爲 DSL 配置對應的方法。

UserData.java

package com.github.skymxc.extension;

/**
 * 爲了實踐嵌套 DSL 建的
 */
public class UserData {
    private String name;
    private int age;
    public String getName() {
        return name;
    }

    /**
     * 注意此方法 沒有 set
     * @param name
     */
    public void name(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void age(int age) {
        this.age = age;
    }

}

而後是外層的 DSL 對應的類,由於有 DSL 嵌套,因此要使用閉包

package com.github.skymxc.extension;

import org.gradle.api.Action;

/**
 * 爲 HelloTask 建立的擴展,用於接收配置參數
 */
public class HelloExtension {

    private String message;
    private final UserData user = new UserData();


    /**
     * 注意此方法沒有 set
     * @param action
     */
    public void user(Action action) {
        action.execute(user);
    }

    // 省略其餘 getter/setter
}

最後就是添加到項目的擴展了,和前面同樣

public class GreetPlugin implements Plugin{
    @Override
    public void apply(Project target) {
        target.getExtensions().create("hello", HelloExtension.class);
        target.getTasks().create("hello", HelloTask.class);
    }
}

在任務中的獲取也是同樣的

HelloExtension hello = getProject().getExtensions().getByType(HelloExtension.class);
UserData user = hello.getUser();

集合對象

再看一個 DSL 配置,這種集合嵌套也常常見到,下面也來簡單實現一下。

fruits {
    apple {
        color '紅色'
    }

    grape {
        color '紫紅色'
    }

    banana {
        color '黃色'
    }

    orange {
        color '屎黃色'
    }

}

這種配置是配合 NamedDomainObjectContainer 實現的,它接收一個類,這個類必須有一個包含 name 參數的構造方法。

Fruit.java

/**
 * 必須有一個 name 屬性,而且有一個 name 參數的構造函數
 */
public class Fruit {

    private String name;
    private String color;

    public Fruit(String name) {
        this.name = name;
    }

    public void color(String color){
        setColor(color);
    }

    //省略 setter/getter
}

配置一個 Factory

FruitFactory.java

import org.gradle.api.NamedDomainObjectFactory;
import org.gradle.internal.reflect.Instantiator;

public class FruitFactory implements NamedDomainObjectFactory{

    private Instantiator instantiator;

    public FruitFactory(Instantiator instantiator) {
        this.instantiator = instantiator;
    }

    @Override
    public Fruit create(String name) {
        return instantiator.newInstance(Fruit.class, name);
    }
}

接着就是建立 NamedDomainObjectContainer 對象並添加到 Project 。

GreetPlugin.java

public class GreetPlugin implements Plugin{
    @Override
    public void apply(Project target) {

        Instantiator instantiator = ((DefaultGradle)target.getGradle()).getServices().get(Instantiator.class);

        NamedDomainObjectContainerfruits = target.container(Fruit.class,new FruitFactory(instantiator));

        target.getExtensions().add("fruits",fruits);

        target.getTasks().create("printlnFruits", ShowFruitTask.class);
    }
}

如今應用這個插件就能夠在腳本里使用上述的 DSL 配置了。

最後是 DSL 配置的接收了

public class ShowFruitTask extends DefaultTask {

    @TaskAction
    public void show(){
        NamedDomainObjectContainerfruits = (NamedDomainObjectContainer) getProject().getExtensions().getByName("fruits");

        fruits.forEach(fruit -> {
            String format = String.format("name: %s , color: %s", fruit.getName(), fruit.getColor());
            getLogger().quiet("fruit : {}",format);
        });
    }
}

關於自定義插件的相關介紹就這些了,更詳細的文檔能夠查看 Gradle 用戶手冊

這篇文章的源碼已經放在 github 上:GradlePractice

相關文章
相關標籤/搜索