Configure applied plugins inside a custom plugin

Greetings!

I am trying to simplify the Gradle build files of a Spring Boot Kotlin multi-project build. To achieve this, I created a custom Gradle plugin in buildSrc:

class MySubprojectPlugin : Plugin<Project> {

    override fun apply(project: Project) {
        with(project) {
            pluginManager.apply {
                apply("org.jetbrains.kotlin.jvm")
                apply("org.jetbrains.kotlin.plugin.spring")
                apply("org.springframework.boot")
                apply("io.spring.dependency-management")
            }
            group = "com.example"

            repositories {
                mavenCentral()
            }
            dependencies.apply {
                add("implementation", kotlin("stdlib-jdk8"))
                add("implementation", kotlin("reflect"))
                add("testImplementation", "org.springframework.boot:spring-boot-starter-test")
            }
            tasks.withType<Test> {
                useJUnitPlatform()
            }
        }
    }
}

And then this plugin is applied in subprojects :

plugins { `my-subproject` }

version = "0.1.0-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_11

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
}

dependencyManagement {
    imports {
        mavenBom("org.springframework.cloud:spring-cloud-dependencies:${property("versionSpringCloudBom")}")
    }
}

tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
    kotlinOptions {
        freeCompilerArgs = listOf("-Xjsr305=strict")
        jvmTarget = "${java.sourceCompatibility}"
    }
}

This approach works fine, however, I would like to further simplify the latter to this:

plugins { `my-subproject` }

version = "0.1.0-SNAPSHOT"

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
}

The problem is I cannot find a way to access the symbols java.sourceCompatibility, dependencyManagement, and KotlinCompile within apply(Project), since they come from the plugins listed in the pluginManager block. Does such way actually exists? If not, is there a better approach than a plugin to factorize build files ?

Thank you for any kind of help you will be able to provide me with. I sincerely appreciate it.

Note: I think this might be possible when using Groovy as seen in this project, but not sure for Kotlin since Project does not define a dependencyManagement property.

If you use a precompiled script plugin instead of a normal plugin and there apply the plugins using the plugins block, then the type-safe accessors are generated and can be used just like in the normal build files.

If you prefer to use a normal plugin for this, you have to use the more verbose approach like configure<JavaPluginConvention> { ... }.

1 Like

Thank you for your answer! Precompiled script plugins is definitely the concept I was looking for. I successfully managed to create a subproject.gradle.kts in buildSrc/src/kotlin/ and apply it via plugin { subproject }.

As a side note, I’d like to mention that I have found a solution using the “normal” plugin I initially created, but it involves rewriting it in Groovy (all the rest stays in Kotlin).
Indeed, from my understanding, Groovy enables us to call members that are not yet defined. Moreover, unlike Kotlin, there is no syntax drift between the body of apply() and a regular Groovy build script (e.g., see the dependencies block).

I would love to use the precompiled script plugins approach, however there’s a catch. Unlike with the “normal” plugin approach, buildSrc is unable to utilize the plugins with apply false in the root build.gradle.kts. I had to manually declare the implementation dependencies AND their versions again:

dependencies {
    implementation("org.jetbrains.kotlin.jvm:org.jetbrains.kotlin.jvm.gradle.plugin:1.4.31")
    implementation("org.jetbrains.kotlin.plugin.spring:org.jetbrains.kotlin.plugin.spring.gradle.plugin:1.4.31")
    implementation("org.springframework.boot:spring-boot-gradle-plugin:2.4.3")
    implementation("io.spring.gradle:dependency-management-plugin:1.0.11.RELEASE")
}

That is a bit cumbersome… Is there a way to use the plugins declared in the root build script, as with the “normal” plugin approach? (Note: the latter still uses apply and not the plugins DSL).

Thank you again for your precious help.

Just throw out the plugins in the root build script?
If they are in the buildSrc class path they are also in your projects build script class path already and that is also everyhting that it did with apply false.

You cannot “reuse” the plugins declared in the root build script because of hen and egg problem.
buildSrc is a full stand-alone multi-project Gradle build, that is just detected by naming convention and run before your build, with its output added to the class path of your main build scripts. But it is still a full stand-alone build that has to work independently so you need to declare its dependencies that you want to use.

A normal plugin can also not reuse anything from the main build scripts. If you apply a plugin by ID using the apply method instead of the plugins block, it will probably work, but as soon as you want to configure a plugin, for example with configure<JavaPluginConvention> { ... } you again need the classes in your class path unless you use Groovy with its duck-typing.

1 Like