How to programmatically apply and configure a plugin and keep properties set in the plugin extension

I’m applying and configuring the springdoc-openapi plugin programmatically and set some of its extension properties. This works as expected

        val openApi = target.extensions.getByType(OpenApiExtension::class.java)
        with(openApi) {
            outputDir.set(target.layout.buildDirectory.dir("generated/resources/openapi"))
            outputFileName.set("file.json")
            apiDocsUrl.set("http://localhost:8080/v3/api-docs")
        }

However I still wanted that it is possible that the buildscript could contain the extension closure to override any of those properties set by the above code.

openApi {
    outputDir=buildDir
}

Obviously my code could just do a if statement per property to check if it already has a value and only initialize it when it is not set.
But for a large set of properties this would be just ugly code. As I would expect that this is a common issue I was wondering if Gradle had a model to deal with that.

Thanks.

I’m not sure where your problem is.
If you apply the plugin and then configure the extension in your convention plugin,
then this should happen before the consumer build script of your convention plugin is evaluated,
so the consumer build script settings should win over yours as they are done after yours.

I was expecting that as you are explaining and had that with other plugins working properly. But it does not in this case. The springdoc-openapi plugin is performing all of its setup in an afterEvaluate block.

So my initialization also had to be moved to an afterEvaluate block. Since this is a contribution to an open source project on GitHub, I can actually provide a link autoconfigure-gradle-plugin/SpringDocOpenApiConfigurePlugin.kt at feature/issue-157 · MartinAhrer/autoconfigure-gradle-plugin · GitHub. Should have done that in first place.

Yeah, well, if you use afterEvaluate it is obvious why it doesn’t work. That’s one of the main effects of afterEvaluate. That it introduces timing and order problems. That’s why it should be avoided as hell wherever possible. So properly use the lazy APIs, wiring properties together and transforming properties, instead of eagerly querying them, then you should not need to use the bloody afterEvaluate and everything should start working properly.

Well in this case nothing that I’m in charge of and can change. It’s the springdoc-openapi plugin playing that game. I can only ask why they decided to do so and if there is no good reason to ask to change that.
I’m forced to use afterEvaluate otherwise non of the lazy task references worked.

Yes, that’s the big drawback of afterEvaluate.
I once even had to do an afterEvaluate from within an afterEvaluate to delay far enough to trick some other afterEvaluate.

1 Like

For now the solution is the following as initially anticipated. It works but is not the best

           // these isPresent checks are required due to afterEvaluate usage in springdoc-openapi plugin.
            if (!outputDir.isPresent) {
                outputDir.set(target.layout.buildDirectory.dir("generated/resources/openapi"))
            }
            if (!outputFileName.isPresent) {
                outputFileName.set("${basename}.json")
            }
            if (!apiDocsUrl.isPresent) {
                apiDocsUrl.set("http://localhost:${serverPort}/v3/api-docs")
            }

Not sure how that should help.
If it is done before the afterEvaluate { ... } was evaluated, the isPresent checks will be false unless somewhere else is a value set too and the afterEvaluate { ... } will overwrite it.
If it is done after the afterEvaluate { ... } was eavaluated, the isPresent checks will be true and you don’t change the config.

Hmmm… it seems to work… any catch though?

Well, from what you showed, what I described. :slight_smile:
But anyway, whatever works for you is probably fine for you. :ok_hand:

I’m trying to contribute to the springdoc-openapi plugin to get rid of the afterEvaluate thing. So far my convention plugin works and hope I don’t run into the problems you see. Though Junit plugins tests fail with multi module projects (strangely a single module project just works) and I suspect that it’s a afterEvaluate effect.

1 Like

:bowing_man: I managed to break it as you had explained. So yes it seems I was just lucky that it worked by chance. You have been totally right.

2 Likes