Gradle custom extension with lazy configuration doesn't work

Using afterEvaluate is almost always the wrong thing to do. In 98.7 % of the cases it is just symptom treatment like calling SwingUtilities.invokeLater or Platform.runLater to “fix” a GUI problem. It usually just shifts the problem to a later time where it is much harder to reproduce, much harder to debug, and much harder to fix. The main thing afterEvaluate does is introducing timing problems, ordering problems, and race conditions. That’s why the Property / Provider APIs were introduced, so that properties can lazily be wired together without the need to do delayed evaluation to give someone the chance to configure the value before you read it. What happens for example if someone sets the value in an afterEvaluate that is evaluated after your afterEvaluate? …

Your conclusions were right though.
There are a couple of ways to do it properly.
You could for example make the consumer not set this property but instead just apply the pure checkstyle plugin, then in your plugin you can have project.pluginManager.withPlugin("checkstyle") { /* configure checkstyle */.
Or you can have multiple convention plugins and one of them if applied, applies and configures the checkstyle plugin without condition, as the condition is that the consumer applies it.
Or you can replace the addCheckStyle property by an addCheckStyle function that is then called by the consumer and do the checkstyle configuration within that function.


Btw. your extension can be much simpler unless you need to support ancient Gradle versions:

abstract class CodeStyleExtension {
    abstract val addCheckStyle: Property<Boolean>
    init {
        addCheckStyle.convention(false)
    }
}

Or if you follow good practices and configure defaults and wirings in the plugin, you can even have

interface CodeStyleExtension {
    val addCheckStyle: Property<Boolean>
}

and then

project.extensions.create(
    "myplugin",
    CodeStyleExtension::class.java
).apply {
    addCheckStyle.convention(false)
}

to create it.

Also, extensions.create already returns the extension instance to you, so you don’t have to request the extension by type, unless you oversimplified the example.

And, Propertys should always be val, not var.
Their value might change, but the instance should never change.
And using val also enables the assignment plugin in latest Gradle versions where you can use addCheckStyle = true also in Kotlin DSL build scripts.

1 Like