自定义Gradle插件(码一个简单的gradle插件)

对于Java工程来说,估计大家使用的自动化构建管理工具基本都是Maven吧,当然我也是maven的忠实拥护者,而Android里使用的自动化管理工具是Gradle,当然还有最初Android在eclipse中使用的ant管理打包工具,这个是年前的事情我们直接忽略吧,开发过Android的且使用android stdio工具的大神们,它不仅能开发Android还能开发java、python等,当然要想开发这些东西,必须依赖我们对应的插件,而我们今天手写一个简单gradle插件。


一、使用Gradle创建项目

由于我一直都是在开发Android的时候使用Gradle,而在开发java的时候使用的是Maven,而Gradle强大的工具不仅限于开发Android,当然不是说Maven不强大于是乎开搞:

  1. 首先创建我们的项目(新建文件夹:其名称为我们的项目名)

  2. 在我们的项目根目录下创建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
        ]
    }
    
  3. 开始创建我们的java工程,what?不是自定义Gradle插件吗,客观别急,倾听我娓娓道来,在根目录下创建module(创建文件夹GradleJava),并在其内创建其自己的build.gradle文件,然后在之前创建的setting.gradle文件里声明添加此modlue(GradleJava)为:include ":GradleJava",在build.gradle(GradleJava)中依赖java插件为:apply plugin: 'java',同步配置项目,发现我们的项目结构是这样的:
    在这里插入图片描述
    ????,啥情况,难道要我一个个创建对应的文件夹,好烦!!!,也不知道哪来劲,干脆自己写一个自动创建成自己的想要的资源目录结构,目标结构如下(忽略红色部分谢谢?,并不影响):
    在这里插入图片描述

二、手写一个Gradle插件
  1. 创建我们groovy项目,为了简便我就直接在我们的,刚才创建的项目里创建我们module项目,点击Gradle->Groovy->next,名为JavaConfigPlugin吧,后就是大家熟悉创建项目环节我不再掩饰。 在这里插入图片描述

  2. 我的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' 
            }
        }
    }
    
  3. 首先创建我们的核型插件类JavaConfigPlugin并且实现接口Plugin:
    当依赖此插件时,会调用其apply方法,我们可以在此方法里进行我们需要的操作:

    public class JavaConfigPlugin implements Plugin<Project> {
    	@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);
        }
    }
    
  4. 编码我们插件工作内容:
    1)project获取Extensions,然后创建我们自己的Extension(fileConfig常量值配置的有),后续才可以在build.gradle文件中配置我们的属性,类似rootProject的ext,这里不再详细讲解后续会更细致的解读。
    2)创建我们的任务,此任务负责创建形成java项目目录结构,且创建我们的配置的包。
    代码如下:

    public class JavaConfigPlugin implements Plugin<Project> {
    
        @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<Boolean> 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;
            };
        }
    }	
    
  5. 配置我们的插件id,在resources目录下创建META-INF/gradle-plugins/idName.properties文件,我起的idName是javaFileConfig,也就是META-INF/gradle-plugins/javaFileConfig.properties,在里面添加键值对implemention-class=类的全县定名,我的是implementation-class=vip.zhuhailong.config.JavaConfigPlugin

  6. 此时编译我们的项目,编译成功后在右边gradle模块找到我们对应项目里的task对应的publish任务,双击执行:
    在这里插入图片描述
    此时出现如下提示,那说明我们打包发布成功了,就可以在自己配置的仓库里找到我们对应的插件jar包了
    在这里插入图片描述
    我的仓库(java-file-config):
    在这里插入图片描述

三、依赖我们自己的插件,并配置执行
  1. 回到我们刚才的java项目的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'
    
  2. 同步编译会在右侧gradle模块中找到我们在Plugin中创建的task任务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任务结果如下,成功创建:

task执行结果信息
构建项目结构结果
四、未完待续

这样我们手写一个简单的gradle插件就完成了,这只是最简单的一种了吧,还有一些更高的配置,这些写成插件的话,就需要一些更复杂实现和一些复杂的概念,比如Android里的签名配置,对于编写插件来说那就是一种容器,今天就到这里吧,后续在继续深入讲解。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值