Function is introduced

The previous project business was very large, divided into many modules, each module is made into a library upload warehouse. That way, every time you develop a new business, it’s exhausting to switch branches.

So the big guy in the group made some scripts, some shell, some Python, but it was a headache to learn more than one language, so I thought I could use Gradle to write a plug-in. Well, I spent a lot of time studying Gradle to get out of shell and Python. Lol.

The main function of this plug-in is to pull multiple repositories from different remote locations to the current project. When switching branches later, just change the branch name. The specific usage is as follows

gitClone {
    // Many projects have more than one remote repository. Here you can set the domain name of the repository
    gitStore("https://gitee.com") {
        // Configure the items in the repository
        gitProject(
            // Project link
            url = "jaso_chen.com/camera-study".// Change the project branch
            branch = "master".// Save path
            saveDir = buildDir.absolutePath + "/testDir".// Whether to rename the folder
            rename = "CameraStudy")
        gitProject(
            url = "jaso_chen.com/camera-study",
            branch = "testBranch",
            saveDir = buildDir.absolutePath + "/testDir",
            rename = "CameraStudyTest")}}Copy the code
Implementation steps
  1. To create aJava-Module, the package,buildSrcOnly this name can be recognized as a plug-in

I also do not know why must this name, also be baidu arrive, the official also is with this for example, at that time toss about for a long time all fast collapse

  1. configurationbuild.gradle.ktsBecause it is usedkotlinWritten plug-in, so convert toktsThe important thing to note here is that you must addimplementation(gradleApi())Otherwise, Gradle classes will not be called and the rest of the configuration should be the same as here
plugins {
    java
    kotlin("jvm") version ("1.6.10")
}

group = "org.example"
version = "1.0 the SNAPSHOT"

repositories {
    google()
    mavenCentral()
    gradlePluginPortal()
}
java {
    targetCompatibility = JavaVersion.VERSION_1_8
    sourceCompatibility = JavaVersion.VERSION_1_8
}
dependencies {
    // Add gradle-related apis, otherwise you cannot customize plugins and tasks
    implementation(gradleApi())
    implementation(kotlin("stdlib"))
    implementation(kotlin("stdlib-jdk8"))
    implementation("Org. Jetbrains. Kotlinx: kotlinx coroutines -- core: 1.5.1." ")}Copy the code
  1. Find our class and start writing code

If you didn’t modify the main class when you created the project, you don’t have it. You can create one yourself

  1. To carry onPluginThis class, let me rewrite itapply()methods
class GitPlugin : Plugin<Project> {
    override fun apply(target :Project) {
        // This is the entrance to the plugin. When Gradle introduces the plugin, this method is executed during the configuration phase}}Copy the code
  1. We need to do a plugin to pull multiple repositories at the same time, but no matter what plugin, the first step is to collect data

    But how do we collect data when our code is written in this class? Gradle’s Project provides us with a list of extended properties to which we can add methods and variables that we provide for Gradle calls

class GitPlugin : Plugin<Project> {
    override fun apply(target :Project) {
        // Extend gradle with a method that can be passed to us in the build.gradle file
        // My idea is to provide a DSL with regular arguments, so I pass in a class so we can write a DSL
        // Note here that if an object is passed in, the object must be open decorated, otherwise Gradle cannot construct it
        target.extensions.create("gitClone", GitScope::class.java)
    }
}
Copy the code

After the extension is defined above, it can be called in build.gradle

//build.gradle
gitClone { //this = GitScope

}
Copy the code
  1. You need to defineDSLTo collect data, first define the relevant classes
// Used to define a DSL
open class GitScope {
    internal val stores = arrayListOf<GitStore>()
}
// Used to define a DSL
data class GitStore(val host: String) {
    internal val projects = arrayListOf<GitProject>()
}
// Used to receive data
data class GitProject(
    // Project link
    val url: String,
    // Change the project branch
    val branch: String = "".// Save path
    val saveDir: String,
    // Whether to rename the folder
    val rename: String = ""
)
Copy the code
  1. Define someDSLandgitExecute the command, due to more functions, here only part of the code, details can be seen in the project

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 warehouse
internal fun gitClone(repoUrl: String, dir: String) {
    "git clone $repoUrl $dir".exeCommand()
}

// Switch branches
internal fun gitCheckout(branch: String, dir: String) {
    "git checkout -b $branch origin/$branch".exeCommand(dir)
}
Copy the code
  1. Build. Gradle = build.gradle = build.gradle = build.gradle = build.gradle = build.gradle = build.gradle

    In fact, it is very simple, how to provide out, how to get back. Through the project. Extensions. GetByName (” gitClone “) can get to the us for to preach the object.

// Some code is not complete
private fun gitCloneTask(task: Task) = runBlocking {
    // Coroutines are used here to allow multiple repositories to pull at the same time, and more importantly to wait for all tasks to complete
    // And we define a single, independent task that exits almost instantaneously, so we need coroutines, also for learning coroutines
    coroutineScope.launch {
        // Get the data we collected through extensions
        val gitScope = task.project.extensions.getByName("gitClone") as GitScope
        // Walk through each warehouse
        for (store in gitScope.stores) {
            cloneStoreTask(store)
        }
    }.join()
}

private fun CoroutineScope.cloneStoreTask(store: GitStore) {
    // Walk through each item in the warehouse
    for (project in store.projects) {
        async {
            // The slashes used in the command are either double slashes \\ or backslashes /
            gitClone("${store.host}/${project.url}", project.branch,
                "${project.saveDir.replace("\ \"."/")}/${project.rename}")}}}Copy the code
  1. Now that the feature is written, when will it be implemented? My idea is to provide a Task and wait for the developer to click on it, or to enter a list of commands to execute it. Then you need to define a Task for the Gradle that currently introduces the plugin

    The Apply () method takes the Project object and creates a task directly in the Tasks list

override fun apply(target: Project) {
    // Configure git extensions
    target.extensions.create("gitClone", GitScope::class.java)
    // Define the gitClone task
    target.tasks.create("gitClone").apply {
        // Define grouping, easy to find
        group = "git"
        // Describe the functionality appropriately
        description = "For managing multiple warehouses initializing, switching branches"
        // Execute our function after the task is executed
        doLast(this@GitPlugin::gitCloneTask)
    }
}
Copy the code

By default, tasks are run in the configuration phase, i.e. “clean”, “sync gradle” tasks are executed, to avoid triggering each time, we write the code to run separately

  1. The function of the plug-in has been written, how to use other modules?implementationWell, of course not. Plugins have plugin-dependent ways, which we’ve all seen inplugins{}But how? This requires us to make a registration declaration for the plug-in we write

META-INF/gradle-plugin

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

  1. Now that our plugin is ready, how do we import and configure it? Open any onebuild.gradleI picked it hereapp/build.gradle

Since our plugin is written in Kotlin, it contains some Kotlin features that I don’t know how to use in Groovy, so I changed build.gradle. KTS

//app/build.gradle.kts
plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
    // Add a plug-in
    id("GitPlugin")}/ /... Omit a large chunk of code

// when KTS calls the class, it needs to guide the package
import com.chenchen.plugin.git.*
// This is the extension method that we provide in the plugin, and the specific way to use it is as follows
gitClone {
    // Many projects have more than one remote repository. Here you can set the domain name of the repository
    gitStore("https://gitee.com") {
        // Configure the items in the repository
        gitProject(
            // Project link
            url = "jaso_chen.com/camera-study".// Change the project branch
            branch = "master".// Save path
            saveDir = buildDir.absolutePath + "/testDir".// Whether to rename the folder
            rename = "CameraStudy")
        gitProject(
            url = "jaso_chen.com/camera-study",
            branch = "testBranch",
            saveDir = buildDir.absolutePath + "/testDir",
            rename = "CameraStudyTest")}}Copy the code
  1. After you have written the configuration, clicksync project with gradle filesSync it up and we’ll be on the rightgradleI found it on the task list. I was inapp/build.gradle.ktsIntroducing plug-ins. That’s inappYou can find it in the modulegit/gitCloneThis task

This is the end of the custom plug-in approach. This step is quite elementary, there is still a lot to improve, but the beginner is enough, it seems very simple, in fact, I spent more than 20 hours to complete, encountered various compilation failed, dependency problems, Gradle error can not understand, and so on

In a word, Gradle is very complex, but also very useful, learn a little fur can save a lot of time, more fish is not good!!

The above features are not satisfactory:

  • Can’t giveSettings.gradleDependencies, I want to automatically help these libraries while pulling codeincludeGet in there. Take a long time. Feel like I can’t do it.
  • Can only givebuild.gradleThis level of imported plug-ins, although not much configuration content, is too bloated to be placed in a separate file to configure

If there is a big boss understand, or later strength rose, there is a solution to continue to improve.

Project Address:

Gitlab.com/c297131019/…