对于Java工程来说,估计大家使用的自动化构建管理工具基本都是Maven吧,当然我也是maven的忠实拥护者,而Android里使用的自动化管理工具是Gradle,当然还有最初Android在eclipse中使用的ant管理打包工具,这个是年前的事情我们直接忽略吧,开发过Android的且使用android stdio工具的大神们,它不仅能开发Android还能开发java、python等,当然要想开发这些东西,必须依赖我们对应的插件,而我们今天手写一个简单gradle插件。
由于我一直都是在开发Android的时候使用Gradle,而在开发java的时候使用的是Maven,而Gradle强大的工具不仅限于开发Android,当然不是说Maven不强大于是乎开搞:
首先创建我们的项目(新建文件夹:其名称为我们的项目名)
在我们的项目根目录下创建build.gradle、settings.gradle(内容目前空)文件,然后导入Idea,以下为build.gradle文件:
apply {
from 'config.gradle' //引入自定义属性文件
}
allprojects {//所有项目的依赖
repositories { //仓库
mavenCentral()
jcenter()
google()
}
}
其中自定义属性文件是我在根目录下创建的一个config.gradle文件,是不是很熟悉:
ext {
dependenciesVersion = [
'okHttp' : "3.14.0",//okHttp
"retrofit2" : '2.5.0',//retrofit....
"retrofit2_converter_gson" : '2.5.0',
"retrofit2_adapter_rxjava2": '2.5.0',
"gson" : '2.8.5',//Gson
"rxjava2_rxjava" : '2.2.7',//rxJava2:rxJava
]
dependencies = [
"okHttp" : "com.squareup.okhttp3:logging-interceptor:$dependenciesVersion.okHttp",//日志过滤处理器(包括okHttp)
"retrofit2" : "com.squareup.retrofit2:retrofit:$dependenciesVersion.retrofit2",//retrofit.....
"retrofit2_converter_gson" : "com.squareup.retrofit2:converter-gson:$dependenciesVersion.retrofit2_converter_gson",
"retrofit2_adapter_rxjava2": "com.squareup.retrofit2:adapter-rxjava2:$dependenciesVersion.retrofit2_adapter_rxjava2",
"gson" : "com.google.code.gson:gson:$dependenciesVersion.gson",//gson
"rxjava2_rxjava" : "io.reactivex.rxjava2:rxjava:$dependenciesVersion.rxjava2_rxjava",//rxJava
]
}
开始创建我们的java工程,what?不是自定义Gradle插件吗,客观别急,倾听我娓娓道来,在根目录下创建module(创建文件夹GradleJava),并在其内创建其自己的build.gradle文件,然后在之前创建的setting.gradle文件里声明添加此modlue(GradleJava)为:include ":GradleJava"
,在build.gradle(GradleJava)中依赖java插件为:apply plugin: 'java'
,同步配置项目,发现我们的项目结构是这样的:
????,啥情况,难道要我一个个创建对应的文件夹,好烦!!!,也不知道哪来劲,干脆自己写一个自动创建成自己的想要的资源目录结构,目标结构如下(忽略红色部分谢谢?,并不影响):
创建我们groovy项目,为了简便我就直接在我们的,刚才创建的项目里创建我们module项目,点击Gradle->Groovy->next,名为JavaConfigPlugin吧,后就是大家熟悉创建项目环节我不再掩饰。
我的ide创建module的build.gradle文件内容为:
plugins {
id 'groovy'
}
version 'unspecified'
repositories {
mavenCentral()
}
dependencies {
compile 'org.codehaus.groovy:groovy-all:2.3.11'
testCompile group: 'junit', name: 'junit', version: '4.12'
}
由于我们还需要发布我们的插,及添加开发插件所需要的相关api,对build.gradle做一下调整为:
plugins {
id 'groovy'
id 'maven-publish' //发布插件
}
repositories {
mavenCentral()
}
dependencies {//开发插件所需要的依赖
implementation gradleApi()
implementation localGroovy()
implementation rootProject.ext.dependencies.rxjava2_rxjava //从config.gradle引用的依赖,因为本次开发使用到了Rxjava
}
publishing {//发布配置
publications {
maven(MavenPublication) {
from components.java //打包为 jar
groupId 'vip.zhuhailong'
artifactId 'java-file-config'
version '1.0'
}
}
repositories {//要发布到的目的仓库
maven {
url '/Users/long/repository'
}
}
}
首先创建我们的核型插件类JavaConfigPlugin并且实现接口Plugin:
当依赖此插件时,会调用其apply方法,我们可以在此方法里进行我们需要的操作:
public class JavaConfigPlugin implements Plugin {
@Override
public void apply(Project project) {
}
}
由于像src/main/java等文件夹都是一些固定的唯一不确定的是java目录下的包名,所以我们想要动态创建我们包名,需要创建我们的Extension类-FileConfig,其属性myPackageName就是我们依赖此插件自动为我们生成的包名:
public class FileConfig {
private String myPackageName;
public String getMyPackageName() {
return myPackageName;
}
public void setMyPackageName(String myPackageName) {
this.myPackageName = myPackageName;
}
}
接下来创建我们的一些常量值如下:
public final class R {
/**
* task
*/
public static final class task {
public static final String FILE_CONFIG = "FileConfig";
}
/**
* Task Group name
*/
public static final class group {
public static final String JAVA_FILE_CONFIG = "javaFileConfig";
}
/**
* Extension
*/
public static final class extension {
public static final String FILE_CONFIG = "fileConfig";
}
/**
* file path
*/
public static final class path {
/**
* src/main/*
*/
public static final String SRC_MAIN_JAVA_DIR = "/src/main/java";
public static final String SRC_MAIN_RESOURCES_DIR = "/src/main/resources";
/**
* src/test/*
*/
public static final String SRC_TEST_JAVA_DIR = "/src/test/java";
public static final String SRC_TEST_RESOURCES_DIR = "/src/test/resources";
}
}
和一个简单的String工具类:
public final class StringUtil {
/**
* 判断字符串是否为null
* @param string
* @return
*/
public static final boolean isEmpty(String string) {
return null == string || "".equals(string) || "null".equalsIgnoreCase(string);
}
}
编码我们插件工作内容:
1)project获取Extensions,然后创建我们自己的Extension(fileConfig常量值配置的有),后续才可以在build.gradle文件中配置我们的属性,类似rootProject的ext,这里不再详细讲解后续会更细致的解读。
2)创建我们的任务,此任务负责创建形成java项目目录结构,且创建我们的配置的包。
代码如下:
public class JavaConfigPlugin implements Plugin {
@Override
public void apply(Project project) {
FileConfig fileConfig = project.getExtensions().create(R.extension.FILE_CONFIG, FileConfig.class);//创建我们的配置入口类似rootProject的ext吧
project.getTasks().create(R.task.FILE_CONFIG).doLast(task -> {//创建我们的可执行任务
String projectDir = project.getProjectDir().getPath();//获取module的路径
String myPackageName = fileConfig.getMyPackageName();//获取我们在build.gradle
中的配置属性
Observable.just(StringUtil.isEmpty(myPackageName))//搭建对应的项目目录结构,其中使用takeWhile是想在上一个文件夹创建失败后终止当前搭建任务,对于已经创建的暂不处理,简单插件?
.takeWhile(b -> {
if (b)
throw new IllegalArgumentException("Can't find argument myPackageName");
return true;
})
.takeWhile(createFile(projectDir + R.path.SRC_MAIN_JAVA_DIR + File.separator + myPackageName))
.takeWhile(createFile(projectDir + R.path.SRC_MAIN_RESOURCES_DIR))
.takeWhile(createFile(projectDir + R.path.SRC_TEST_JAVA_DIR + File.separator + myPackageName))
.takeWhile(createFile(projectDir + R.path.SRC_TEST_RESOURCES_DIR))
.subscribe(b -> {
}, throwable ->
System.err.println(throwable.getMessage()), () -> System.out.println("success to create file"));
}).setGroup(R.group.JAVA_FILE_CONFIG);
}
/**
* 创建文件
*
* @param path
*/
private Predicate createFile(String path) {
return b -> {
File file = new File(path);
if (file.exists())
throw new IllegalArgumentException("the [" + path + "] is exists!");
if (!file.mkdirs())
throw new IllegalArgumentException("The result of creating [" + path + "] is failure!");
return true;
};
}
}
配置我们的插件id,在resources目录下创建META-INF/gradle-plugins/idName.properties
文件,我起的idName是javaFileConfig,也就是META-INF/gradle-plugins/javaFileConfig.properties
,在里面添加键值对implemention-class=类的全县定名
,我的是implementation-class=vip.zhuhailong.config.JavaConfigPlugin
此时编译我们的项目,编译成功后在右边gradle模块找到我们对应项目里的task对应的publish任务,双击执行:
此时出现如下提示,那说明我们打包发布成功了,就可以在自己配置的仓库里找到我们对应的插件jar包了
我的仓库(java-file-config):
buildscript { //构建我们依赖脚本
repositories {
maven {
mavenCentral() //为了插件的Rxjava能集成进来
url '/Users/long/repository'
}
}
dependencies {
classpath 'vip.zhuhailong:java-file-config:1.0'
}
}
apply plugin: 'java'
apply plugin: 'javaFileConfig'
FileConfig
:> Task :GradleJava:FileConfig
Can't find argument myPackageName
BUILD SUCCESSFUL in 0s
1 actionable task: 1 executed
17:10:53: Task execution finished 'FileConfig'.
结果显示Can’t find argument myPackageName,是不是很熟悉,这不就是我们前面写的异常吗?
,这是由于我们没有在build.gradle中配置我们的的包名,配置后在在执行:
buildscript { //构建我们依赖脚本
repositories {
maven {
mavenCentral() //为了插件的Rxjava能集成进来
url '/Users/long/repository'
}
}
dependencies {
classpath 'vip.zhuhailong:java-file-config:1.0'
}
}
apply plugin: 'java'
apply plugin: 'javaFileConfig'
fileConfig {
myPackageName 'vip.zhuhailong.gradlejava'
}
再次编译执行我们的task任务结果如下,成功创建:
![]() |
![]() |
这样我们手写一个简单的gradle插件就完成了,这只是最简单的一种了吧,还有一些更高的配置,这些写成插件的话,就需要一些更复杂实现和一些复杂的概念,比如Android里的签名配置,对于编写插件来说那就是一种容器,今天就到这里吧,后续在继续深入讲解。