kotlingradlegradle-plugincompile-time-weavingfreefair-aspectj

Is it possible to modify actual actions inside of configured gradle task using an outer plugin?


Origin problem that I'm trying to solve

I'm using io.freefair.aspectj.post-compile-weaving gradle plugin to apply some aspects to my classes. How this plugin works, firstly, it's runs trough the project and searching all Compile tasks, after these tasks was found it's call #enhanceWithWeavingAction method which puts an ajc action at the end of list actions in each task.

Since I'm include this plugin into every project, I have to put this configuration into each build.gradle.kts file

tasks.name1 {
    configure<AjcAction> {
        options {
            compilerArgs.add("-Xlint:ignore")
        }
    }
}

tasks.name2 {
    configure<AjcAction> {
        options {
            compilerArgs.add("-Xlint:ignore")
        }
    }
}

// and couple configurations of AjcAction below

So, to avoid copy pasting I decided to create a plugin, which will apply these options.

abstract class MyPlugin : Plugin<Project> {

    override fun apply(project: Project): Unit = with(project) {
        // applying original post-compile plugin
        pluginManager.apply(AspectJPostCompileWeavingPlugin::class.java)
        // some conditions
        afterEvaluate {
            tasks.named("name1").configure { configureCompile(it) }
            tasks.named("name2").configure { configureCompile(it) }
            tasks.named("name3").configure { configureCompile(it) }
        }
    }

    /**
     * TODO: find way to put modified actions into original tasks,
     *  for some reason they just will be ignored.
     */
    private fun Project.configureCompile(compileTask: Task) {
        val wrapperClass = Class.forName("org.gradle.api.internal.AbstractTask\$TaskActionWrapper")
        compileTask.apply {
            val ajcWrapper = wrapperClass.cast(actions.last())
            if (compileTask.name == "specieficTaskName") {
                actions.removeAt(actions.size - 1)
                return
            }
            val ajcActionField = ajcWrapper.javaClass.getDeclaredField("action")
            ajcActionField.trySetAccessible()
            val action = ajcActionField.get(ajcWrapper)
            if (action is AjcAction) {
                action.options.compilerArgs.add("-Xlint:ignore")
                ajcActionField.set(ajcWrapper, action)
            }
        }
    }
}

I've used this plugin to configure my other project and faced the problem

Problem of immutable list of actions

During debugging the gradle clean assemble I see, how actions was modified correctly:

first_debug – Everything okay second_debug – Everything okay

But, after these modifications was made, it's looks like gradle just ignores them and using original actions, why it's happen? How can I modify these actions? Or maybe there is another solution of original problem?


Solution

  • There are quite some problems with your plugin as you might have guessed already.

    Do not use afterEvaluate. That's practically never a solution, but duct tape. The main effect of afterEvaluate is to introduce timing problems, ordering problems, and race conditions. It is like using SwingUtilities.invokeLater or Platform.runLater to "fix" a GUI problem. It just does symptom treatment, delaying the problem to a later, harder to reproduce, harder to debug, and harder to fix point in time.

    I guess in your case the tasks name1, name2, and name3 are not yet registered when that plugin is applied and that is why you use it. But just use the reactive versions and you do not need afterEvaluate, so instead of tasks.named("name1").configure do tasks.named { it == "name1" }.configureEach. You can of course also right away test for all three names in the predicate.

    You are getting an internal Gradle class and fields of it by reflection. Multiple things that alone alreay cry loudly "bad practice, don't do that". Especially as it seems there is no reason at all to do that. Modifying the actions should work just fine if you really need to do it. But the documentation of that plugin shows that you can do tasks.compileJava { ajc { ... } } in Groovy or tasks.compileJava { configure<AjcAction> { ... } } in Kotlin to configure the action, which means it is added as extension to the task. So just do val ajcAction = compileTask.extensions.findByName("ajc") as AjcAction to get the action and then set enabled property for specieficTaskName and compilerArgs for the others without dirty tricks.