Skip to Content
Course content

159: Gradle Kotlin DSL Convention Plugins

Click on the "Edit" button in the top corner of the screen to edit your slide content.

By the time you're managing a project with ten or twenty modules, you'll notice a frustrating pattern: your build.gradle.kts files start looking identical. You're copy-pasting the same JVM target, the same compiler options, and the same set of testing libraries into every single module. It's a maintenance nightmare. If you want to bump the Java version from 17 to 21, you're doing a find-and-replace across twenty files. This is where Convention Plugins come in.

Why can't I just use a shared script file?

You might have seen people using apply(from = "common.gradle.kts"). Honestly? Avoid it. That's the old way of doing things. The problem with shared scripts is that they lack proper type safety and IDE support. When you use a Convention Plugin, you're writing actual Kotlin code that is compiled. This means you get autocomplete, refactoring tools, and compile-time checks for your build logic. I've spent way too many hours debugging a typo in a shared script only to realize it didn't fail until the actual execution phase. Convention plugins kill that problem.

Where does the code for these plugins actually live?

The most straightforward place to put this logic is in a special directory called buildSrc. Gradle treats buildSrc as a first-class citizen; it compiles everything inside it first and then puts those classes on the classpath of your main build scripts.

Your structure should look like this:


project-root/
├── buildSrc/
│   ├── build.gradle.kts
│   └── src/main/kotlin/
│       └── my-kotlin-library.gradle.kts
├── core/
│   └── build.gradle.kts
└── api/
    └── build.gradle.kts

In buildSrc/build.gradle.kts, you just need to make sure the kotlin-dsl plugin is applied so you can write your plugins using Kotlin:


plugins {
    `kotlin-dsl`
}

repositories {
    mavenCentral()
}

How do I actually write a convention and apply it?

Let's say you want every "library" module in your project to have the same JVM target and the same testing setup. Instead of a complex class, you can use a "precompiled script plugin." This is just a .gradle.kts file inside buildSrc/src/main/kotlin.

Create a file named my-kotlin-library.gradle.kts:


plugins {
    kotlin("jvm")
}

tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
    kotlinOptions {
        jvmTarget = "17"
        freeCompilerArgs = freeCompilerArgs + "-Xjsr305=strict"
    }
}

dependencies {
    // Every library module gets JUnit 5 by default
    testImplementation(kotlin("test"))
    testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
}

tasks.test {
    useJUnitPlatform()
}

Now, the magic part: the filename my-kotlin-library.gradle.kts automatically becomes the plugin ID. In your core/build.gradle.kts or api/build.gradle.kts, you can now replace all that boilerplate with a single line:


plugins {
    id("my-kotlin-library")
}

dependencies {
    // Only module-specific dependencies go here
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
}

What if I need to pass parameters to my convention?

Convention plugins are meant to be "opinions"—they define how things *should* be. If you find yourself wanting to pass a lot of parameters, you might be building a general-purpose plugin rather than a convention. However, a common trick I use is to define an extension. By creating a small data class in buildSrc, you can allow modules to "opt-out" of certain behaviors or provide a specific version string, while still keeping the core logic centralized in the plugin.




📋 Practical Task

Exercise: Create a Shared Testing Convention Plugin

You are working on a project with multiple modules, and you're tired of configuring MockK and JUnit 5 in every single build.gradle.kts file. Your goal is to centralize this into a convention plugin.

  1. Create a buildSrc directory with the necessary build.gradle.kts applying the kotlin-dsl plugin.
  2. Inside buildSrc/src/main/kotlin/, create a precompiled script plugin named internal-test-convention.gradle.kts.
  3. In this plugin, configure the following:
    • Apply the kotlin("jvm") plugin.
    • Add io.mockk:mockk:1.13.5 as a testImplementation dependency.
    • Add org.junit.jupiter:junit-jupiter:5.10.0 as a testImplementation dependency.
    • Configure the test task to use useJUnitPlatform().
  4. Apply this plugin to a dummy module (e.g., :core) using the id("internal-test-convention") syntax and verify that the dependencies are available without being explicitly declared in the module's own build file.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.