Assign closure to variable in Kotlin DSL

Dear guru,

I have the following code in Groovy DSL. The idea is to save repositories configuration in variable and than use it later in several places. Could you please tell me how to do that in Kotlin?

def repos = {
	mavenLocal()

	maven {
		url "..."
	}
}

pluginManagement {
    repositories(repos)
    repositories {
        gradlePluginPortal()
    }
}

repositories(repos)

Thanks in advance,
Vadim

Technically, this is a Groovy Closure:

def repos = {
	mavenLocal()

	maven {
		url "..."
	}
}

which can be written in Kotlin as

val repos = closureOf<RepositoryHandler> {
    mavenLocal()
    maven(url = "...")
}

However, this won’t give you exactly what you want because the repositories method in Project takes a Closure<RepositoryHandler>, but the repositories method in Settings.pluginManagement takes an Action<RepositoryHandler>. In Groovy, a Closure works for both, but in Kotlin, the strong typing means you can’t pass in the exact same value as is. You’d have to do some conversion.

Is it possible to convert Closure to Action object manually in Kotlin script?

Don’t use a Closure in Kotlin code unless you are forced to, for example by some plugin that depends on getting a Closure as parameter.
But for Gradle built-in things this should not be the case.

Settings.pluginManagement.repositories takes an Action<? super RepositoryHandler>.
Project.repositories takes a Closure, but
ProjectExtensions.kt of the kotlin-dsl classes defines an exstension function Project.repositories that takes a RepositoryHandler.() -> Unit.

And RepositoryHandler.() -> Unit does work as Action<? super RepositoryHandler>.

So what you want is

val repos: RepositoryHandler.() -> Unit = {
    mavenLocal()
    maven("...")
}

But be aware that pluginManagement block is special and executed before the rest of the script, so that settings plugins for the same settings script are resolved from the repositories and with the rules defined in the pluginManagement block, so you can for example not do something like this as then repo is not found:

val repos: RepositoryHandler.() -> Unit = {
}

pluginManagement {
    repositories(repos)
}

What you can do is for example

pluginManagement {
    val repos: RepositoryHandler.() -> Unit = {
    }

    repositories(repos)
    dependencyResolutionManagement.repositories(repos)
}

as the pluginManagement block is evaluated first separately, but can still configure the whole settings object, it is more like a beforeEvaluate with a different name and special methods additional that are only available inside.

Or you could do it like

pluginManagement {
    var repos: RepositoryHandler.() -> Unit by extra
    repos = {
    }

    repositories(repos)
}

dependencyResolutionManagement {
    val repos: RepositoryHandler.() -> Unit by settings
    repositories(repos)
}
1 Like

@Vampire, thank you a lot for detailed explanation! It’s pretty clear now.

One more questions please. Documentation says that it is possible execute several init scripts but it’s not quite clear whether is it possible to mix Groovy and Kotlin scripts? Say, if USER_HOME/.gradle/ contains both init.gradle and init.gradle.kts which one will be executed?

My guess would have been both, but I tried as you could have done too and it seems only the Groovy one is executed. If you put them (or one of them) into init.d both get executed. But I think this is more a bug than a feature, I reported it at init.gradle and init.gradle.kts are not executed both · Issue #16337 · gradle/gradle · GitHub.

@Vampire, thank you a lot for you help! Could you please also advice whether is it possible to share variables between different init scripts, especially if they are written in different languages. Say, I have init.gradle and init.gradle.kts in init.d. Which one will be executed first? May I define variable or property in one script and use it in another?

The order is defined in the docs.
Iirc it is lexicographically within a folder.
If you want to share data between the init scripts you probably have to use the ext properties on the Gradle object.

Looks like Gradle object doesn’t implement ExtensionAware so can not have ext properties … Am I right?

You are not. :slight_smile:
It does not declare it implements it in the code.
But like all instances that get decorated by Gradle through implicit instantiation, the extension awareness is added dynamically at runtime.

Well, could you please advice how to declare and consume variables in Gradle context. I define variable in Groovy script:
ext { inhouseUrl = "..." }
than try to use in Kotlin one:
val inhouseUrl: String by Gradle.extra
and got an error:
Unresolved reference: extra

$ find init.gradle init.gradle.kts | xargs -I[] sh -c 'echo; echo []; cat []'

init.gradle
println "init.gradle"
ext.inhouseUrl = "..."

init.gradle.kts
println("init.gradle.kts")
val inhouseUrl: String by (gradle as ExtensionAware).extra
println("inhouseUrl = ${inhouseUrl}")
$ ./gradlew -I init.gradle -I init.gradle.kts help
init.gradle
init.gradle.kts
inhouseUrl = ...