You can open this sample in an IDE that supports Gradle.

This sample shows how build logic can be organized into reusable pieces and published to a repository for reuse in other projects for multi-repo setups.

There is also a new sample demonstrating how to use the incubating Test Suite Plugin in this scenario.

Use case

As an example, let’s say an organization produces two types of Java software - services and libraries. We want to apply a set of code quality checking rules to both types of projects and configure some aspects specific to each type.

Organizing build logic

The use case can be modelled by layering three separate plugins:

Build logic layout
├── convention-plugins
│   ├── build.gradle.kts
│   ├── settings.gradle.kts
│   ├── src
│   │   ├── main
│   │   │   └── kotlin
│   │   │       ├── com.myorg.java-conventions.gradle.kts
│   │   │       ├── com.myorg.library-conventions.gradle.kts
│   │   │       └── com.myorg.service-conventions.gradle.kts
...
Build logic layout
├── convention-plugins
│   ├── build.gradle
│   ├── settings.gradle
│   ├── src
│   │   ├── main
│   │   │   └── groovy
│   │   │       ├── com.myorg.java-conventions.gradle
│   │   │       ├── com.myorg.library-conventions.gradle
│   │   │       └── com.myorg.service-conventions.gradle
...
  • com.myorg.java-conventions - configures conventions that are generic for any Java project in the organization. This applies for both types of previously identified software and thus this plugin will be applied in both subsequent plugins.

  • com.myorg.library-conventions - adds publishing configuration to publish to the organization’s repository and configures mandatory documentation checks.

  • com.myorg.service-conventions - configures integration tests and checks for mandatory content in a README. Since services differ from libraries, different requirements for documentation are configured in this case.

All plugins created in this sample contain functional tests that use TestKit to verify their behavior.

Compiling convention plugins

In this sample, convention plugins are implemented as precompiled script plugins - this is the simplest way to start out as you can use one of Gradle’s DSLs directly to implement the build logic, just as if the plugin was a regular build script.

In order for precompiled script plugins to be discovered, the convention-plugins project needs to apply the groovy-gradle-plugin plugin in its build.gradle file:

In order for precompiled script plugins to be discovered, the convention-plugins project needs to apply the kotlin-dsl plugin in its build.gradle.kts file:

convention-plugins/build.gradle.kts
plugins {
    `kotlin-dsl`
}
convention-plugins/build.gradle
plugins {
    id 'groovy-gradle-plugin'
}

Publishing convention plugins

In this sample we are targeting a multi-repo setup. In order to apply the above plugins to separate projects, they have to be published to a company’s artifact repository. Convention plugins are regular Gradle plugins - thus they can be published to an external repository like any other Gradle plugin.

Here, we configure the project to publish the plugins using the maven-publish plugin. For demonstration purposes, we publish to a local filesystem directory. You can find information about how to publish to a remote repository in the repositories section of the maven-publish plugin.

convention-plugins/build.gradle.kts
plugins {
    `kotlin-dsl`
    `maven-publish`
}

group = "com.myorg.conventions"
version = "1.0"

publishing {
    repositories {
        maven {
            // change to point to your repo, e.g. http://my.org/repo
            url = uri(layout.buildDirectory.dir("repo"))
        }
    }
}

tasks.publish {
    dependsOn("check")
}
convention-plugins/build.gradle
plugins {
    id 'groovy-gradle-plugin'
    id 'maven-publish'
}

group = 'com.myorg.conventions'
version = '1.0'

publishing {
    repositories {
        maven {
            // change to point to your repo, e.g. http://my.org/repo
            url = layout.buildDirectory.dir('repo')
        }
    }
}

tasks.named('publish') {
    dependsOn('check')
}

The plugins can be published using:

./gradlew publish

In order to consume them in another project, configure the plugins repository in the settings file and apply the plugin:

settings.gradle.kts
pluginManagement {
    repositories {
        gradlePluginPortal()
        maven {
            // replace the path with the actual path to the repository
            url = uri("<path-to>/convention-plugins/build/repo")
        }
    }
}
build.gradle.kts
plugins {
    id("com.myorg.service-conventions") version "1.0"
}
settings.gradle
pluginManagement {
    repositories {
        gradlePluginPortal()
        maven {
            // replace the path with the actual path to the repository
            url = uri('<path-to>/convention-plugins/build/repo')
        }
    }
}
build.gradle
plugins {
    id 'com.myorg.service-conventions' version '1.0'
}

Things to note

Applying an external plugin in convention plugin

The com.myorg.java-conventions plugin uses the SpotBugs plugin to perform static code analysis.

SpotBugs is an external plugin - external plugins need to be added as implementation dependencies before they can be applied in a convention plugin:

convention-plugins/build.gradle.kts
repositories {
    gradlePluginPortal() // so that external plugins can be resolved in dependencies section
}

dependencies {
    implementation("com.github.spotbugs.snom:spotbugs-gradle-plugin:5.2.1")
    testImplementation("junit:junit:4.13")
}
convention-plugins/build.gradle
repositories {
    gradlePluginPortal() // so that external plugins can be resolved in dependencies section
}

dependencies {
    implementation 'com.github.spotbugs.snom:spotbugs-gradle-plugin:5.2.1'
    testImplementation platform("org.spockframework:spock-bom:2.2-groovy-3.0")
    testImplementation 'org.spockframework:spock-core'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

tasks.named('test', Test) {
    useJUnitPlatform()
}
  • The dependency artifact coordinates (GAV) for a plugin can be different from the plugin id.

  • The Gradle Plugin Portal (gradlePluginPortal()) is added as a repository for plugin dependencies.

  • The plugin version is determined from the dependency version.

Once the dependency is added, the external plugin can be applied in a convention plugin by id:

convention-plugins/src/main/kotlin/com.myorg.java-conventions.gradle.kts
plugins {
    java
    checkstyle

    // NOTE: external plugin version is specified in implementation dependency artifact of the project's build file
    id("com.github.spotbugs")
}
convention-plugins/src/main/groovy/com.myorg.java-conventions.gradle
plugins {
    id 'java'
    id 'checkstyle'

    // NOTE: external plugin version is specified in implementation dependency artifact of the project's build file
    id 'com.github.spotbugs'
}

Applying other convention plugins

Convention plugins can apply other convention plugins.

The com.myorg.library-conventions and com.myorg.service-conventions plugins both apply the com.myorg.java-conventions plugin:

convention-plugins/src/main/kotlin/com.myorg.library-conventions.gradle.kts
plugins {
    `java-library`
    `maven-publish`
    id("com.myorg.java-conventions")
}
convention-plugins/src/main/kotlin/com.myorg.service-conventions.gradle.kts
plugins {
    id("com.myorg.java-conventions")
}
convention-plugins/src/main/groovy/com.myorg.library-conventions.gradle
plugins {
    id 'java-library'
    id 'maven-publish'
    id 'com.myorg.java-conventions'
}
convention-plugins/src/main/groovy/com.myorg.service-conventions.gradle
plugins {
    id 'com.myorg.java-conventions'
}

Using classes from the main source set

Convention plugins can use classes defined in the main source set of the plugins project.

In this sample, com.myorg.service-conventions plugin uses a custom task class from src/main/java to configure service README checks:

convention-plugins/src/main/kotlin/com.myorg.service-conventions.gradle.kts
val readmeCheck by tasks.registering(com.example.ReadmeVerificationTask::class) {
    readme = layout.projectDirectory.file("README.md")
    readmePatterns = listOf("^## Service API$")
}
convention-plugins/src/main/groovy/com.myorg.service-conventions.gradle
def readmeCheck = tasks.register('readmeCheck', com.example.ReadmeVerificationTask) {
    // Expect the README in the project directory
    readme = layout.projectDirectory.file("README.md")
    // README must contain a Service API header
    readmePatterns = ['^## Service API$']
}

For more details on authoring custom Gradle plugins, consult the user manual.