Maksym Moroz
10/11/2025, 3:29 PMafterEvaluate but since this discussion about just how much of a legacy approach afterEvaluate is, I have been looking for a good solution for this.
Would BuildService with FinishEventListener that throws if plugins were not applied be an idiomatic approach?Martin
10/11/2025, 4:18 PMafterEvaluate {} for improved error messages.
In that other discussion there was another way to make it work without afterEvaluate {} by checking the classpath but if that's not possible, then afterEvaluate {} is fine by meVampire
10/11/2025, 8:49 PMMaksym Moroz
10/12/2025, 9:19 AMandroid {} extension from the plugin that isn't even in plugins {} block. I don't like it
I would say I would only consider to apply plugins inside my convention one, if my convention plugin would expose its own extension to wrap things that should be customisable and that's is hard to account for without passing around a bunch of properties that in the end will never be used
This is obviously just how I see things but I really prefer the explicitness and the idea that the convention plugin is just a useful extension not replacementVampire
10/12/2025, 12:21 PMThe problem I have with convention plugins applying plugin to configure under the hood is that it's not transparent that happens
I don't see how it is not transparent. If I apply the
my.android.library convention plugin I would even expect that it also applies the plugin for me. Otherwise I also always have to apply two plugins when one would be sufficient.
Also, you asked for the idiomatic approach. And the idiomatic approach is, if your plugin needs another plugin it should apply it.
And what bugs me specifically: let's say I make a common android library configuration convention plugin so this plugin applies android library plugin under the hood, now that the client engineer wants to customize some part of the android configuration they should go and refer to android {} extension from the plugin that isn't even in plugins {} block. I don't like it
But this is just normal and idiomatic. You also configure
base { archiveName } without ever applying the base plugin explicitly usually, or publishing { publications { ... } } when never explicitly applying the publishing plugin but only maven-publish or ivy-publish.
Besides that, if your client engineer does not like that, noone stops them to apply the plugin also explicitly. Applying a plugin a second time is just a no-op to support exactly this idiomatic setup.
I really prefer the explicitness and the idea that the convention plugin is just a useful extension not replacement
It is just a useful extension that also applies the plugin for you, saving another line to copy around. And well, you asked for the idiomatic approach. :-) Of course you don't have to follow the idiomatic approach, but then you should not wonder if you need to jump some hoops. :-) Despite what Martin said, I think
afterEvaluate should be avoided under almost any cost. Each usage brings timing problems, ordering problems, and race conditions. If for example someone applies the plugin in an afterEvaluate block after you registered your afterEvaluate block, your check will complain.
A build service with an operation completion listener could probably work, but I guess simpler would be to register a task that fails the build with a message and then have a pluginManager.withPlugin("the-expected-plugin") that sets the task to enabled = false and a tasks.matching { it.name != "theFailTask" }.configureEach where you depend on the task.Vampire
10/12/2025, 1:05 PMafterEvaluate {
println(pluginManager.hasPlugin("base"))
}
afterEvaluate {
apply(plugin = "base")
}
afterEvaluate {
println(pluginManager.hasPlugin("base"))
}
and you will see that it prints first false, then true to see what I mean. 🙂