美文网首页
自定义Gradle Plugin入门——Git仓库管理插件

自定义Gradle Plugin入门——Git仓库管理插件

作者: 我想摸你的大腿只是简单的试探啊 | 来源:发表于2022-03-17 11:56 被阅读0次
    功能介绍

    以前的项目业务非常多,分了很多模块,每个模块都做成库上传都仓库。这样的话每次开发新业务切换分支都累个半死。

    所以组里的大佬搞了一些脚本,有shell的,有python的,但是要多学一门语言就感觉很头疼,所以想着能不能用Gradle写个插件实现。好吧,为了不学shellpython,花了很长时间研究Gradle,笑死。

    这个插件主要的功能就是从不同的远程拉取多个仓库到当前项目,后期切换分支的时候,修改一下分支名就好了,具体使用方式如下

    gitClone {
        //很多项目的远程仓库不止一个,这里可以设置仓库的域名
        gitStore("https://gitee.com") {
            //配置仓库中的项目
            gitProject(
                //项目链接
                url = "jaso_chen.com/camera-study",
                //项目分支,以后切换分支大概就是修改branch
                branch = "master",
                //存放路径
                saveDir = buildDir.absolutePath + "/testDir",
                //是否需要重命名文件夹
                rename = "CameraStudy")
            gitProject(
                url = "jaso_chen.com/camera-study",
                branch = "testBranch",
                saveDir = buildDir.absolutePath + "/testDir",
                rename = "CameraStudyTest")
        }
    }
    
    实现步骤
    1. 创建一个Java-Module,包名为buildSrc,只有这个名字才能识别为插件

    我也不知道为什么必须这个名字,也是百度到的,官方也是以这个为例,当时折腾好久都快崩溃了

    [图片上传失败...(image-4b6638-1647489362381)]

    1. 配置build.gradle.kts,因为是用kotlin编写的插件,所以转成kts来配置比较方便
      这里要注意的是一定要添加implementation(gradleApi()),否则调用不了Gradle的类
      其他配置最好也和这里的保持一致,不然报错比较麻烦
    plugins {
        java
        kotlin("jvm") version ("1.6.10")
    }
    
    group = "org.example"
    version = "1.0-SNAPSHOT"
    
    repositories {
        google()
        mavenCentral()
        gradlePluginPortal()
    }
    java {
        targetCompatibility = JavaVersion.VERSION_1_8
        sourceCompatibility = JavaVersion.VERSION_1_8
    }
    dependencies {
        //添加Gradle相关的API,否则无法自定义Plugin和Task
        implementation(gradleApi())
        implementation(kotlin("stdlib"))
        implementation(kotlin("stdlib-jdk8"))
        implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1")
    }
    
    1. 找到我们的类,开始编写代码

    [图片上传失败...(image-bd7c29-1647489362381)]

    如果刚刚创建项目没有修改主类,就没有这个类,可以自己随便创建一个

    1. 先继承Plugin这个类,并重写apply()方法
    class GitPlugin : Plugin<Project> {
        override fun apply(target :Project) {
            //这里是插件的入口,当gradle引入了插件,在配置阶段就会执行这个方法
        }
    }
    
    1. 我们这个插件需要做一个同时拉取多个仓库的功能,不过无论什么插件,第一步都是需要收集数据的

      但是我们的代码写在这个类中,怎么去收集数据呢?
      GradleProject为我们提供了一个扩展属性的列表,可以向这个列表中添加我们提供给Gradle调用的方法、变量

    class GitPlugin : Plugin<Project> {
        override fun apply(target :Project) {
            //给gradle扩展一个方法,这样在build.gradle文件里面就可以传参给我们这里
            //我的想法是提供一个dsl,按规则传参,所以这里传入一个类,便于我们编写dsl
            //这里注意,如果传入的是对象,这个对象必须是open修饰的,否则Gradle无法构造
            target.extensions.create("gitClone", GitScope::class.java)
        }
    }
    

    在上面定义好扩展之后可以在build.gradle调用

    //build.gradle
    gitClone { //this = GitScope
    
    }
    
    1. 需要定义DSL和收集数据,首先要定义好相关的类
    //用于定义DSL
    open class GitScope {
        internal val stores = arrayListOf<GitStore>()
    }
    //用于定义DSL
    data class GitStore(val host: String) {
        internal val projects = arrayListOf<GitProject>()
    }
    //用于接收数据
    data class GitProject(
        //项目链接
        val url: String,
        //项目分支,以后切换分支大概就是修改branch
        val branch: String = "",
        //存放路径
        val saveDir: String,
        //是否需要重命名文件夹
        val rename: String = ""
    )
    
    1. 定义一些DSLgit执行的命令,由于功能比较多,这里只贴部分代码,详细的可以到项目里看
    
    fun scope(scope: GitScope.() -> Unit) {
        val gitScope = GitScope()
        gitScope.scope()
    }
    
    fun GitScope.gitStore(host: String, scope: GitStore.() -> Unit) {
        val store = GitStore(host)
        store.scope()
        this.stores.add(store)
    }
    
    fun GitStore.gitProject(url: String, branch: String = "", saveDir: String, rename: String = "") {
        val project = GitProject(url, branch, saveDir, rename)
        this.projects.add(project)
    }
    
    //clone仓库
    internal fun gitClone(repoUrl: String, dir: String) {
        "git clone $repoUrl $dir".exeCommand()
    }
    
    //切换分支
    internal fun gitCheckout(branch: String, dir: String) {
        "git checkout -b $branch origin/$branch".exeCommand(dir)
    }
    
    1. 现在先不管build.gradle怎么配置参数,先把功能给写完,我们先假设已经配置好参数了,那怎么获取参数呢?

      其实也很简单,怎么提供出去的,怎么获取回来。通过project.extensions.getByName("gitClone")就可以获取到我们传的对象。

    //部分代码不完整
    private fun gitCloneTask(task: Task) = runBlocking {
        //这里用了协程是为了可以多个仓库同时拉取,更重要的是为了等待所有任务完成
        //而且我们定义的是单个独立的任务,几乎瞬间就退出,所以需要协程,也为了学习协程
        coroutineScope.launch {
            //通过extensions获取到我们收集的数据
            val gitScope = task.project.extensions.getByName("gitClone") as GitScope
            //遍历每个仓库
            for (store in gitScope.stores) {
                cloneStoreTask(store)
            }
        }.join()
    }
    
    private fun CoroutineScope.cloneStoreTask(store: GitStore) {
        //遍历仓库里每个项目
        for (project in store.projects) {
            async {
                //命令里使用的斜杠要么是双斜杠\\,要么是反斜杠/,需要处理一下
                gitClone("${store.host}/${project.url}", project.branch,
                    "${project.saveDir.replace("\\", "/")}/${project.rename}"
                )
            }
        }
    }
    
    1. 功能都写好了,那么什么时候执行这段功能呢?我的想法是,提供一个Task任务,等开发者点一下,或者输入一串命令执行,那这样就需要给当前引入插件的Gradle定义一个任务

      apply()方法里可以拿到Project对象,就可以直接在tasks列表里创建一个任务

    override fun apply(target: Project) {
        //配置git扩展
        target.extensions.create("gitClone", GitScope::class.java)
        //定义gitClone任务
        target.tasks.create("gitClone").apply {
            //定义分组,容易查找
            group = "git"
            //适当描述一下功能
            description = "用于管理多仓库初始化、切换分支"
            //在任务执行之后再执行我们的功能
            doLast(this@GitPlugin::gitCloneTask)
        }
    }
    

    task默认是在配置阶段运行的,也就是执行"clean"、"sync gradle"这些task都会被执行,为了避免每次都触发,我们把代码写在单独运行的时候再执行

    1. 插件的功能已经写好了,其他模块怎么使用呢,implementation吗,那肯定不是的,插件有插件依赖的方式,我们都见过,在plugins{}里引入,但是怎么引入呢?这就需要我们为我们写的插件做一个注册声明

    [图片上传失败...(image-20a255-1647489362381)]

    [图片上传失败...(image-641387-1647489362381)]

    META-INF/gradle-plugin

    [图片上传失败...(image-fed14d-1647489362381)]

    implementation-class=com.chenchen.plugin.git.GitPlugin

    1. 这样我们的插件就弄好了,接下来是怎么引入和配置了,打开任意一个build.gradle,我这里选了app/build.gradle

    由于我们的插件是用kotlin写的,里面包含一些kotlin特性,我不知道怎么在groovy使用,所以把build.gradle改成了build.gradle.kts

    //app/build.gradle.kts
    plugins {
        id("com.android.application")
        id("org.jetbrains.kotlin.android")
        //添加插件
        id("GitPlugin")
    }
    
    //...省略一大片代码
    
    //kts调用类的时候需要导包
    import com.chenchen.plugin.git.*
    //这个就是我们在插件里提供的扩展方法,具体的使用方式就如以下这样
    gitClone {
        //很多项目的远程仓库不止一个,这里可以设置仓库的域名
        gitStore("https://gitee.com") {
            //配置仓库中的项目
            gitProject(
                //项目链接
                url = "jaso_chen.com/camera-study",
                //项目分支,以后切换分支大概就是修改branch
                branch = "master",
                //存放路径
                saveDir = buildDir.absolutePath + "/testDir",
                //是否需要重命名文件夹
                rename = "CameraStudy")
            gitProject(
                url = "jaso_chen.com/camera-study",
                branch = "testBranch",
                saveDir = buildDir.absolutePath + "/testDir",
                rename = "CameraStudyTest")
        }
    }
    
    1. 编写好这段配置之后,点击sync project with gradle files同步一下,我们就可以在右边的gradle任务列表里找到这个任务了,我是在app/build.gradle.kts引入插件的,那在app模块里就可以找到git/gitClone这个任务

    [图片上传失败...(image-c0ca90-1647489362381)]

    自定义插件的方式到这就结束了。这个步骤比较初级,还有很多需要完善的,但是新手入门已经是够了,看起来非常简单,实际上我花了超过20小时的时间来完成,中间遇到各种编译不通过,依赖出问题,Gradle报错看不懂,等等问题

    总而言之,Gradle非常复杂,但也非常有用,学一点皮毛能减少大量的时间,多摸鱼不好吗!!!

    以上的功能还有不满意的地方:

    • 无法给Settings.gradle依赖,我想在拉代码的时候,同时自动帮这些库include进去,折腾好久,感觉做不到。
      </p>

    • 只能给build.gradle这个级别的引入插件,虽然配置内容不多,但也显得比较臃肿,没法放在一个独立的文件去配置

    如果有大佬懂的,或者以后实力上涨了,有解决办法了再继续完善。

    项目地址:

    https://gitlab.com/c297131019/gitplugin

    相关文章

      网友评论

          本文标题:自定义Gradle Plugin入门——Git仓库管理插件

          本文链接:https://www.haomeiwen.com/subject/yjsvdrtx.html