# Sharing build logic in a multi-repo setup Sample

version 9.0.0

- Groovy DSL

- Kotlin DSL

| 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:

Example 1. Enabling precompiled script plugins
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.

Example 2. Publishing configuration
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.3-groovy-4.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.