功能介绍
以前的项目业务非常多,分了很多模块,每个模块都做成库上传都仓库。这样的话每次开发新业务切换分支都累个半死。
所以组里的大佬搞了一些脚本,有shell
的,有python
的,但是要多学一门语言就感觉很头疼,所以想着能不能用Gradle
写个插件实现。好吧,为了不学shell
和python
,花了很长时间研究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")
}
}
实现步骤
- 创建一个
Java-Module
,包名为buildSrc
,只有这个名字才能识别为插件
我也不知道为什么必须这个名字,也是百度到的,官方也是以这个为例,当时折腾好久都快崩溃了
[图片上传失败...(image-4b6638-1647489362381)]
- 配置
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")
}
- 找到我们的类,开始编写代码
[图片上传失败...(image-bd7c29-1647489362381)]
如果刚刚创建项目没有修改主类,就没有这个类,可以自己随便创建一个
- 先继承
Plugin
这个类,并重写apply()
方法
class GitPlugin : Plugin {
override fun apply(target :Project) {
//这里是插件的入口,当gradle引入了插件,在配置阶段就会执行这个方法
}
}
-
我们这个插件需要做一个同时拉取多个仓库的功能,不过无论什么插件,第一步都是需要收集数据的
但是我们的代码写在这个类中,怎么去收集数据呢?
Gradle
的Project
为我们提供了一个扩展属性的列表,可以向这个列表中添加我们提供给Gradle
调用的方法、变量
class GitPlugin : Plugin {
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
}
- 需要定义
DSL
和收集数据,首先要定义好相关的类
//用于定义DSL
open class GitScope {
internal val stores = arrayListOf()
}
//用于定义DSL
data class GitStore(val host: String) {
internal val projects = arrayListOf()
}
//用于接收数据
data class GitProject(
//项目链接
val url: String,
//项目分支,以后切换分支大概就是修改branch
val branch: String = "",
//存放路径
val saveDir: String,
//是否需要重命名文件夹
val rename: String = ""
)
- 定义一些
DSL
和git
执行的命令,由于功能比较多,这里只贴部分代码,详细的可以到项目里看
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)
}
-
现在先不管
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}"
)
}
}
}
-
功能都写好了,那么什么时候执行这段功能呢?我的想法是,提供一个
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都会被执行,为了避免每次都触发,我们把代码写在单独运行的时候再执行
- 插件的功能已经写好了,其他模块怎么使用呢,
implementation
吗,那肯定不是的,插件有插件依赖的方式,我们都见过,在plugins{}
里引入,但是怎么引入呢?这就需要我们为我们写的插件做一个注册声明
[图片上传失败...(image-20a255-1647489362381)]
[图片上传失败...(image-641387-1647489362381)]
META-INF/gradle-plugin
[图片上传失败...(image-fed14d-1647489362381)]
implementation-class=com.chenchen.plugin.git.GitPlugin
- 这样我们的插件就弄好了,接下来是怎么引入和配置了,打开任意一个
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")
}
}
- 编写好这段配置之后,点击
sync project with gradle files
同步一下,我们就可以在右边的gradle
任务列表里找到这个任务了,我是在app/build.gradle.kts
引入插件的,那在app
模块里就可以找到git/gitClone
这个任务
[图片上传失败...(image-c0ca90-1647489362381)]
自定义插件的方式到这就结束了。这个步骤比较初级,还有很多需要完善的,但是新手入门已经是够了,看起来非常简单,实际上我花了超过20小时的时间来完成,中间遇到各种编译不通过,依赖出问题,Gradle
报错看不懂,等等问题
总而言之,Gradle
非常复杂,但也非常有用,学一点皮毛能减少大量的时间,多摸鱼不好吗!!!
以上的功能还有不满意的地方:
无法给
Settings.gradle
依赖,我想在拉代码的时候,同时自动帮这些库include
进去,折腾好久,感觉做不到。
只能给
build.gradle
这个级别的引入插件,虽然配置内容不多,但也显得比较臃肿,没法放在一个独立的文件去配置
如果有大佬懂的,或者以后实力上涨了,有解决办法了再继续完善。