简体   繁体   中英

How to Run Custom Gradle Task In Gradle Plugin During Android Project Configuration Phase?

Okay, So I have been pulling my hair out trying to get this to work properly and I just want to write my tests and push this to maven already so my standup updates are not the same day after day.

Problem Statement: I have multiple android projects that all require the same pre-configuration. This includes declaring maven repositories, setting versionCodes and versionNames, and other misc configuration items. The same blocks are copy and pasted in every project or placed in a local common.gradle file and then applied and called at the project level build.gradle configuration stage.

My Solution: Extract all of this common logic into a standalone gradle plugin and let each projects build.gradle file apply this plugin to receive these configuration events.

I created a standalone plugin, did an override on plugin and it looks something like this:

class CommonManager: Plugin<Project> {
    override fun apply(target: Project) {
        println("APPLY FUNCTION HAS BEEN ENTERED")
        target.tasks.create("myPluginTask", ReleaseManager::class.java)
    }

The ReleaseManager class is a class that extends DefaultTask and creates a task to run. This is what is recommended in the gradle docs and it makes sense from a testability / reusability standpoint and that class looks something like this:

abstract class ReleaseManager: DefaultTask() {
    @get:Input
    abstract val versionHistoryPath: Property<String>

    @TaskAction
    fun configureVersionsAndReleaseMeta() { // do work... }

According to the docs I have read and other example projects I have thumbed through, this is set up correctly. I built the plugin using java-library instead of java just to have a local .jar file to point to in my android projects for quick testing. An example of one of these build.gradle files looks like the following:

buildscript {

    repositories {
        google()
        mavenCentral()
        maven {
            url "https://plugins.gradle.org/m2/"
        }
    }

    dependencies {
        // misc dependencies ...
        classpath files("libs/MyCustomPlugin-1.0-SNAPSHOT.jar")
    }
}


apply plugin: "com.myplugin.common"

myPluginTask {
    versionHistoryPath.set("sdk/version_history.txt")
}

subprojects {
    apply plugin: 'maven-publish'
    apply plugin: 'com.jfrog.artifactory'
    group = 'org.jfrog.test.gradle.publish'
}

I create a dependency on the .jar and apply the plugin, then configure the task with the property it needs to run. However, the task never runs during the configuration phase when the log outputs > Configure project I see my print statement from the apply function in my plugin log but no output or configuration happens in the task class that I defined. If I click the play arrow next to the myPluginTaks{} configuration definition, it does in fact run fine. At this point, I know I'm pointing to the .jar correctly, I can access the task that I create in my plugin class, and I can set its property respectively. I'm newer to doing this kind of custom work with gradle and want to understand what I am doing wrong. Let me outline my thought process and debugging steps I have tried.

My "Bug" Statement: I want the task "myPluginTask" to execute after I apply my plugin in my build.gradle script and I have configured the task with the proper input. It is currently not executing the task.

Thoughts and notes:

  • I used create over register in the plugin apply function when creating my task. My understanding is that by using create, I am saying "hey I want to create a task, and I want the build to know about it immediately" rather than "Hey here's a task definition, but lets lazy load it once it is actually invoked on the implementation end"

  • I moved the implementation of ReleaseManager into the plugin class as individual functions and called these functions inside the task creation closure. That works. To my understanding this is because anything defined in there is run during a builds configuration phase. I can defer this to the execution phase with a doFirst or doLast closure if I wanted to.

  • My hunch is, I've created a task and sure the build now knows about it immediately, but it has no plans on executing the task until at least the execution phase and needs a trigger. Whether that trigger is a direct invocation of the task or the task depends on a task that is going to run in the execution phase.

Any help would be greatly appreciated. Happy to provide any additional information if needed.

A user below provided an insightful link that helped me arrive at a solution. I also have a much better understanding of Gradle at this point and can better articulate my problem.

TLDR; My hunch was correct. I had successfully created a task, but did not provide a way to trigger such task. You do this by depending on a task provided by the target project.

To reiterate, my plugin class looked like the following:

class CommonManager: Plugin<Project> {
    override fun apply(target: Project) {
        println("APPLY FUNCTION HAS BEEN ENTERED")
        target.tasks.create("myPluginTask", ReleaseManager::class.java)
    }

My module level build.gradle file applying the plugin had the following:

apply plugin: "com.myplugin.common"
apply plugin: "com.android.library"

The apply function would trigger in my plugin and the task would be created. It would NOT run however. To accomplish this, I needed to depend on another task in my target build as a trigger. That effectively looked like the following:

class CommonManager: Plugin<Project> {
    override fun apply(target: Project) {
        println("APPLY FUNCTION HAS BEEN ENTERED")
        target.tasks.create("myPluginTask", ReleaseManager::class.java) { task ->
            // Have target project create a dependency between its task and mine
            target.tasks.findByName("preBuild").dependsOn(task)
    }

"preBuild" is a task provided by the android plugin. In my case com.android.library this lead to issue two. When building my project, the plugin would throw an error stating that "preBuild" is not a defined task. At first I thought I just couldn't reference tasks provided by the android library but then it hit me. If you look at my above code block for my modules build.gradle file, you will notice I define my common plugin first. This means at the time of my plugins apply block firing, the plugin was unaware of this task because the android plugin had yet to be applied.

You could define the android plugin first and document that order of definition is important but, that is not a pragmatic solution. What you can do however, is implement the afterEvaluate closure on the target project like so:

class CommonManager: Plugin<Project> {
    override fun apply(target: Project) {
        println("APPLY FUNCTION HAS BEEN ENTERED")
        target.tasks.create("myPluginTask", ReleaseManager::class.java) { task ->
            target.afterEvaluate {
                // Have target project create a dependency between its task and mine
                target.tasks.findByName("preBuild").dependsOn(task)
            }
    }

This will wait for evaluation of the modules build.gradle file to finish. Therefore, once my plugins apply block is triggered, my plugin will know about the "preBuild" task because the android plugin was applied during project evaluation.

Bonus tip : I am using create to make my task in the plugin. If you are using register, you do not need the afterEvaluate closure. If you define one, the plugin will throw a runtime error if running gradle 7.0 or higher. The gradle community considers that a redundancy.

Am not quite sure that this will help you or not .

did you try to

myPluginTask.configure {
   mustRunAfter TASKNAME
}

I was looking around and this might be helpful as well .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM