插件被用來封裝構建邏輯和一些通用配置。將可重複使用的構建邏輯和默認約定封裝到插件裏,以便於其餘項目使用。 你可使用你喜歡的語言開發插件,可是最終是要編譯成字節碼在 JVM 運行的。 Gradle 有兩種插件,腳本插件和二進制插件。java
使用版本 5.6.2git
插件被用來封裝構建邏輯和一些通用配置。將可重複使用的構建邏輯和默認約定封裝到插件裏,以便於其餘項目使用。github
你可使用你喜歡的語言開發插件,可是最終是要編譯成字節碼在 JVM 運行的。api
Gradle 有兩種插件,腳本插件和二進制插件。緩存
關於插件的介紹,能夠參考個人另外一篇文章 Gradle 插件閉包
這裏講的自定義插件是二進制插件,二進制插件能夠打包發佈,有利於分享。app
三個地方的插件的用途目的不一樣。maven
其餘項目沒法使用,只能在本腳本里使用。ide
在項目的 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
這裏使用的是 Groovy 。
在這個目錄下建立項目會被 Gradle 自動識別的。
結構以下
$projectDir/buildSrc/src/main/groovy
這裏作簡單的示範:
在插件裏爲 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(); } }
上面使用了一個擴展來接收參數, 普通的對象就能夠,例如
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" }
例如
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 工程目錄
更改 build.gradle 腳本,配置項目
最後就是這樣子
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 任務,將插件發佈到指定倉庫。
發佈成功後的倉庫
插件建立完成了,也發佈了,下面就是使用這個插件了。
這裏對插件的使用就簡單介紹一下,詳細的能夠查看以前的這篇介紹:Gradle 插件
buildscript { repositories { maven { url 'file://E:/libs/localMaven' } } dependencies { classpath 'com.github.skymxc:plugin:1.0.2' } }
我分別在兩個 Java 項目裏使用了插件:
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 視圖,能夠看到已經添加的任務。
像上面同樣建立一個項目,不過此次是一個 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 工件都是由三部分標識的
日常咱們添加依賴的這樣的:
implementation 'groupId:artifactId:version'
而咱們的插件是經過 id 應用的,怎麼經過 id 找到對應的工件呢,這就有了「插件標記工件」。
應用插件時會把 id 映射成這樣: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 的發佈任務
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 後能夠在對應的倉庫查看
pluginManagement { repositories { maven { url 'file://E:/libs/localMaven' } } }
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 呢,答案是隨便一個普通類均可以的。
經過 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 很常見,那怎麼實現的呢。
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