How to configure a settings plugin

TL;DR:

A project plugin can be configured by the config block introduced by creating an extension for it (project.extensions.create(...)). How can this be done in a “Settings” plugin?

Long story:

I have a Settings plugin, that should apply a different “Project” plugin, if configured to do so. To do that I have the following code:

MySettingsPluginExtension.kt

open class MySettingsPluginExtension
@Inject constructor(
	private val settings: Settings,
	objects: ObjectFactory
) {

	val applyProjectPlugin: Property<Boolean> =
		objects.property<Boolean>()
			.convention(
				settings.providers.provider {
					return@provider settings.extensions.extraProperties.let {
						if (it.has("mySettings.applyProjectPlugin")) it.get("mySettings.applyProjectPlugin").toString().toBoolean() else true
					}
				}
			)
}

MySettingsPlugin.kt

open class MySettingsPlugin : Plugin<Settings> {

	override fun apply(settings: Settings) {
		val mySettingsPluginExtension = settings.extensions.create("mySettings", MySettingsPluginExtension::class.java, settings)

		// automatically apply the project plugin in the root project, if configured
		if (mySettingsPluginExtension.applyProjectPlugin.get()) {
			settings.gradle.rootProject {
				it.plugins.apply(MyProjectPlugin::class.java)
			}
		}
	}
}

My first assumption was, that I just get a block mySettings { ... } in my settings.gradle.kt where I could do applyProjectPlugin.set(false) to disable it. That does not exist though.

It works to set mySettings.applyProjectPlugin=false in my gradle.properties file.

I found two ways of having some way to configure my plugin, but both don’t seem to work:

I added the following block to my Settings Plugin:

fun Settings.mySettings(action: Action<MySettingsPluginExtension>): Unit =
	(this as ExtensionAware).extensions.configure("mySettings", action)

Now I can import this method in my settings.gradle.kt:

import com.example.gradle.plugins.settings.mySettings
...
mySettings {
    applyProjectPlugin.set(false)
}

But changing values there does not seem to have any effect.

The same goes for the following block in the project I applied the Settings plugin to:

configure<MySettingsPluginExtension> {
    applyProjectPlugin.set(false)
}

After both of them I would expect, that the project plugin is not applied anymore. But it still is.

So my question: How do I configure my Settings plugin from my actual project.

Thanks in advance!

As you found out already, there are unfortunately no type-safe accessors generated for Kotlin DSL settings scripts. And you already found 2 of the 2.5 ways to configure your extension, either by providing the type-safe accessor yourself and importing it, or by configuring the extension by type. A variation would be to get the extension by type and name like extensions.getByName<MySettingsPluginExtension>("mySettings").apply { ... }.

The .5 would be to have the accessor in the package org.gradle.kotlin.dsl, then it would be available without import, but imho that is not really a clean way, just mentioning it for completeness.

That your configuration does not have an effect is exactly the same problem you would have in a project plugin with the code you used. You create the extension and then immediately check its values before the settings script had a chance to configure it. So you first evaluate the value and react to it and after that configure the value.

So either defer the evaluation of the extension e.g. like with

settings.gradle.rootProject {
    if (mySettingsPluginExtension.applyProjectPlugin.get()) {
        it.apply(MyProjectPlugin::class.java)
    }
}

or have a method in the extension that does the action instead of having a property, something like

fun applyProjectPlugin() = settings.gradle.rootProject {
    it.apply(MyProjectPlugin::class.java)
}

in the extension that you then call from your settings script.

Sidenote: if you look at the JavaDoc of Project.getPlugins() you learn that you should not use .plugins, that’s why it is missing from my snippets. :wink:

Thanks! I assumed it had something to do with “the plugin is applied, but the config is not evaluated yet”. But I could not find any examples on how to do it properly!

Doing this does not work, unfortunately:

settings.gradle.rootProject {
	if (cosmoSettingsPluginExtension.applyProjectPlugin.get()) {
		it.apply(MyProjectPlugin::class.java)
	}
}

It fails with

None of the following functions can be called with the arguments supplied.
apply((Closure<Any!>…Closure<*>?)) defined in org.gradle.api.Project
apply((Mutable)Map<String!, *>!) defined in org.gradle.api.Project
apply(Action<in ObjectConfigurationAction!>!) defined in org.gradle.api.Project

I use this settings plugin also to setup my repositories. And I add a parameter to the extension for the repositoriesMode the same way I do it for applying the project plugin. I then do the following in my apply method:

	override fun apply(settings: Settings) {
		val mySettingsPluginExtension = settings.extensions.create("mySettings", MySettingsPluginExtension::class.java, settings)

		settings.dependencyResolutionManagement.apply {
			repositories....
			repositoriesMode.set(mySettingsExtension.repositoriesMode)
		}
	}

Can you tell me why this is working then? Is it because I do not actually evaluate the value of mySettingsExtension.repositoriesMode, but pass on the Property object?

Thanks!

None of the following functions can be called with the arguments supplied.

If it.plugins.apply ... worked, it.apply ... should have too.
I wrote that from the top of my head, adapting your example.
Actually in Kotlin DSL it should be this, not it, so

settings.gradle.rootProject {
	if (cosmoSettingsPluginExtension.applyProjectPlugin.get()) {
		apply(CosmoProjectPlugin::class.java)
	}
}

Can you tell me why this is working then? Is it because I do not actually evaluate the value of mySettingsExtension.repositoriesMode , but pass on the Property object?

Exactly.
That’s exactly why the lazy Property / Provider API was invented.
To be able to wire properties together and evaluate them as late as possible, optimally when configuration was finished completely.
With your boolean property you call .get() and thus immediately evaluate its value.

The it in the context is the project object.

Doing a plain apply(MyProjectPlugin::class.java) fails as well. It’s trying to recursively calling the apply method of this Plugin.

Ah, sorry.
Of course it.apply<MyProjectPlugin>() in Kotlin DSL then. :slight_smile:

Another solution seems to be:

settings.gradle.rootProject { project ->
	if (mySettingsPluginExtension.applyProjectPlugin.get()) {
		project.pluginManager.apply(MyProjectPlugin::class.java)
	}
}

In fact the only solution. it.apply<MyProjectPlugin>() fails with the same error as above :slight_smile:

I found the docu here: PluginAware - Gradle DSL Version 8.1.1

Thanks for your help!

Which “same error” are you referring to?

It results in the same error as this.

The documentation for plugins says:

The container of plugins that have been applied to this object.

While not deprecated, it is preferred to use the methods of this interface or the PluginAware.getPluginManager() than use the plugin container.

Use one of the ‘apply’ methods on this interface or on the PluginAware.getPluginManager() to apply plugins instead of applying via the plugin container.

https://docs.gradle.org/current/dsl/org.gradle.api.plugins.PluginAware.html#org.gradle.api.plugins.PluginAware:plugins

What seems to work is

settings.gradle.rootProject { project ->
	project.apply(mapOf("plugin" to MyProjectPlugin::class))
}

For it.apply<MyProjectPlugin>() you need to import org.gradle.kotlin.dsl.apply.