skip to content

Developing a Plugin and Its Consumer Together

Including a plugin build so the consumer applies the in-development plugin id directly, iterating on both sides in one workspace. Asked as the practical alternative to publishing to mavenLocal after every change.

on this pageshow

questions

5

What must the in-development plugin build declare so that a consumer including it can apply the plugin by its id?

level: juniorimportance: must knowfreq 40%

answer

  1. apply java-gradle-plugin
  2. gradlePlugin { plugins { register } }
  3. id + implementationClass
  4. generates plugin marker
  5. no marker = no resolve

basics

~10 s

The plugin build must apply the java-gradle-plugin plugin and declare the plugin id and implementationClass in a gradlePlugin { plugins { register(...) } } block. That generates the marker the consumer's id resolves against.

solid answer

~30 s

For an included build's plugin to be applicable by id, the plugin build needs the `java-gradle-plugin` plugin applied and a `gradlePlugin {}` block declaring each plugin's `id` and `implementationClass`. `java-gradle-plugin` generates the **plugin marker** (`<id>:<id>.gradle.plugin`) and descriptor metadata. When the consumer writes `plugins { id("com.example.myplugin") }` and includes the build, Gradle substitutes that requested marker with the included build's output. Without the `gradlePlugin` declaration (or precompiled-script-plugin auto-detection), there's no marker to match, so the id won't resolve even though the build is included. So the two requirements are: apply `java-gradle-plugin`, and declare the id → implementationClass mapping.

code

kotlin · 11 lines
kotlin
// build-logic/build.gradle.kts
plugins { `java-gradle-plugin` }

gradlePlugin {
    plugins {
        register("myplugin") {
            id = "com.example.myplugin"
            implementationClass = "com.example.MyPlugin"
        }
    }
}

go deeper

for a junior

Know you apply java-gradle-plugin and declare id + implementationClass so the consumer can apply the plugin by id.

for a middle

Explain that this generates the plugin marker that includeBuild substitution matches against.

for a senior

Contrast hand-written gradlePlugin declarations with precompiled-script-plugin auto-detection and the shared marker requirement.

for a principal

Discuss conventions for declaring and versioning plugin ids across a shared build-logic build.

## What 'apply by id' needs under the hood Applying a plugin via `plugins { id("com.example.myplugin") }` makes Gradle look for a **plugin marker artifact**: a tiny module whose coordinates are `com.example.myplugin:com.example.myplugin.gradle.plugin`, which points at the real plugin implementation module. For a published plugin, the build that produces it generates this marker. For an included build to satisfy the id, it must produce that same marker. ## The two requirements in the plugin build 1. **Apply `java-gradle-plugin`.** This plugin: - puts `gradleApi()` on the compile classpath, - validates plugin metadata, - and **generates the plugin marker publication** + the `META-INF` plugin descriptor. 2. **Declare the plugin(s):** ```kotlin plugins { `java-gradle-plugin` } gradlePlugin { plugins { register("myplugin") { id = "com.example.myplugin" implementationClass = "com.example.MyPlugin" } } } ``` The `id` is what consumers type; `implementationClass` is the fully-qualified `Plugin<Project>` (or `Plugin<Settings>`) class. ## How it ties to includeBuild With those in place and `includeBuild("build-logic")` in the consumer settings, Gradle matches the consumer's requested marker to the included build's generated marker and substitutes the locally built plugin — no repository, no publish. ## Alternative: precompiled script plugins If the plugin build uses `kotlin-dsl` / `groovy-gradle-plugin` with files like `src/main/kotlin/com.example.myconvention.gradle.kts`, Gradle auto-derives the id from the file name and auto-generates the marker — you don't hand-write the `gradlePlugin { register }` block for those. The underlying requirement (a marker to substitute) is the same. ## Failure mode If you include the build but forget `java-gradle-plugin` or the id declaration, the consumer's `plugins { id(...) }` fails to resolve — the build is included, but there's no marker to match.

  • What does the java-gradle-plugin plugin generate that makes id-based resolution work?
    It generates the plugin marker publication (`<id>:<id>.gradle.plugin`) and the plugin descriptor metadata. The marker is the coordinate Gradle matches when a consumer requests the id, so the included build can substitute its output for it.
  • How is this different when using precompiled script plugins (kotlin-dsl)?
    With kotlin-dsl / groovy-gradle-plugin, a file like `com.example.foo.gradle.kts` auto-derives its id from the filename and auto-generates the marker, so you don't hand-write the gradlePlugin register block. The requirement — a generated marker to substitute — is identical.

saying these in an interview costs you the question

  • Saying just including the build is enough without declaring the id/marker.
  • Forgetting that java-gradle-plugin (or kotlin-dsl) is what creates the marker.
  • Confusing implementationClass with the plugin id.

context

open as a page

How can you develop a Gradle plugin and a project that consumes it in the same workspace, applying the plugin by its id without publishing first?

level: middleimportance: must knowfreq 55%

basics

~10 s

Put the plugin in its own build, then add includeBuild("build-logic") to the consumer's settings.gradle. The consumer can then apply the plugin by its id with no publishing.

open as a page

In a composite build where the consumer applies an in-development plugin, what happens when you change the plugin source and re-run a consumer task?

level: middleimportance: should knowfreq 30%

basics

~10 s

Gradle rebuilds the included plugin build first (with normal up-to-date checks), then runs the consumer task against the freshly compiled plugin. No publishing or version bump is needed between edits.

open as a page

When developing a plugin together with its consumer, when would you prefer an included build (includeBuild) over putting the plugin in buildSrc?

level: seniorimportance: should knowfreq 35%

basics

~10 s

Use buildSrc for plugin logic private to one build. Use an explicit included build when the plugin is reusable, applied by a public id, or shared across multiple consumer builds.

open as a page

Your in-development plugin is a Settings plugin (applied in settings.gradle), not a project plugin. Can you co-develop it with a consumer via includeBuild, and what changes?

level: seniorimportance: nice to knowfreq 15%

basics

~20 s

A Settings plugin applies in the consumer's settings.gradle, but includeBuild's plugin substitution is processed during settings evaluation. To apply an included Settings plugin by id you use pluginManagement { includeBuild(...) } so the plugin build is available early enough.

open as a page