Convention plugin apply false not working?

Hello All,
I’m working with gradle 7.5.1 and kotlin dsl and have this issue:

I would like to create a convention plugin which doesn’t apply the specified plugin:

src/main/kotlin/my-conventions.gradle.kts:

plugins {
    id ("myPluginWhichMustHaveAConfigurationPropertySetIfApplied") apply false
}

when trying to consume the convention-plugin:

plugins {
    id("my-conventions")
}
 An exception occurred applying plugin request [id:my-conventions '']
...
Could not get unknown property ... -> the must have configuration property

what went wrong?
Tom

P.S. You need a sample project, because this is not reproducible ? Give me a hint and I’ll create something in my github accout,

No need to provide an MCVE here.
What you hit is that it should imho actually fail if you try to do that from a precompiled script plugin: Plugins block apply plugins automatically despite "apply false" for precompiled script plugins · Issue #14437 · gradle/gradle · GitHub
The apply false is simply ignored and actually it does not make any sense.
If you write apply false the intention is to add this plugin to the classpath of the script.
But with precompiled script plugins, you define the dependencies - including plugins you want to use - as dependencies in the build script of the project building the plugin, so the plugin already is in the classpath of that script.

Hi Björn,
Thanks for the detailed answer. In fact, I’ve copied and modified the code from
https://docs.gradle.org/current/samples/sample_sharing_convention_plugins_with_build_logic.html#header
for my purposes. So this question is a duplicate of

Maybe a bit “out of topic” here now, but the name “convention plugin” associates for me something like a pattern, which makes it possible to provide a central bundle of plugins for our developers that guarantees that the plugins they apply (from this bundle) are compatible to the gradle version they use for the current project.
That doesn’t seem to work in this case, since most of the plugins we use in our project are only applied in (some) subprojects.
What do you think the appropriate pattern for this task?
Kind regards Tom

Not sure what you mean.
A convention plugin establishes your own conventions like plugins to apply and settings for those plugins.
So yes, “to provide a central bundle of plugins for our developers that guarantees that the plugins they apply (from this bundle) are compatible to the gradle version they use for the current project” should be covered by that.

But I don’t know what you mean with “That doesn’t seem to work in this case, since most of the plugins we use in our project are only applied in (some) subprojects.”.
The convention plugin still defines the plugin version to use.
Just not inline in the precompiled script plugin, but in the declared dependencies of your precompiled script plugin that you define in the build script of the project building the convention plugins, not in the project applying the convention plugins.
Whether you apply the convention plugin in a root project or subproject does not matter for that.
You apply a convention plugin to the project where those convention should be applied, so you might for example have a (sub)project that builds a java library and you apply a convention plugin to it that in turn applies the java-library plugin and some other plugins you want applied in all Java library plugins and does configuration for it.

Hi, Björn, hi all!

Thank you for your detailed answer:
I try to give a summary, as far I’ve understood this issue:

A convention plugin fulfills the task of combining different plugins that are required by the developer.
Versions can be defined and we can pre-initialize project specific values.

A convention plugin has 2 flavors:
Precompiled script convention plugins
Standalone convention plugins

Precompiled script convention plugins are placed in the projects itself. They are wrapping (various) plugins that are used in the context of the project and its sub-projects. The fact that the wrapped plugins are automatically applied in the plugins closure block is useful, since these plugins are directly choosen to work in the project context and any initializations that may have been specified for this specific project anyway.

But what we need is a convention plugin that is stored in a central location of our infrastructure and can be used by all teams. We don’t know which of the “wrapped” plugins will be used, by the varous projects. The only think we need is a kind of contract for the developer: If you use the convention plugin you can be sure, that all offered plugins you apply are working.

I suppose that a standalone convention plugin can be the solution for that.
Based on a Udemy course by @jendrik , I created such a plugin, which you can find here:

In order to use it you must must do a publishToMavenLocal to get a defined version Id for this plugin.

The consumerGroovy works just as I expected.
But there is a race condition in consumerKotlin. The dependencies closure is evaluated before the mcve-java-plugin-kotlin has been evaluated. The consumerKotlin is exiting with an error.

Now I’m totally confused:

Did I encounter a bug?
But where is the bug? Is it possible that the groovyConsumer just works accidentally?
Is something wrong with my code example?

Thank you for hints
Tom

A convention plugin has 2 flavors:
Precompiled script convention plugins
Standalone convention plugins

Not really.
A convention plugin just means a plugin with your own conventions that you apply where you want those conventions to be used, no matter where it is implemented, be it standalone published, or in a composite build, or in buildSrc, or directly in the build, and no matter how it is implemented, be it a script plugin, a precompiled script plugin, a binary plugin written in Java, or Kotlin, or Groovy, or Scala, or whatever other language able to compile to JVM bytecode.

Precompiled script convention plugins are placed in the projects itself.

They are never “in the projects themselves”, they are always in a separate build. Whether it is a buildSrc build, or a build included as composite build in the same repository as the project where it is used, or in a completely standalone build that you publish to use it in other builds is irrelevant.

But what we need is a convention plugin that is stored in a central location of our infrastructure and can be used by all teams.

As I said, you can still write the plugin as precompiled script plugin, or as normal binary plugin using and JVM language, and then for example publish the result to some repository to use it in the other projects.

We don’t know which of the “wrapped” plugins will be used, by the varous projects. The only think we need is a kind of contract for the developer: If you use the convention plugin you can be sure, that all offered plugins you apply are working.

Well, you can write the convention plugins in a way that they react to which plugins are applied and do not apply plugins themselves. That’s what you do with pluginManager.withPlugin("...") { ... } to just react to a plugin being applied and configure it according to your conventions.

Or you can have multiple convention plugins, for example one per “wrapped” plugin and the project applying your convention plugins can choose which ones to apply.

If your main concern is to streamline the used versions, you can also write a central version catalog and / or platform. Both can be published too and then used by other builds.

In order to use it you must must do a publishToMavenLocal to get a defined version Id for this plugin.

Why publish to maven local and not use it also via composite build like the other?
Btw. using mavenLocal is quite problematic and should be avoided wherever possible and usually if really needed it is better to use some dedicated file-based repository or at least defined using repository filter exactly which dependencies should be taken from mavenLocal.

And why do you apply one of the plugins properly using the plugins { ... } block and the other using the legacy apply plugin: (resp. apply(plugin =) that you should practically never use?

But there is a race condition in consumerKotlin. The dependencies closure is evaluated before the mcve-java-plugin-kotlin has been evaluated. The consumerKotlin is exiting with an error.

That’s not correct.
Your plugin is evaluated first.
I didn’t execute and you didn’t write the error, but I guess the error is, that it does not find implementation.
That is because you do use the legacy approach using apply(plugin = as mentioned above.
For plugins applied that way no type-safe accessors for the Kotlin DSL are generated. Those only exist for plugins properly applied using the plugins { ... } DSL.

Is it possible that the groovyConsumer just works accidentally?

Groovy is an extremely dynamic language with duck-typing.
At runtime the plugin is applied and thus the implementation call can be found and thus succeeds.
Kotlin is not runtime-dynamic like that, but is type-safe, so needs the proper accessors generated, which is not done in your case as explained above, so the compilation already fails.

Btw. you should practically never use the all distribution, but always bin.
There is exactly one situation where the all distribution is useful.
That is when you use the Groovy DSL (which I advise against anyway) and even then only while editing the build scripts to work-around an IDE shortcoming.
Other than that, the all distribution just wastes time, bandwidth and diskspace for anyone and anything just executing the build.