Introduction to Plugins (original) (raw)

There are three types of plugins you can develop:

# Type Location: Most likely: Benefit:
1 Script plugins A .gradle(.kts) script file A local plugin The plugin is automatically compiled and included in the classpath of the build script.
2 Precompiled script plugins buildSrc folder or composite build A convention plugin The plugin is automatically compiled, tested, and available on the classpath of the build script. The plugin is visible to every build script used by the build.
3 Binary plugins Standalone project A shared plugin The plugin JAR is produced and can be published. The plugin can be used in multiple builds and shared with others.

Script plugins

A script plugin is a plugin written directly in a build file (.gradle or .gradle.kts). They are usually small, local utilities for one project and not intended for reuse.

Script plugins are useful for quick experiments but not recommended for production builds. They often evolve into proper plugins placed in buildSrc or published as standalone plugins.

The simplest plugin is a class that implements Plugin and configures the project when applied:

View full sample project on GitHub

build.gradle.kts

class GreetingPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        project.task("hello") {
            doLast {
                println("Hello from the GreetingPlugin")
            }
        }
    }
}

// Apply the plugin
apply<GreetingPlugin>()

build.gradle

class GreetingPlugin implements Plugin<Project> {
    void apply(Project project) {
        project.task('hello') {
            doLast {
                println 'Hello from the GreetingPlugin'
            }
        }
    }
}

// Apply the plugin
apply plugin: GreetingPlugin
Hello from the GreetingPlugin

You can also put such a plugin in a separate script file and apply it with apply(from = "other.gradle.kts") or apply from: 'other.gradle':

other.gradle.kts

class GreetingScriptPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        project.task("hi") {
            doLast {
                println("Hi from the GreetingScriptPlugin")
            }
        }
    }
}

// Apply the plugin
apply<GreetingScriptPlugin>()

other.gradle

class GreetingScriptPlugin implements Plugin<Project> {
    void apply(Project project) {
        project.task('hi') {
            doLast {
                println 'Hi from the GreetingScriptPlugin'
            }
        }
    }
}

// Apply the plugin
apply plugin: GreetingScriptPlugin

build.gradle.kts

apply(from = "other.gradle.kts")

build.gradle

apply from: 'other.gradle'
Hi from the GreetingScriptPlugin

**Script plugins should be avoided.**Script plugins are convenient for local, throwaway logic but make builds harder to maintain.

Precompiled script plugins

Precompiled script plugins are compiled into class files and packaged into a JAR before they are executed. These plugins use the Gradle DSLs instead of pure Java, Kotlin, or Groovy.

To create a precompiled script plugin, you can:

  1. Use Gradle’s Kotlin DSL - The plugin is a .gradle.kts file, and applies id("kotlin-dsl").
  2. Use Gradle’s Groovy DSL - The plugin is a .gradle file, and apply id("groovy-gradle-plugin").

To apply a precompiled script plugin, you need to know its ID. The ID is derived from the plugin script’s filename and its (optional) package declaration.

For example, the script src/main/kotlin/some-java-library.gradle.kts has a plugin ID of some-java-library (assuming it has no package declaration). Likewise, src/main/kotlin/my/some-java-library.gradle(.kts) has a plugin ID of my.some-java-library as long as it has a package declaration of my.

Precompiled script plugin names have two important limitations:

When the plugin is applied to a project, Gradle creates an instance of the plugin class and calls the instance’s Plugin.apply() method.

Let’s rewrite the GreetingPlugin script plugin as a precompiled script plugin. The script plugin is moved to its own file in buildSrc:

View full sample project on GitHub

buildSrc/src/main/kotlin/GreetingPlugin.gradle.kts

tasks.register("hello") {
    doLast {
        println("Hello from the convention GreetingPlugin")
    }
}

buildSrc/src/main/groovy/GreetingPlugin.gradle

tasks.register("hello") {
    doLast {
        println("Hello from the convention GreetingPlugin")
    }
}

The GreetingPlugin can now be applied in other subprojects' builds by using its ID:

app/build.gradle.kts

plugins {
    application
    id("GreetingPlugin")
}

app/build.gradle

plugins {
    id 'application'
    id('GreetingPlugin')
}
Hello from the convention GreetingPlugin

Convention plugins

A convention plugin is typically a precompiled script plugin that configures existing core and community plugins with your own conventions (i.e. default values) such as setting the Java version by using java.toolchain.languageVersion = JavaLanguageVersion.of(17).

Convention plugins are also used to enforce project standards and help streamline the build process. They can apply and configure plugins, create new tasks and extensions, set dependencies, and much more.

Let’s take an example build with three subprojects: one for data-model, one for database-logic and one for app code. The project has the following structure:

.
├── buildSrc
│   ├── src
│   │   └──...
│   └── build.gradle.kts
├── data-model
│   ├── src
│   │   └──...
│   └── build.gradle.kts
├── database-logic
│   ├── src
│   │   └──...
│   └── build.gradle.kts
├── app
│   ├── src
│   │   └──...
│   └── build.gradle.kts
└── settings.gradle.kts

The build file of the database-logic subproject is as follows:

View full sample project on GitHub

database-logic/build.gradle.kts

plugins {
    id("java-library")
    id("org.jetbrains.kotlin.jvm") version "2.3.0"
}

repositories {
    mavenCentral()
}

java {
    toolchain.languageVersion.set(JavaLanguageVersion.of(11))
}

tasks.test {
    useJUnitPlatform()
}

kotlin {
    jvmToolchain(11)
}

// More build logic

database-logic/build.gradle

plugins {
    id 'java-library'
    id 'org.jetbrains.kotlin.jvm' version '2.3.0'
}

repositories {
    mavenCentral()
}

java {
    toolchain.languageVersion.set(JavaLanguageVersion.of(11))
}

tasks.test {
    useJUnitPlatform()
}

kotlin {
    jvmToolchain {
        languageVersion.set(JavaLanguageVersion.of(11))
    }
}

// More build logic

We apply the java-library plugin and add the org.jetbrains.kotlin.jvm plugin for Kotlin support. We also configure Kotlin, Java, tests and more.

Our build file is beginning to grow.

The more plugins we apply and the more plugins we configure, the larger it gets. There’s also repetition in the build files of the app and data-model subprojects, especially when configuring common extensions like setting the Java version and Kotlin support.

To address this, we use convention plugins. This allows us to avoid repeating configuration in each build file and keeps our build scripts more concise and maintainable. In convention plugins, we can encapsulate arbitrary build configuration or custom build logic.

To develop a convention plugin, you can use the protected buildSrc folder, which represents a completely separate Gradle build.buildSrc has its own settings file to define where dependencies of this build are located.

We add a Kotlin script called my-java-library.gradle.kts inside the buildSrc/src/main/kotlin directory. Or conversely, a Groovy script called my-java-library.gradle inside the buildSrc/src/main/groovy directory. We put all the plugin application and configuration from the database-logic build file into it:

buildSrc/src/main/kotlin/my-java-library.gradle.kts

plugins {
    id("java-library")
    id("org.jetbrains.kotlin.jvm")
}

repositories {
    mavenCentral()
}

java {
    toolchain.languageVersion.set(JavaLanguageVersion.of(11))
}

tasks.test {
    useJUnitPlatform()
}

kotlin {
    jvmToolchain(11)
}

buildSrc/src/main/groovy/my-java-library.gradle

plugins {
    id 'java-library'
    id 'org.jetbrains.kotlin.jvm'
}

repositories {
    mavenCentral()
}

java {
    toolchain.languageVersion.set(JavaLanguageVersion.of(11))
}

tasks.test {
    useJUnitPlatform()
}

kotlin {
    jvmToolchain {
        languageVersion.set(JavaLanguageVersion.of(11))
    }
}

The name of the file my-java-library is the ID of our plugin, which we can now reference in all of our subprojects.

The database-logic build file becomes much simpler by removing all the redundant build logic and applying our convention my-java-library plugin instead:

database-logic/build.gradle.kts

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

database-logic/build.gradle

plugins {
    id('my-java-library')
}

This convention plugin enables us to easily share common configurations across all our build files. Any modifications can be made in one place, simplifying maintenance.

Binary plugins

Binary plugins in Gradle are plugins that are built as standalone JAR files and applied to a project using the plugins {} block in the build script.

Let’s move our GreetingPlugin to a standalone project so that we can publish it and share it with others. The plugin is essentially moved from the buildSrc folder to its own build called greeting-plugin.

| | You can publish the plugin from buildSrc, but this is not recommended practice. Plugins that are ready for publication should be in their own build. | | ------------------------------------------------------------------------------------------------------------------------------------------------------- |

greeting-plugin is simply a Java project that produces a JAR containing the plugin classes.

The easiest way to package and publish a plugin to a repository is to use the Gradle Plugin Development Plugin. This plugin provides the necessary tasks and configurations (including the plugin metadata) to compile your script into a plugin that can be applied in other builds.

Here is a simple build script for the greeting-plugin project using the Gradle Plugin Development Plugin:

View full sample project on GitHub

build.gradle.kts

plugins {
    `java-gradle-plugin`
}

gradlePlugin {
    plugins {
        create("simplePlugin") {
            id = "org.example.greeting"
            implementationClass = "org.example.GreetingPlugin"
        }
    }
}

build.gradle

plugins {
    id 'java-gradle-plugin'
}

gradlePlugin {
    plugins {
        simplePlugin {
            id = 'org.example.greeting'
            implementationClass = 'org.example.GreetingPlugin'
        }
    }
}