Florian Reisinger
Florian Reisinger

Reputation: 3088

How to extend the Gradle Kotlin DSL with custom extension functions?

I am trying to build my own extension function, which uses an extension method from C:/Users/<username>/.gradle/caches/7.3-20211011231002+0000/kotlin-dsl/accessors/5030f2917b46f065859e6c634449b97d/sources/org/gradle/kotlin/dsl/Accessors2oadk7let745pm8ahqypkqzlk.kt:63 - which seems to be something deeply internal.

In my buildSrc subproject I want to try to add the following extension function

val org.gradle.api.Project.testSources
    get() = sourceSets.getByName("test").output

Is there a way to resolve the extension functions defined in the Kotlin DSL (my overall goal is instead of writing project(":<name>").sourceSets["test"].output to write project(":<name>").testSources).

I do not really understand why all the extension methods are not available....

The build-gradle.kts of my buildSrc.

plugins {
    `kotlin-dsl`
}

Repo (this will not build as one file is missing, which I want to keep private, but that shouldn't matter for this issue)

Upvotes: 4

Views: 3771

Answers (1)

aSemy
aSemy

Reputation: 7139

You can share extension functions, or field extension, within a single project by simply defining them in .kt files in ./buildSrc/src/main/kotlin/. You can then import them into any of the project's build.gradle.kts files.

For example, if you create ./buildSrc/src/main/kotlin/my/project/MyExtensions.kt

  • Note that it's a .kt, not .kts file.
  • Also note that the package is set - often IntelliJ forgets to generate it when working in buildSrc!
//MyExtensions.kt
package my.project

val org.gradle.api.Project.testSources
    get() = sourceSets.getByName("test").output // compilation error...

But... that code won't work. Because it's in a .kt file, not a .kts, the Gradle magic won't load the Kotlin DSL sourceSets accessor.

For tips on how to access Gradle objects without the Kotlin DSL accessors, looking at the Gradle Plugin development guide is best https://docs.gradle.org/current/userguide/custom_plugins.html.

You can also cmd/ctrl + left click in the build.gradle.kts files to see what the Kotlin DSL accessor is...

// auto-generated Kotlin DSL accessor
/**
 * Retrieves the [sourceSets][org.gradle.api.tasks.SourceSetContainer] extension.
 */
val org.gradle.api.Project.`sourceSets`: org.gradle.api.tasks.SourceSetContainer get() =
    (this as org.gradle.api.plugins.ExtensionAware).extensions.getByName("sourceSets") as org.gradle.api.tasks.SourceSetContainer

The auto generated code is a good start.

There's a few ways of accessing Gradle objects. Personally I'd prefer to retrieve the SourceSetContainer by type, not by string. And so here's how to fix your field extension.

package my.project

import org.gradle.api.Project
import org.gradle.api.tasks.SourceSetContainer
import org.gradle.api.tasks.SourceSetOutput
import org.gradle.kotlin.dsl.findByType

val Project.testSources: SourceSetOutput?
    get() = extensions
        .findByType<SourceSetContainer>()
        ?.getByName("test")
        ?.output

Now in <root>/build.gradle.kts you can import the extension, and use it as expected.

//build.gradle.kts
import my.project.testSources

project.testSources?.files?.forEach { file: File? ->
    logger.lifecycle("my extension function found a testSources file $file")
}

Sharing functions with plugins - Extension Aware

You can also share functions with regular Gradle plugins. See https://docs.gradle.org/current/dsl/org.gradle.api.plugins.ExtensionAware.html. However I don't think defining extension functions is possible.

Upvotes: 8

Related Questions