How do you author a precompiled convention plugin in buildSrc, and how is it applied in a module?
answer
- kotlin-dsl in buildSrc/build.gradle.kts
- *.gradle.kts under src/main/kotlin
- file name = plugin id
- applied via plugins { id(...) }
- applied plugins must be buildSrc deps
basics
~10 sApply kotlin-dsl in buildSrc/build.gradle.kts, then add a *.gradle.kts file under buildSrc/src/main/kotlin. Its file name (minus the extension) becomes the plugin id, applied via plugins { id("...") }.
solid answer
~40 sA **precompiled convention plugin** is a `*.gradle.kts` script placed under `buildSrc/src/main/kotlin`. To enable them you apply the `kotlin-dsl` plugin in `buildSrc/build.gradle.kts`, which compiles each such script into a real `Plugin<Project>` at build time. The **file name becomes the plugin id**: `acme.java-conventions.gradle.kts` is applied as `plugins { id("acme.java-conventions") }` in any module. An optional package-like prefix maps to a namespace. Inside the script you write the same DSL you'd write in a normal build file — `plugins { }`, `java { }`, `dependencies { }`, `tasks.withType<...> { }`. Any plugin you `apply` inside the convention must be declared as a dependency in `buildSrc/build.gradle.kts` (via its Maven coordinates), because that's where the convention is compiled. This gives you type-safe, IDE-navigable shared configuration with no `apply from` string interpolation.
code
kotlin · 16 lines// buildSrc/build.gradle.kts
plugins { `kotlin-dsl` }
repositories { gradlePluginPortal() }
dependencies {
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.0")
}
// buildSrc/src/main/kotlin/acme.kotlin-conventions.gradle.kts
plugins {
kotlin("jvm") // impl resolved from buildSrc dependency above
}
kotlin { jvmToolchain(21) }
tasks.withType<Test>().configureEach { useJUnitPlatform() }
// app/build.gradle.kts
plugins { id("acme.kotlin-conventions") }go deeper
Know that a *.gradle.kts file in buildSrc becomes a plugin you apply by id.
Author one end-to-end: kotlin-dsl, file-name-as-id, declare applied-plugin dependencies, apply in a module.
Explain composition of conventions and why precompiled beats apply-from (type safety, plugin id, caching).
Standardize a convention-plugin taxonomy across the org and govern their dependency surface.
## What a convention plugin is A **convention plugin** captures a reusable bundle of build configuration ("our standard Java module looks like this") so that each consuming module reduces to `plugins { id("acme.java-conventions") }`. A **precompiled** convention plugin is the ergonomic way to write one: a `*.gradle.kts` script that Gradle compiles into a `Plugin<Project>` class for you — no hand-written `class X : Plugin<Project>`. ## Enabling them In `buildSrc/build.gradle.kts`: ```kotlin plugins { `kotlin-dsl` } repositories { gradlePluginPortal() } dependencies { // every plugin your conventions apply must be a dependency here implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.0") } ``` The `kotlin-dsl` plugin turns each `src/main/kotlin/*.gradle.kts` into a compiled plugin. ## File name → plugin id The **plugin id is derived from the file name** (and any package prefix). `acme.java-conventions.gradle.kts` ⇒ id `acme.java-conventions`. So: ```kotlin // buildSrc/src/main/kotlin/acme.java-conventions.gradle.kts plugins { `java-library` } java { toolchain { languageVersion.set(JavaLanguageVersion.of(21)) } } tasks.withType<Test>().configureEach { useJUnitPlatform() } ``` And in a module: ```kotlin // app/build.gradle.kts plugins { id("acme.java-conventions") } ``` ## The classpath rule (the common gotcha) If your convention does `plugins { id("org.jetbrains.kotlin.jvm") }` or `kotlin("jvm")`, the Kotlin Gradle plugin must be a **dependency of buildSrc** — otherwise the convention won't compile. Precompiled scripts apply plugins *by id*, but the implementation must be resolvable on buildSrc's classpath. You add it via Maven coordinates (the `*-gradle-plugin` artifact), not via a `plugins {}` version in buildSrc. ## Why precompiled over apply-from `apply(from = "shared.gradle.kts")` is untyped, not cached well, and has no plugin id. Precompiled convention plugins are real plugins: type-safe accessors, ordered via the plugin mechanism, applied with the standard `plugins {}` block, and composable (one convention can apply another). They are the modern idiom for de-duplicating build logic in buildSrc. ## Composition Conventions can build on each other: `acme.library-conventions.gradle.kts` can start with `plugins { id("acme.java-conventions") }` to layer extra rules on the base, keeping each focused.
- Where does the plugin id of a precompiled convention plugin come from?From the file name (minus the .gradle.kts extension), optionally namespaced by a package-like prefix. So acme.java-conventions.gradle.kts -> id "acme.java-conventions".
- Your convention does kotlin("jvm") but buildSrc fails to compile. Why?The Kotlin Gradle plugin implementation is not on buildSrc's classpath. Add it as a dependency in buildSrc/build.gradle.kts via its Maven coordinates.
- How do you layer conventions?Have one convention apply another in its own plugins {} block, e.g. acme.library-conventions applies acme.java-conventions then adds extra rules.
saying these in an interview costs you the question
- Forgetting to apply kotlin-dsl, so the *.gradle.kts files are not compiled into plugins.
- Applying a plugin inside a convention without adding its artifact as a buildSrc dependency.
- Using a `plugins { id(...) version "..." }` block inside buildSrc instead of declaring coordinates in dependencies.