# Binary Plugins

version 9.8.0-20260811015503+0000

Binary plugins refer to plugins that are compiled and distributed as JAR files. These plugins are usually written in Java or Kotlin and provide custom functionality or tasks to a Gradle build.

## Anatomy of a binary plugin

A typical Gradle plugin is composed of three cooperating parts:

Part | Purpose | Notes
--- | --- | ---
Plugin class | Applies behavior to a target (`Project/Settings`), registers tasks, and wires defaults. | Implements `Plugin<Project>` (or `Plugin<Settings>`). Keeps configuration lazy; avoid doing work in apply.
Extension | Holds user configuration exposed as a DSL block. | Use `Property<T>`, `RegularFileProperty`, `DirectoryProperty`, and `ListProperty<T>` for laziness, validation, and Configuration Cache compatibility.
Task type | Implements the actual work when executed. | Model inputs/outputs with `@Input`, `@InputFile`, `@OutputFile`, etc., to enable incremental builds and caching.

## Example of a binary plugin

Here’s a detailed look at the project structure for a simple `Greeting` plugin:

build.gradle.kts

    plugin/     (1)
    ├── build.gradle.kts    (2)
    └── src/
        └── main/
            └── kotlin/
                └── org/
                    └── example/    (3)
                        ├── GreetingPlugin.kt
                        ├── GreetingPluginExtension.kt
                        └── GreetingTask.kt

1 | `plugin/`: This is the root directory for the Gradle plugin project.
2 | `build.gradle.kts`: The build script for the plugin itself. It specifies dependencies like the Gradle API and Kotlin, and it’s where you apply the `kotlin-dsl` or `java-gradle-plugin` to make your code a runnable Gradle plugin.
3 | `src/main/kotlin/org/example/`: This is the standard source directory for Kotlin code, organized by package name.

build.gradle

    plugin/     (1)
    ├── build.gradle    (2)
    └── src/
        └── main/
            └── groovy/
                └── org/
                    └── example/    (3)
                        ├── GreetingPlugin.groovy
                        ├── GreetingPluginExtension.groovy
                        └── GreetingTask.groovy

1 | `plugin/`: This is the root directory for the Gradle plugin project.
2 | `build.gradle`: The build script for the plugin itself. It specifies dependencies like the Gradle API and Groovy, and it’s where you apply the `groovy-gradle-plugin` or `java-gradle-plugin` to make your code a runnable Gradle plugin.
3 | `src/main/groovy/org/example/`: This is the standard source directory for Groovy code, organized by package name.

### The plugin class (`GreetingPlugin.kt`)

The core of the plugin is the `GreetingPlugin` class. It implements the `Plugin<Project>` interface and overrides the `apply` method, which is the entry point for your plugin’s logic:

plugin/src/main/kotlin/org/example/GreetingPlugin.kt

    package org.example

    import org.gradle.api.Plugin
    import org.gradle.api.Project

    class GreetingPlugin : Plugin<Project> {
        override fun apply(project: Project) {
            // Create extension and set sensible defaults (conventions)
            val ext = project.extensions.create("greeting", GreetingExtension::class.java).apply {  (1)
                message.convention("Hello from plugin")
                outputFile.convention(project.layout.buildDirectory.file("greeting.txt"))
            }

            // Register task and map extension -> task inputs (lazy wiring)
            project.tasks.register("greet", GreetTask::class.java) {    (2)
                group = "example"
                description = "Writes a greeting to a file"
                message.set(ext.message)
                outputFile.set(ext.outputFile)
            }
        }
    }

plugin/src/main/groovy/org/example/GreetingPlugin.groovy

    package org.example

    import org.gradle.api.Plugin
    import org.gradle.api.Project
    import org.example.GreetingExtension
    import org.example.GreetTask

    class GreetingPlugin implements Plugin<Project> {
        void apply(Project project) {
            // Create extension and set sensible defaults (conventions)
            def ext = project.extensions.create("greeting", GreetingExtension)  (1)
            ext.message.convention("Hello from plugin")
            ext.outputFile.convention(project.layout.buildDirectory.file("greeting.txt"))

            // Register task and map extension -> task inputs (lazy wiring)
            project.tasks.register("greet", GreetTask) {    (2)
                group = "example"
                description = "Writes a greeting to a file"
                message.set(ext.message)
                outputFile.set(ext.outputFile)
            }
        }
    }

1 | `project.extensions.create()`: This creates an extension named greeting. This is how users of your plugin can configure its behavior in their build.gradle file. The apply block sets convention values, which are default values that the user can override.
2 | `project.tasks.register()`: This registers a task of type GreetTask named greet. This is the action that your plugin provides.

Lazy Wiring: The `message.set(ext.message)` and `outputFile.set(ext.outputFile)` lines are examples of lazy wiring. The task’s inputs are not read immediately. Instead, they are connected to the extension’s properties, and the values are only resolved when the task actually runs. This is crucial for performance and correctness in Gradle builds.

### The extension (`GreetingExtension.kt`)

The extension defines the properties that a user can configure. It uses `Property` and `RegularFileProperty` types, which are designed for lazy evaluation:

plugin/src/main/kotlin/org/example/GreetingExtension.kt

    package org.example

    import org.gradle.api.provider.Property
    import org.gradle.api.file.RegularFileProperty

    abstract class GreetingExtension {
        abstract val message: Property<String>          (1)
        abstract val outputFile: RegularFileProperty    (2)
    }

plugin/src/main/groovy/org/example/GreetingExtension.groovy

    package org.example

    import org.gradle.api.provider.Property
    import org.gradle.api.file.RegularFileProperty

    abstract class GreetingExtension {
        abstract Property<String> getMessage()          (1)
        abstract RegularFileProperty getOutputFile()    (2)
    }

1 | `Property<T>`: This is a container for a value of type T. The actual value is not read until it’s needed, which enables lazy configuration.
2 | `RegularFileProperty`: A specific type of property for file paths. Gradle uses this to track file inputs and outputs for up-to-date checks and the build cache.

### The custom task (`GreetTask.kt`)

This is the class for the custom task. It defines the inputs and outputs, and the action to perform:

plugin/src/main/kotlin/org/example/GreetTask.kt

    package org.example

    import org.gradle.api.DefaultTask
    import org.gradle.api.file.RegularFileProperty
    import org.gradle.api.provider.Property
    import org.gradle.api.tasks.Input
    import org.gradle.api.tasks.OutputFile
    import org.gradle.api.tasks.TaskAction

    abstract class GreetTask : DefaultTask() {
        @get:Input      (1)
        abstract val message: Property<String>

        @get:OutputFile (2)
        abstract val outputFile: RegularFileProperty

        @TaskAction     (3)
        fun run() {
            val file = outputFile.get().asFile
            file.parentFile.mkdirs()
            file.writeText(message.get())
            logger.lifecycle("Wrote greeting to ${file}")
        }
    }

plugin/src/main/groovy/org/example/GreetTask.groovy

    package org.example

    import org.gradle.api.DefaultTask
    import org.gradle.api.file.RegularFileProperty
    import org.gradle.api.provider.Property
    import org.gradle.api.tasks.Input
    import org.gradle.api.tasks.OutputFile
    import org.gradle.api.tasks.TaskAction

    abstract class GreetTask extends DefaultTask {
        @Input          (1)
        abstract Property<String> getMessage()

        @OutputFile     (2)
        abstract RegularFileProperty getOutputFile()

        @TaskAction     (3)
        void run() {
            def file = getOutputFile().get().asFile
            file.parentFile.mkdirs()
            file.write(getMessage().get())
            logger.lifecycle("Wrote greeting to ${file}")
        }
    }

1 | `@Input`: This annotation marks a property as a task input. When a task has an input, Gradle will automatically track its value. If the value changes between builds, Gradle knows the task is out-of-date and needs to be re-run.
2 | `@OutputFile`: This annotation marks a property as a task output. Gradle tracks the location of this file. If the file is missing or its contents are older than the inputs, Gradle considers the task out-of-date.
3 | `@TaskAction`: This annotation marks the method that contains the work to be done. This method is executed when the task runs.

Up-to-Date Checks: By properly annotating inputs and outputs, Gradle can perform up-to-date checks. If the task’s inputs and outputs haven’t changed since the last build, Gradle can skip the task entirely, which dramatically speeds up the build process.

### Applying the plugin

To use this `Greeting` plugin, you need to apply the plugin in your `build.gradle(.kts)` file. This makes the plugin’s tasks and extensions available to your project:

    plugins {
        id("org.example.greeting")
    }

### Configuring the plugin

Once the plugin is applied, you can configure it using the extension you defined, which is named `greeting`. This allows you to override the default values set in the `GreetingPlugin`. Using the `greeting` extension block:

    greeting {
        // Override the default message
        message = "Hola from my custom build"
        // Set a custom output file path
        outputFile = layout.buildDirectory.file("custom-greeting.txt")
    }

### Running the task

The plugin registers a task named `greet`. After applying and configuring the plugin, you can execute this task from the command line.

    $ ./gradlew greet

Running the `greet` task will:

- Use the `message` and `outputFile` properties, either with their default values or your custom configured values.

- Write the specified message to the output file (e.g., `build/custom-greeting.txt`).

- Print a log message to the console confirming the file has been written.

## Using the Plugin Development plugin

The Gradle Plugin Development plugin can be used to assist in developing Gradle plugins.

This plugin will automatically apply the Java Plugin, add the `gradleApi()` dependency to the `api` configuration, generate the required plugin descriptors in the resulting JAR file, and configure the Plugin Marker Artifact to be used when publishing.

To apply and configure the plugin, add the following code to your build file:

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'
            }
        }
    }

Writing and using custom task types is recommended when developing plugins as it automatically benefits from incremental builds. As an added benefit of applying the plugin to your project, the task `validatePlugins` automatically checks for an existing input/output annotation for every public property defined in a custom task type implementation.

## Creating a plugin ID

Plugin IDs are meant to be globally unique, similar to Java package names (i.e., a reverse domain name). This format helps prevent naming collisions and allows grouping plugins with similar ownership.

An explicit plugin identifier simplifies applying the plugin to a project. Your plugin ID should combine components that reflect the namespace (a reasonable pointer to you or your organization) and the name of the plugin it provides. For example, if your Github account is named `foo` and your plugin is named `bar`, a suitable plugin ID might be `com.github.foo.bar`. Similarly, if the plugin was developed at the `baz` organization, the plugin ID might be `org.baz.bar`.

Plugin IDs should adhere to the following guidelines:

- May contain any alphanumeric character, '.', and '-'.

- Must contain at least one '.' character separating the namespace from the plugin’s name.

- Conventionally use a lowercase reverse domain name convention for the namespace.

- Conventionally use only lowercase characters in the name.

- `org.gradle`, `com.gradle`, and `com.gradleware` namespaces may not be used.

- Cannot start or end with a '.' character.

- Cannot contain consecutive '.' characters (i.e., '..').

A namespace that identifies ownership and a name is sufficient for a plugin ID.

When bundling multiple plugins in a single JAR artifact, adhering to the same naming conventions is recommended. This practice helps logically group related plugins.

There is no limit to the number of plugins that can be defined and registered (by different identifiers) within a single project.

The identifiers for plugins written as a class should be defined in the project’s build script containing the plugin classes. For this, the `java-gradle-plugin` needs to be applied:

buildSrc/build.gradle.kts

    plugins {
        id("java-gradle-plugin")
    }

    gradlePlugin {
        plugins {
            create("androidApplicationPlugin") {
                id = "com.android.application"
                implementationClass = "com.android.AndroidApplicationPlugin"
            }
            create("androidLibraryPlugin") {
                id = "com.android.library"
                implementationClass = "com.android.AndroidLibraryPlugin"
            }
        }
    }

buildSrc/build.gradle

    plugins {
        id 'java-gradle-plugin'
    }

    gradlePlugin {
        plugins {
            androidApplicationPlugin {
                id = 'com.android.application'
                implementationClass = 'com.android.AndroidApplicationPlugin'
            }
            androidLibraryPlugin {
                id = 'com.android.library'
                implementationClass = 'com.android.AndroidLibraryPlugin'
            }
        }
    }

## Working with files

When developing plugins, it’s a good idea to be flexible when accepting input configuration for file locations.

It is recommended to use Gradle’s managed properties and `project.layout` to select file or directory locations. This will enable lazy configuration so that the actual location will only be resolved when the file is needed and can be reconfigured at any time during build configuration.

This Gradle build file defines a task `GreetingToFileTask` that writes a greeting to a file. It also registers two tasks: `greet`, which creates the file with the greeting, and `sayGreeting`, which prints the file’s contents. The `greetingFile` property is used to specify the file path for the greeting:

build.gradle.kts

    abstract class GreetingToFileTask : DefaultTask() {

        @get:OutputFile
        abstract val destination: RegularFileProperty

        @TaskAction
        fun greet() {
            val file = destination.get().asFile
            file.parentFile.mkdirs()
            file.writeText("Hello!")
        }
    }

    val greetingFile = objects.fileProperty()

    tasks.register<GreetingToFileTask>("greet") {
        destination = greetingFile
    }

    tasks.register("sayGreeting") {
        dependsOn("greet")
        val greetingFile = greetingFile
        doLast {
            val file = greetingFile.get().asFile
            println("${file.readText()} (file: ${file.name})")
        }
    }

    greetingFile = layout.buildDirectory.file("hello.txt")

build.gradle

    abstract class GreetingToFileTask extends DefaultTask {

        @OutputFile
        abstract RegularFileProperty getDestination()

        @TaskAction
        def greet() {
            def file = getDestination().get().asFile
            file.parentFile.mkdirs()
            file.write 'Hello!'
        }
    }

    def greetingFile = objects.fileProperty()

    tasks.register('greet', GreetingToFileTask) {
        destination = greetingFile
    }

    tasks.register('sayGreeting') {
        dependsOn greet
        doLast {
            def file = greetingFile.get().asFile
            println "${file.text} (file: ${file.name})"
        }
    }

    greetingFile = layout.buildDirectory.file('hello.txt')

    $ ./gradlew -q sayGreeting

    Hello! (file: hello.txt)

In this example, we configure the `greet` task `destination` property as a closure/provider, which is evaluated with the Project.file(java.lang.Object) method to turn the return value of the closure/provider into a `File` object at the last minute. Note that we specify the `greetingFile` property value after the task configuration. This lazy evaluation is a key benefit of accepting any value when setting a file property and then resolving that value when reading the property.

You can learn more about working with files lazily in Working with Files.

## Making a plugin configurable using extensions

Most plugins offer configuration options for build scripts and other plugins to customize how the plugin works. Plugins do this using extension objects.

A Project has an associated ExtensionContainer object that contains all the settings and properties for the plugins that have been applied to the project. You can provide configuration for your plugin by adding an extension object to this container.

An extension object is simply an object with Java Bean properties representing the configuration.

Let’s add a `greeting` extension object to the project, which allows you to configure the greeting:

build.gradle.kts

    interface GreetingPluginExtension {
        val message: Property<String>
    }

    class GreetingPlugin : Plugin<Project> {
        override fun apply(project: Project) {
            // Add the 'greeting' extension object
            val extension = project.extensions.create<GreetingPluginExtension>("greeting")
            // Add a task that uses configuration from the extension object
            project.task("hello") {
                doLast {
                    println(extension.message.get())
                }
            }
        }
    }

    apply<GreetingPlugin>()

    // Configure the extension
    the<GreetingPluginExtension>().message = "Hi from Gradle"

build.gradle

    interface GreetingPluginExtension {
        Property<String> getMessage()
    }

    class GreetingPlugin implements Plugin<Project> {
        void apply(Project project) {
            // Add the 'greeting' extension object
            def extension = project.extensions.create('greeting', GreetingPluginExtension)
            // Add a task that uses configuration from the extension object
            project.task('hello') {
                doLast {
                    println extension.message.get()
                }
            }
        }
    }

    apply plugin: GreetingPlugin

    // Configure the extension
    greeting.message = 'Hi from Gradle'

    $ ./gradlew -q hello

    Hi from Gradle

In this example, `GreetingPluginExtension` is an object with a property called `message`. The extension object is added to the project with the name `greeting`. This object becomes available as a project property with the same name as the extension object. `the<GreetingPluginExtension>()` is equivalent to `project.extensions.getByType(GreetingPluginExtension::class.java)`.

Often, you have several related properties you need to specify on a single plugin. Gradle adds a configuration block for each extension object, so you can group settings:

build.gradle.kts

    interface GreetingPluginExtension {
        val message: Property<String>
        val greeter: Property<String>
    }

    class GreetingPlugin : Plugin<Project> {
        override fun apply(project: Project) {
            val extension = project.extensions.create<GreetingPluginExtension>("greeting")
            project.task("hello") {
                doLast {
                    println("${extension.message.get()} from ${extension.greeter.get()}")
                }
            }
        }
    }

    apply<GreetingPlugin>()

    // Configure the extension using a DSL block
    configure<GreetingPluginExtension> {
        message = "Hi"
        greeter = "Gradle"
    }

build.gradle

    interface GreetingPluginExtension {
        Property<String> getMessage()
        Property<String> getGreeter()
    }

    class GreetingPlugin implements Plugin<Project> {
        void apply(Project project) {
            def extension = project.extensions.create('greeting', GreetingPluginExtension)
            project.task('hello') {
                doLast {
                    println "${extension.message.get()} from ${extension.greeter.get()}"
                }
            }
        }
    }

    apply plugin: GreetingPlugin

    // Configure the extension using a DSL block
    greeting {
        message = 'Hi'
        greeter = 'Gradle'
    }

    $ ./gradlew -q hello

    Hi from Gradle

In this example, several settings can be grouped within the `configure<GreetingPluginExtension>` block. The `configure` function is used to configure an extension object. It provides a convenient way to set properties or apply configurations to these objects. The type used in the build script’s `configure` function (`GreetingPluginExtension`) must match the extension type. Then, when the block is executed, the receiver of the block is the extension.

In this example, several settings can be grouped within the `greeting` closure. The name of the closure block in the build script (`greeting`) must match the extension object name. Then, when the closure is executed, the fields on the extension object will be mapped to the variables within the closure based on the standard Groovy closure delegate feature.

## Declaring a DSL configuration container

Using an extension object extends the Gradle DSL to add a project property and DSL block for the plugin. Because an extension object is a regular object, you can provide your own DSL nested inside the plugin block by adding properties and methods to the extension object.

Let’s consider the following build script for illustration purposes.

build.gradle.kts

    plugins {
        id("org.myorg.server-env")
    }

    environments {
        create("dev") {
            url = "http://localhost:8080"
        }

        create("staging") {
            url = "http://staging.enterprise.com"
        }

        create("production") {
            url = "http://prod.enterprise.com"
        }
    }

build.gradle

    plugins {
        id 'org.myorg.server-env'
    }

    environments {
        dev {
            url = 'http://localhost:8080'
        }

        staging {
            url = 'http://staging.enterprise.com'
        }

        production {
            url = 'http://prod.enterprise.com'
        }
    }

The DSL exposed by the plugin exposes a container for defining a set of environments. Each environment the user configures has an arbitrary but declarative name and is represented with its own DSL configuration block. The example above instantiates a development, staging, and production environment, including its respective URL.

Each environment must have a data representation in code to capture the values. The name of an environment is immutable and can be passed in as a constructor parameter. Currently, the only other parameter the data object stores is a URL.

The following `ServerEnvironment` object fulfills those requirements:

ServerEnvironment.java

    abstract public class ServerEnvironment {
        private final String name;

        @javax.inject.Inject
        public ServerEnvironment(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        abstract public Property<String> getUrl();
    }

Gradle exposes the factory method ObjectFactory.domainObjectContainer(Class, NamedDomainObjectFactory) to create a container of data objects. The parameter the method takes is the class representing the data. The created instance of type NamedDomainObjectContainer can be exposed to the end user by adding it to the extension container with a specific name.

It’s common for a plugin to post-process the captured values within the plugin implementation, e.g., to configure tasks:

ServerEnvironmentPlugin.java

    public class ServerEnvironmentPlugin implements Plugin<Project> {
        @Override
        public void apply(final Project project) {
            ObjectFactory objects = project.getObjects();

            NamedDomainObjectContainer<ServerEnvironment> serverEnvironmentContainer =
                objects.domainObjectContainer(ServerEnvironment.class, name -> objects.newInstance(ServerEnvironment.class, name));
            project.getExtensions().add("environments", serverEnvironmentContainer);

            serverEnvironmentContainer.all(serverEnvironment -> {
                String env = serverEnvironment.getName();
                String capitalizedServerEnv = env.substring(0, 1).toUpperCase() + env.substring(1);
                String taskName = "deployTo" + capitalizedServerEnv;
                project.getTasks().register(taskName, Deploy.class, task -> task.getUrl().set(serverEnvironment.getUrl()));
            });
        }
    }

In the example above, a deployment task is created dynamically for every user-configured environment.

You can find out more about implementing project extensions in Developing Custom Gradle Types.

## Modeling DSL-like APIs

DSLs exposed by plugins should be readable and easy to understand.

For example, let’s consider the following extension provided by a plugin. In its current form, it offers a "flat" list of properties for configuring the creation of a website:

build-flat.gradle.kts

    plugins {
        id("org.myorg.site")
    }

    site {
        outputDir = layout.buildDirectory.file("mysite")
        websiteUrl = "https://gradle.org"
        vcsUrl = "https://github.com/gradle/gradle-site-plugin"
    }

build-flat.gradle

    plugins {
        id 'org.myorg.site'
    }

    site {
        outputDir = layout.buildDirectory.file("mysite")
        websiteUrl = 'https://gradle.org'
        vcsUrl = 'https://github.com/gradle/gradle-site-plugin'
    }

As the number of exposed properties grows, you should introduce a nested, more expressive structure.

The following code snippet adds a new configuration block named `siteInfo` as part of the extension. This provides a stronger indication of what those properties mean:

build.gradle.kts

    plugins {
        id("org.myorg.site")
    }

    site {
        outputDir = layout.buildDirectory.file("mysite")

        siteInfo {
            websiteUrl = "https://gradle.org"
            vcsUrl = "https://github.com/gradle/gradle-site-plugin"
        }
    }

build.gradle

    plugins {
        id 'org.myorg.site'
    }

    site {
        outputDir = layout.buildDirectory.file("mysite")

        siteInfo {
            websiteUrl = 'https://gradle.org'
            vcsUrl = 'https://github.com/gradle/gradle-site-plugin'
        }
    }

Implementing the backing objects for such an extension is simple. First, introduce a new data object for managing the properties `websiteUrl` and `vcsUrl`:

SiteInfo.java

    abstract public class SiteInfo {

        abstract public Property<String> getWebsiteUrl();

        abstract public Property<String> getVcsUrl();
    }

In the extension, create an instance of the `siteInfo` class and a method to delegate the captured values to the data instance.

To configure underlying data objects, define a parameter of type Action.

The following example demonstrates the use of `Action` in an extension definition:

SiteExtension.java

    abstract public class SiteExtension {

        abstract public RegularFileProperty getOutputDir();

        @Nested
        abstract public SiteInfo getSiteInfo();

        public void siteInfo(Action<? super SiteInfo> action) {
            action.execute(getSiteInfo());
        }
    }

## Mapping extension properties to task properties

Plugins commonly use an extension to capture user input from the build script and map it to a custom task’s input/output properties. The build script author interacts with the extension’s DSL, while the plugin implementation handles the underlying logic:

app/build.gradle.kts

    // Extension class to capture user input
    class MyExtension {
        @Input
        var inputParameter: String? = null
    }

    // Custom task that uses the input from the extension
    class MyCustomTask : org.gradle.api.DefaultTask() {
        @Input
        var inputParameter: String? = null

        @TaskAction
        fun executeTask() {
            println("Input parameter: $inputParameter")
        }
    }

    // Plugin class that configures the extension and task
    class MyPlugin : Plugin<Project> {
        override fun apply(project: Project) {
            // Create and configure the extension
            val extension = project.extensions.create("myExtension", MyExtension::class.java)
            // Create and configure the custom task
            project.tasks.register("myTask", MyCustomTask::class.java) {
                group = "custom"
                inputParameter = extension.inputParameter
            }
        }
    }

app/build.gradle

    // Extension class to capture user input
    class MyExtension {
        @Input
        String inputParameter = null
    }

    // Custom task that uses the input from the extension
    class MyCustomTask extends DefaultTask {
        @Input
        String inputParameter = null

        @TaskAction
        def executeTask() {
            println("Input parameter: $inputParameter")
        }
    }

    // Plugin class that configures the extension and task
    class MyPlugin implements Plugin<Project> {
        void apply(Project project) {
            // Create and configure the extension
            def extension = project.extensions.create("myExtension", MyExtension)
            // Create and configure the custom task
            project.tasks.register("myTask", MyCustomTask) {
                group = "custom"
                inputParameter = extension.inputParameter
            }
        }
    }

In this example, the `MyExtension` class defines an `inputParameter` property that can be set in the build script. The `MyPlugin` class configures this extension and uses its `inputParameter` value to configure the `MyCustomTask` task. The `MyCustomTask` task then uses this input parameter in its logic.

You can learn more about types you can use in task implementations and extensions in Lazy Configuration.

## Adding default configuration with conventions

Plugins should provide sensible defaults and standards in a specific context, reducing the number of decisions users need to make. Using the `project` object, you can define default values. These are known as conventions.

Conventions are properties that are initialized with default values and can be overridden by the user in their build script. For example:

build.gradle.kts

    interface GreetingPluginExtension {
        val message: Property<String>
    }

    class GreetingPlugin : Plugin<Project> {
        override fun apply(project: Project) {
            // Add the 'greeting' extension object
            val extension = project.extensions.create<GreetingPluginExtension>("greeting")
            extension.message.convention("Hello from GreetingPlugin")
            // Add a task that uses configuration from the extension object
            project.task("hello") {
                doLast {
                    println(extension.message.get())
                }
            }
        }
    }

    apply<GreetingPlugin>()

build.gradle

    interface GreetingPluginExtension {
        Property<String> getMessage()
    }

    class GreetingPlugin implements Plugin<Project> {
        void apply(Project project) {
            // Add the 'greeting' extension object
            def extension = project.extensions.create('greeting', GreetingPluginExtension)
            extension.message.convention('Hello from GreetingPlugin')
            // Add a task that uses configuration from the extension object
            project.task('hello') {
                doLast {
                    println extension.message.get()
                }
            }
        }
    }

    apply plugin: GreetingPlugin

    $ ./gradlew -q hello

    Hello from GreetingPlugin

In this example, `GreetingPluginExtension` is a class that represents the convention. The message property is the convention property with a default value of 'Hello from GreetingPlugin'.

Users can override this value in their build script:

build.gradle.kts

    GreetingPluginExtension {
        message = "Custom message"
    }

build.gradle

    GreetingPluginExtension {
        message = 'Custom message'
    }

    $ ./gradlew -q hello

    Custom message

## Separating capabilities from conventions

Separating capabilities from conventions in plugins allows users to choose which tasks and conventions to apply.

For example, the Java Base plugin provides un-opinionated (i.e., generic) functionality like `SourceSets`, while the Java plugin adds tasks and conventions familiar to Java developers like `classes`, `jar` or `javadoc`.

When designing your own plugins, consider developing two plugins — one for capabilities and another for conventions — to offer flexibility to users.

In the example below, `MyPlugin` contains conventions, and `MyBasePlugin` defines capabilities. Then, `MyPlugin` applies `MyBasePlugin`, this is called plugin composition. To apply a plugin from another one:

MyBasePlugin.java

    import org.gradle.api.Plugin;
    import org.gradle.api.Project;

    public class MyBasePlugin implements Plugin<Project> {
        public void apply(Project project) {
            // define capabilities
        }
    }

MyPlugin.java

    import org.gradle.api.Plugin;
    import org.gradle.api.Project;

    public class MyPlugin implements Plugin<Project> {
        public void apply(Project project) {
            project.getPluginManager().apply(MyBasePlugin.class);

            // define conventions
        }
    }

## Reacting to plugins

A common pattern in Gradle plugin implementations is configuring the runtime behavior of existing plugins and tasks in a build.

For example, a plugin could assume that it is applied to a Java-based project and automatically reconfigure the standard source directory:

InhouseStrongOpinionConventionJavaPlugin.java

    public class InhouseStrongOpinionConventionJavaPlugin implements Plugin<Project> {
        public void apply(Project project) {
            // Careful! Eagerly appyling plugins has downsides, and is not always recommended.
            project.getPluginManager().apply(JavaPlugin.class);
            SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class);
            SourceSet main = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
            main.getJava().setSrcDirs(Arrays.asList("src"));
        }
    }

The drawback to this approach is that it automatically forces the project to apply the Java plugin, imposing a strong opinion on it (i.e., reducing flexibility and generality). In practice, the project applying the plugin might not even deal with Java code.

Instead of automatically applying the Java plugin, the plugin could react to the fact that the consuming project applies the Java plugin. Only if that is the case, then a certain configuration is applied:

InhouseConventionJavaPlugin.java

    public class InhouseConventionJavaPlugin implements Plugin<Project> {
        public void apply(Project project) {
            project.getPluginManager().withPlugin("java", javaPlugin -> {
                SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class);
                SourceSet main = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
                main.getJava().setSrcDirs(Arrays.asList("src"));
            });
        }
    }

Reacting to plugins is preferred over applying plugins if there is no good reason to assume that the consuming project has the expected setup.

The same concept applies to task types:

InhouseConventionWarPlugin.java

    public class InhouseConventionWarPlugin implements Plugin<Project> {
        public void apply(Project project) {
            project.getTasks().withType(War.class).configureEach(war ->
                war.getWebXmlFile().convention(project.getLayout().getProjectDirectory().file("src/someWeb.xml")));
        }
    }

## Reacting to build features

Plugins can access the status of build features in the build. The Build Features API allows checking whether the user requested a particular Gradle feature and if it is active in the current build. An example of a build feature is the configuration cache.

There are two main use cases:

- Using the status of build features in reports or statistics.

- Incrementally adopting experimental Gradle features by disabling incompatible plugin functionality.

Below is an example of a plugin that utilizes both of the cases.

Reacting to build features

    public abstract class MyPlugin implements Plugin<Project> {

        @Inject
        protected abstract BuildFeatures getBuildFeatures(); (1)

        @Override
        public void apply(Project p) {
            BuildFeatures buildFeatures = getBuildFeatures();

            Boolean configCacheRequested = buildFeatures.getConfigurationCache().getRequested() (2)
                .getOrNull(); // could be null if user did not opt in nor opt out
            String configCacheUsage = describeFeatureUsage(configCacheRequested);
            MyReport myReport = new MyReport();
            myReport.setConfigurationCacheUsage(configCacheUsage);

            boolean isolatedProjectsActive = buildFeatures.getIsolatedProjects().getActive() (3)
                .get(); // the active state is always defined
            if (!isolatedProjectsActive) {
                myOptionalPluginLogicIncompatibleWithIsolatedProjects();
            }
        }

        private String describeFeatureUsage(Boolean requested) {
            return requested == null ? "no preference" : requested ? "opt-in" : "opt-out";
        }

        private void myOptionalPluginLogicIncompatibleWithIsolatedProjects() {
        }
    }

1 | The `BuildFeatures` service can be injected into plugins, tasks, and other managed types.
2 | Accessing the `requested` status of a feature for reporting.
3 | Using the `active` status of a feature to disable incompatible functionality.

| Disabling functionality based on a feature’s `active` status makes the plugin behave differently depending on whether the feature is enabled, which raises complexity and can lead to divergent build results. Treat such conditionals as a last resort, and prefer making the functionality compatible with the feature instead. For the Isolated Projects feature specifically, see Guard incompatible build logic as a last resort.

### Build feature properties

A `BuildFeature` status properties are represented with `Provider<Boolean>` types.

The `BuildFeature.getRequested()` status of a build feature determines if the user requested to enable or disable the feature.

When the `requested` provider value is:

- `true`— the user opted in for using the feature

- `false`— the user opted out from using the feature

- `undefined`— the user neither opted in nor opted out from using the feature

The `BuildFeature.getActive()` status of a build feature is always defined. It represents the effective state of the feature in the build.

When the `active` provider value is:

- `true`— the feature may affect the build behavior in a way specific to the feature

- `false`— the feature will not affect the build behavior

Note that the `active` status does not depend on the `requested` status. Even if the user requests a feature, it may still not be active due to other build options being used in the build. Gradle can also activate a feature by default, even if the user did not specify a preference.

## Using a custom `dependencies` block

A plugin can provide dependency declarations in custom blocks that allow users to declare dependencies in a type-safe and context-aware way.

For instance, instead of users needing to know and use the underlying `Configuration` name to add dependencies, a custom `dependencies` block lets the plugin pick a meaningful name that can be used consistently.

### Adding a custom `dependencies` block

To add a custom `dependencies` block, you need to create a new type that will represent the set of dependency scopes available to users. That new type needs to be accessible from a part of your plugin (from a domain object or extension). Finally, the dependency scopes need to be wired back to underlying `Configuration` objects that will be used during dependency resolution.

See JvmComponentDependencies and JvmTestSuite for an example of how this is used in a Gradle core plugin.

#### 1. Create an interface that extends `Dependencies`

| You can also extend GradleDependencies to get access to Gradle-provided dependencies like `gradleApi()`.

ExampleDependencies.java

    /**
     * Custom dependencies block for the example plugin.
     */
    public interface ExampleDependencies extends Dependencies {

#### 2. Add accessors for dependency scopes

For each dependency scope your plugin wants to support, add a getter method that returns a `DependencyCollector`.

ExampleDependencies.java

        /**
         * Dependency scope called "implementation"
         */
        DependencyCollector getImplementation();

#### 3. Add accessors for custom `dependencies` block

To make the custom `dependencies` block configurable, the plugin needs to add a `getDependencies` method that returns the new type from above and a configurable block method named `dependencies`.

By convention, the accessors for your custom `dependencies` block should be called `getDependencies()`/`dependencies(Action)`. This method could be named something else, but users would need to know that a different block can behave like a `dependencies` block.

ExampleExtension.java

        /**
         * Custom dependencies for this extension.
         */
        @Nested
        ExampleDependencies getDependencies();

        /**
         * Configurable block
         */
        default void dependencies(Action<? super ExampleDependencies> action) {
            action.execute(getDependencies());
        }

#### 4. Wire dependency scope to `Configuration`

Finally, the plugin needs to wire the custom `dependencies` block to some underlying `Configuration` objects. If this is not done, none of the dependencies declared in the custom block will be available to dependency resolution.

ExamplePlugin.java

            project.getConfigurations().dependencyScope("exampleImplementation", conf -> {
                conf.fromDependencyCollector(example.getDependencies().getImplementation());
            });

| In this example, the name users will use to add dependencies is "implementation", but the underlying `Configuration` is named `exampleImplementation`.

build.gradle.kts

    example {
        dependencies {
            implementation("junit:junit:4.13")
        }
    }

build.gradle

    example {
        dependencies {
            implementation("junit:junit:4.13")
        }
    }

### Differences between the custom `dependencies` and the top-level `dependencies` blocks

Each dependency scope returns a `DependencyCollector` that provides strongly-typed methods to add and configure dependencies.

There is also a `DependencyFactory` with factory methods to create new dependencies from different notations. Dependencies can be created lazily using these factory methods, as shown below.

A custom `dependencies` block differs from the top-level `dependencies` block in the following ways:

- Dependencies must be declared using a `String`, an instance of `Dependency`, a `FileCollection`, a `Provider` of `Dependency`, or a `ProviderConvertible` of `MinimalExternalModuleDependency`.

- Outside of Gradle build scripts, you must explicitly call a getter for the `DependencyCollector` and `add`.

  - `dependencies.add("implementation", x)` becomes `getImplementation().add(x)`

- You cannot add a dependency with an instance of `Project`. You must turn it into a `ProjectDependency` first.

- You cannot add version catalog bundles directly. Instead, use the `bundle` method on each configuration.

  - Kotlin and Groovy: `implementation(libs.bundles.testing)` becomes `implementation.bundle(libs.bundles.testing)`

- You cannot use providers for non-`Dependency` types directly. Instead, map them to a `Dependency` using the `DependencyFactory`.

  - Kotlin and Groovy: `implementation(myStringProvider)` becomes `implementation(myStringProvider.map { dependencyFactory.create(it) })`

  - Java: `implementation(myStringProvider)` becomes `getImplementation().add(myStringProvider.map(getDependencyFactory()::create)`

- Unlike the top-level `dependencies` block, constraints are not in a separate block.

  - Instead, constraints are added by decorating a dependency with `constraint(…​)` like `implementation(constraint("org:foo:1.0"))`.

Keep in mind that the `dependencies` block may not provide access to the same methods as the top-level `dependencies` block.

| Plugins should prefer adding dependencies via their own `dependencies` block.

## Providing default dependencies

The implementation of a plugin sometimes requires the use of an external dependency.

You might want to automatically download an artifact using Gradle’s dependency management mechanism and later use it in the action of a task type declared in the plugin. Ideally, the plugin implementation does not need to ask the user for the coordinates of that dependency - it can simply predefine a sensible default version.

Let’s look at an example of a plugin that downloads files containing data for further processing. The plugin implementation declares a custom configuration that allows for assigning those external dependencies with dependency coordinates:

DataProcessingPlugin.java

    public class DataProcessingPlugin implements Plugin<Project> {
        public void apply(Project project) {
            Configuration dataFiles = project.getConfigurations().create("dataFiles", c -> {
                c.setCanBeConsumed(false);
                c.setCanBeResolved(true);
                c.setDescription("The data artifacts to be processed for this plugin.");
                c.defaultDependencies(d -> d.add(project.getDependencies().create("org.myorg:data:1.4.6")));
            });

            project.getTasks().withType(DataProcessing.class).configureEach(
                dataProcessing -> dataProcessing.getDataFiles().from(dataFiles));
        }
    }

DataProcessing.java

    abstract public class DataProcessing extends DefaultTask {

        @InputFiles
        abstract public ConfigurableFileCollection getDataFiles();

        @TaskAction
        public void process() {
            System.out.println(getDataFiles().getFiles());
        }
    }

This approach is convenient for the end user as there is no need to actively declare a dependency. The plugin already provides all the details about this implementation.

But what if the user wants to redefine the default dependency?

No problem. The plugin also exposes the custom configuration that can be used to assign a different dependency. Effectively, the default dependency is overwritten:

build.gradle.kts

    plugins {
        id("org.myorg.data-processing")
    }

    dependencies {
        dataFiles("org.myorg:more-data:2.6")
    }

build.gradle

    plugins {
        id 'org.myorg.data-processing'
    }

    dependencies {
        dataFiles 'org.myorg:more-data:2.6'
    }

You will find that this pattern works well for tasks that require an external dependency when the task’s action is executed. You can go further and abstract the version to be used for the external dependency by exposing an extension property (e.g. `toolVersion` in the JaCoCo plugin).

### Minimizing the use of external libraries

Using external libraries in your Gradle projects can bring great convenience, but be aware that they can introduce complex dependency graphs. Gradle’s `buildEnvironment` task can help you visualize these dependencies, including those of your plugins. Keep in mind that plugins share the same classloader, so conflicts may arise with different versions of the same library.

To demonstrate let’s assume the following build script:

build.gradle.kts

    plugins {
        id("org.asciidoctor.jvm.convert") version "4.0.2"
    }

build.gradle

    plugins {
        id 'org.asciidoctor.jvm.convert' version '4.0.2'
    }

The output of the task clearly indicates the classpath of the `classpath` configuration:

    $ ./gradlew buildEnvironment

    > Task :buildEnvironment

    ------------------------------------------------------------
    Root project 'external-libraries'
    ------------------------------------------------------------

    classpath
    \--- org.asciidoctor.jvm.convert:org.asciidoctor.jvm.convert.gradle.plugin:4.0.2
         \--- org.asciidoctor:asciidoctor-gradle-jvm:4.0.2
              +--- org.ysb33r.gradle:grolifant-rawhide:3.0.0
              |    \--- org.tukaani:xz:1.6
              +--- org.ysb33r.gradle:grolifant-herd:3.0.0
              |    +--- org.tukaani:xz:1.6
              |    +--- org.ysb33r.gradle:grolifant40:3.0.0
              |    |    +--- org.tukaani:xz:1.6
              |    |    +--- org.apache.commons:commons-collections4:4.4
              |    |    +--- org.ysb33r.gradle:grolifant-core:3.0.0
              |    |    |    +--- org.tukaani:xz:1.6
              |    |    |    +--- org.apache.commons:commons-collections4:4.4
              |    |    |    \--- org.ysb33r.gradle:grolifant-rawhide:3.0.0 (*)
              |    |    \--- org.ysb33r.gradle:grolifant-rawhide:3.0.0 (*)
              |    +--- org.ysb33r.gradle:grolifant50:3.0.0
              |    |    +--- org.tukaani:xz:1.6
              |    |    +--- org.ysb33r.gradle:grolifant40:3.0.0 (*)
              |    |    +--- org.ysb33r.gradle:grolifant-core:3.0.0 (*)
              |    |    \--- org.ysb33r.gradle:grolifant40-legacy-api:3.0.0
              |    |         +--- org.tukaani:xz:1.6
              |    |         +--- org.apache.commons:commons-collections4:4.4
              |    |         +--- org.ysb33r.gradle:grolifant-core:3.0.0 (*)
              |    |         \--- org.ysb33r.gradle:grolifant40:3.0.0 (*)
              |    +--- org.ysb33r.gradle:grolifant60:3.0.0
              |    |    +--- org.tukaani:xz:1.6
              |    |    +--- org.ysb33r.gradle:grolifant40:3.0.0 (*)
              |    |    +--- org.ysb33r.gradle:grolifant50:3.0.0 (*)
              |    |    +--- org.ysb33r.gradle:grolifant-core:3.0.0 (*)
              |    |    \--- org.ysb33r.gradle:grolifant-rawhide:3.0.0 (*)
              |    +--- org.ysb33r.gradle:grolifant70:3.0.0
              |    |    +--- org.tukaani:xz:1.6
              |    |    +--- org.ysb33r.gradle:grolifant40:3.0.0 (*)
              |    |    +--- org.ysb33r.gradle:grolifant50:3.0.0 (*)
              |    |    +--- org.ysb33r.gradle:grolifant60:3.0.0 (*)
              |    |    \--- org.ysb33r.gradle:grolifant-core:3.0.0 (*)
              |    +--- org.ysb33r.gradle:grolifant80:3.0.0
              |    |    +--- org.tukaani:xz:1.6
              |    |    +--- org.ysb33r.gradle:grolifant40:3.0.0 (*)
              |    |    +--- org.ysb33r.gradle:grolifant50:3.0.0 (*)
              |    |    +--- org.ysb33r.gradle:grolifant60:3.0.0 (*)
              |    |    +--- org.ysb33r.gradle:grolifant70:3.0.0 (*)
              |    |    \--- org.ysb33r.gradle:grolifant-core:3.0.0 (*)
              |    +--- org.ysb33r.gradle:grolifant-core:3.0.0 (*)
              |    \--- org.ysb33r.gradle:grolifant-rawhide:3.0.0 (*)
              +--- org.asciidoctor:asciidoctor-gradle-base:4.0.2
              |    \--- org.ysb33r.gradle:grolifant-herd:3.0.0 (*)
              \--- org.asciidoctor:asciidoctorj-api:2.5.7

    (*) - Indicates repeated occurrences of a transitive dependency subtree. Gradle expands transitive dependency subtrees only once per project; repeat occurrences only display the root of the subtree, followed by this annotation.

    A web-based, searchable dependency report is available by adding the --scan option.

    BUILD SUCCESSFUL in 0s
    1 actionable task: 1 executed

A Gradle plugin does not run in its own, isolated classloader, so you must consider whether you truly need a library or if a simpler solution suffices.

For logic that is executed as part of task execution, use the Worker API that allows you to isolate libraries.

## Providing multiple variants of a plugin

Variants of a plugin refer to different flavors or configurations of the plugin that are tailored to specific needs or use cases. These variants can include different implementations, extensions, or configurations of the base plugin.

The most convenient way to configure additional plugin variants is to use feature variants, a concept available in all Gradle projects that apply one of the Java plugins:

    dependencies {
        implementation 'com.google.guava:guava:30.1-jre'        // Regular dependency
        featureVariant 'com.google.guava:guava-gwt:30.1-jre'    // Feature variant dependency
    }

In the following example, each plugin variant is developed in isolation. A separate source set is compiled and packaged in a separate jar for each variant.

The following sample demonstrates how to add a variant that is compatible with Gradle 7.0+ while the "main" variant is compatible with older versions:

build.gradle.kts

    val gradle7 = sourceSets.create("gradle7")

    java {
        registerFeature(gradle7.name) {
            usingSourceSet(gradle7)
            capability(project.group.toString(), project.name, project.version.toString()) (1)
        }
    }

    configurations.configureEach {
        if (isCanBeConsumed && name.startsWith(gradle7.name))  {
            attributes {
                attribute(GradlePluginApiVersion.GRADLE_PLUGIN_API_VERSION_ATTRIBUTE, (2)
                    objects.named("7.0"))
            }
        }
    }

    tasks.named<Copy>(gradle7.processResourcesTaskName) { (3)
        val copyPluginDescriptors = rootSpec.addChild()
        copyPluginDescriptors.into("META-INF/gradle-plugins")
        copyPluginDescriptors.from(tasks.pluginDescriptors)
    }

    dependencies {
        "gradle7CompileOnly"(gradleApi()) (4)
    }

build.gradle

    def gradle7 = sourceSets.create('gradle7')

    java {
        registerFeature(gradle7.name) {
            usingSourceSet(gradle7)
            capability(project.group.toString(), project.name, project.version.toString()) (1)
        }
    }

    configurations.configureEach {
        if (canBeConsumed && name.startsWith(gradle7.name))  {
            attributes {
                attribute(GradlePluginApiVersion.GRADLE_PLUGIN_API_VERSION_ATTRIBUTE, (2)
                          objects.named(GradlePluginApiVersion, '7.0'))
            }
        }
    }

    tasks.named(gradle7.processResourcesTaskName) { (3)
        def copyPluginDescriptors = rootSpec.addChild()
        copyPluginDescriptors.into('META-INF/gradle-plugins')
        copyPluginDescriptors.from(tasks.pluginDescriptors)
    }

    dependencies {
        gradle7CompileOnly(gradleApi()) (4)
    }

1 | Assign the implicit capability that corresponds to the components GAV to the variant.
2 | Assign the Gradle API version attribute to all consumable configurations of our Gradle7 variant. Gradle uses this information to determine which variant to select during plugin resolution.
3 | Configure the `processGradle7Resources` task to ensure the plugin descriptor file is added to the Gradle7 variant Jar.
4 | Add a dependency to the `gradleApi()` for our new variant so that the API is visible during compilation time.

The sample declares a separate source set and a feature variant for our Gradle 7 plugin variant, then wires the feature into a proper Gradle plugin variant.

| Only Gradle versions 7 or higher can be explicitly targeted by a variant, as support for this was only added in Gradle 7.

Note that there is currently no convenient way to access the API of other Gradle versions as the one you are building the plugin with. Ideally, every variant should be able to declare a dependency on the API of the minimal Gradle version it supports. This will be improved in the future.

The above snippet assumes that all variants of your plugin have the plugin class at the same location. That is, if your plugin class is `org.example.GreetingPlugin`, you need to create a second variant of that class in `src/gradle7/java/org/example`.

### Using version-specific variants of multi-variant plugins

Given a dependency on a multi-variant plugin, Gradle will automatically choose its variant that best matches the current Gradle version when it resolves any of:

- plugins specified in the `plugins {}` block;

- `buildscript` classpath dependencies;

- dependencies in the root project of the build source (`buildSrc`) that appear on the compile or runtime classpath;

- dependencies in a project that applies the Java Gradle Plugin Development plugin or the Kotlin DSL plugin, appearing on the compile or runtime classpath.

The best matching variant is the variant that targets the highest Gradle API version and does not exceed the current build’s Gradle version.

In all other cases, a plugin variant that does not specify the supported Gradle API version is preferred if such a variant is present.

In projects that use plugins as dependencies, requesting the variants of plugin dependencies that support a different Gradle version is possible. This allows a multi-variant plugin that depends on other plugins to access their APIs, which are exclusively provided in their version-specific variants.

This snippet makes the plugin variant `gradle7` defined above consume the matching variants of its dependencies on other multi-variant plugins:

build.gradle.kts

    configurations.configureEach {
        if (isCanBeResolved && name.startsWith(gradle7.name))  {
            attributes {
                attribute(GradlePluginApiVersion.GRADLE_PLUGIN_API_VERSION_ATTRIBUTE,
                    objects.named("7.0"))
            }
        }
    }

build.gradle

    configurations.configureEach {
        if (canBeResolved && name.startsWith(gradle7.name))  {
            attributes {
                attribute(GradlePluginApiVersion.GRADLE_PLUGIN_API_VERSION_ATTRIBUTE,
                    objects.named(GradlePluginApiVersion, '7.0'))
            }
        }
    }

## Avoiding plugin dependency conflicts

When developing a plugin that relies on the API of another plugin (e.g., the Android Gradle Plugin or the Kotlin Plugin), declaring that API dependency using the standard `implementation` configuration can cause serious classpath conflicts for the end user.

When applying your plugin to a project, the dependency you used may spill into the build script classpath of the project’s subprojects, potentially overriding a newer version of the dependency required there.

To prevent buildscript classpath leakage and maintain version flexibility for your users, use the `compileOnly` configuration for any dependency that represents an underlying plugin’s API.

    dependencies {
        // BAD: Can spill onto subproject classpaths, potentially overriding their version
        // implementation("com.android.tools.build:gradle-api:8.0.0")

        // GOOD: Only available during compilation; user's project dictates the runtime version
        compileOnly("com.android.tools.build:gradle-api:8.0.0")
    }

If you need to enforce a specific version (e.g., for internal Convention Plugins), the `implementation` approach may be acceptable, but this should be done with clear awareness of the potential user conflicts.

Because `compileOnly` dependencies are not available at runtime, plugin authors should:

1. Document that the plugin requires the other plugin to be explicitly applied by the user.

2. Implement the plugin defensively to avoid throwing `NoClassDefFoundError` when the dependency is missing. Use `PluginManager.withPlugin(String, Action)` to guard code that references classes from the `compileOnly` dependency:

       project.pluginManager.withPlugin("com.android.application") {
           // Safe to reference Android plugin classes here
       }

| Using the `compileOnly` configuration will cause the standard plugin testing method, `GradleRunner.withPluginClasspath()`, to fail. See Automatic classpath injection with the Plugin Development Plugin to learn more.