插件被用來封裝構建邏輯和一些通用配置。將可重覆使用的構建邏輯和預設約定封裝到插件里,以便於其他項目使用。 你可以使用你喜歡的語言開發插件,但是最終是要編譯成位元組碼在 JVM 運行的。 Gradle 有兩種插件,腳本插件和二進位插件。 ...
使用版本 5.6.2
插件被用來封裝構建邏輯和一些通用配置。將可重覆使用的構建邏輯和預設約定封裝到插件里,以便於其他項目使用。
你可以使用你喜歡的語言開發插件,但是最終是要編譯成位元組碼在 JVM 運行的。
Gradle 有兩種插件,腳本插件和二進位插件。
關於插件的介紹,可以參考我的另一篇文章 Gradle 插件
這裡講的自定義插件是二進位插件,二進位插件可以打包發佈,有利於分享。
可以在三個地方定義插件
- 在腳本里
- 在 buildSrc 下
- 在單獨的項目里
三個地方的插件的用途目的不同。
在腳本里的插件
其他項目無法使用,只能在本腳本里使用。
在 buildSrc 下
在項目的 buildSrc 目錄下的插件,這個項目里的所有(子)項目都可以使用。
在單獨的項目里
你可以為你的插件創建一個項目,這個項目可以打包發佈 JAR,提供給其他任何項目使用。
創建一個插件
建議使用靜態語言,例如 Java ,Kotlin,開發工具建議使用 IntelliJ IDEA 。
一個插件就是個實現了 Plugin
當插件被應用到項目時,Gradle 會實例化這個插件並調用 Plugin.apply() 方法,並將這個項目的實例當做參數傳遞進去。插件就可以對這個項目進行各種配置了。
CustomPLugin.java
// 定義一個插件
class CustomPLugin implements Plugin<Project>{
@Override
void apply(Project target) {
// do something
}
}
前面說到可以在三個地方創建插件,現在來一一實現下。
在腳本里創建一個插件
可以在 build.gradle 腳本里任意地方定義。
build.gradle
// 定義一個插件
class CustomPLugin implements Plugin<Project>{
@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
- 在 buildSrc 下按照 java 工程或者 groovy 工程(這取決於你用什麼語言)新建目錄
$projectDir/buildSrc/src/main/groovy
- 在 groovy 創建你的包 (可能現在還不能被識別為項目,那就創建目錄),例如 com.github.skymxc
- 在包里創建插件,也就是創建一個實現了 Plugin 的類。
這裡做簡單的示範:
在插件里為 jar 任務添加一個操作:生成記錄文件
JarLogPlugin.groovy
/**
* 輸出 生成記錄到指定文件
*/
class JarLogPlugin implements Plugin<Project> {
@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
- 在 main 目錄下創建 resources 文件夾
- 在 resources 目錄下創建 META-INF 文件夾
- 在 META-INF 目錄下創建 gradle-plugins 文件夾
- 在 gradle-plugins 目錄下創建 properties 文件,名字就是你的插件 ID。
- 在 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 工程目錄
更改 build.gradle 腳本,配置項目
- 應用 maven-publih 插件
- 添加 Gradle 和 Groovy 的依賴
- 配置上傳任務
最後就是這樣子
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<Project> {
@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 插件
- 在根項目的 build.gradle 配置倉庫,添加依賴
buildscript {
repositories {
maven {
url 'file://E:/libs/localMaven'
}
}
dependencies {
classpath 'com.github.skymxc:plugin:1.0.2'
}
}
- 應用插件
我分別在兩個 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 視圖,可以看到已經添加的任務。
使用 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 後可以在對應的倉庫查看
發佈插件後的使用
- 配置倉庫,這次在 settings.gradle 里配置
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 配置塊進行的。
那怎麼為插件配置 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<Project> {
@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<? super UserData> action) {
action.execute(user);
}
// 省略其他 getter/setter
}
最後就是添加到項目的擴展了,和前面一樣
public class GreetPlugin implements Plugin<Project> {
@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<Fruit> {
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<Project> {
@Override
public void apply(Project target) {
Instantiator instantiator = ((DefaultGradle)target.getGradle()).getServices().get(Instantiator.class);
NamedDomainObjectContainer<Fruit> fruits = 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(){
NamedDomainObjectContainer<Fruit> fruits = (NamedDomainObjectContainer<Fruit>) 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
資料
- 自定義插件 https://docs.gradle.org/current/userguide/custom_plugins.html
- 開發輔助插件 https://docs.gradle.org/current/userguide/java_gradle_plugin.html
- 使用插件 https://docs.gradle.org/current/userguide/plugins.html
- 發佈 https://docs.gradle.org/current/userguide/publishing_overview.html
- maven 發佈插件 https://docs.gradle.org/current/userguide/publishing_maven.html
- Gradle 教程 https://gradle.org/guides/?q=Plugin%20Development
- Gradle DSL https://blog.csdn.net/zlcjssds/article/details/79229209
End