# Sharing build logic between subprojects 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 in a multi-project build can be organized into reusable plugins.

## Use case

As an example, let’s say a project with three subprojects produces two public Java libraries that use the third subproject as an internal shared library. This is the project structure:

Project structure

    ├── internal-module
    │   └── build.gradle.kts
    ├── library-a
    │   ├── build.gradle.kts
    │   └── README.md
    ├── library-b
    │   ├── build.gradle.kts
    │   └── README.md
    └── settings.gradle.kts

Project structure

    ├── internal-module
    │   └── build.gradle
    ├── library-a
    │   ├── build.gradle
    │   └── README.md
    ├── library-b
    │   ├── build.gradle
    │   └── README.md
    └── settings.gradle

Let’s say that all our projects will be Java projects. In this case we want to apply a common set of rules to all of them, such as source directory layout, compiler flags, code style conventions, code quality checks and so on.

Two out of three projects are more than just Java projects - they are libraries that we perhaps want to publish to an external repository. Publishing configuration, such as a common group name for the libraries as well as the repository coordinates might be a cross-cutting concern that both libraries need to share. For this example let’s also say that we want to enforce that our libraries expose some documentation with a common structure.

## Organizing build logic

From the use case above, we have identified that we have two types of projects - generic Java projects and public libraries. We can model this use case by layering two separate plugins that each define the type of project that applies them:

Build logic layout

    ├── buildSrc
    │   ├── build.gradle.kts
    │   ├── settings.gradle.kts
    │   ├── src
    │   │   ├── main
    │   │   │   └── kotlin
    │   │   │       ├── myproject.java-conventions.gradle.kts
    │   │   │       └── myproject.library-conventions.gradle.kts
    ...

Build logic layout

    ├── buildSrc
    │   ├── build.gradle
    │   ├── settings.gradle
    │   ├── src
    │   │   ├── main
    │   │   │   └── groovy
    │   │   │       ├── myproject.java-conventions.gradle
    │   │   │       └── myproject.library-conventions.gradle
    ...

- `myproject.java-conventions` - configures conventions that are generic for any Java project in the organization. It applies the core `java` and `checkstyle` plugins as well as an external `com.github.spotbugs` plugin, configures common compiler options as well as code quality checks.

- `myproject.library-conventions` - adds publishing configuration to publish to the organization’s repository and checks for mandatory content in a README. It applies `java-library` and `maven-publish` plugins as well as the `myproject.java-conventions` plugin.

The internal library subproject applies `myproject.java-conventions` plugin:

internal-module/build.gradle.kts

    plugins {
        id("myproject.java-conventions")
    }

    dependencies {
        // internal module dependencies
    }

internal-module/build.gradle

    plugins {
        id 'myproject.java-conventions'
    }

    dependencies {
        // internal module dependencies
    }

The two public library subprojects apply `myproject.library-conventions` plugin.

library-a/build.gradle.kts

    plugins {
        id("myproject.library-conventions")
    }

    dependencies {
        implementation(project(":internal-module"))
    }

library-b/build.gradle.kts

    plugins {
        id("myproject.library-conventions")
    }

    dependencies {
        implementation(project(":internal-module"))
    }

library-a/build.gradle

    plugins {
        id 'myproject.library-conventions'
    }

    dependencies {
        implementation project(':internal-module')
    }

library-b/build.gradle

    plugins {
        id 'myproject.library-conventions'
    }

    dependencies {
        implementation project(':internal-module')
    }

Note how applying a convention plugin to a subproject effectively declares its type. By applying `myproject.java-conventions` plugin we state: this is a "Java" project. By applying `myproject.library-conventions` plugin we state: this is a "Library" project.

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

This sample does not have any project source code and only lays out a hypothetical project structure where two library subprojects depend on a shared internal subproject.

## 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 `buildSrc` project needs to apply the `kotlin-dsl` plugin in its `build.gradle.kts` file:

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

Example 1. Enabling precompiled script plugins
buildSrc/build.gradle.kts

    plugins {
        `kotlin-dsl`
    }

buildSrc/build.gradle

    plugins {
        id 'groovy-gradle-plugin'
    }

## Things to note

### Applying an external plugin in precompiled script plugin

The `myproject.java-conventions` plugin uses 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 precompiled script plugin:

buildSrc/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")
    }

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

    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 precompiled script plugin by id:

buildSrc/src/main/kotlin/myproject.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")
    }

buildSrc/src/main/groovy/myproject.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 precompiled script plugins

Precompiled script plugins can apply other precompiled script plugins.

The `myproject.library-conventions` plugin applies the `myproject.java-conventions` plugin:

buildSrc/src/main/kotlin/myproject.library-conventions.gradle.kts

    plugins {
        `java-library`
        `maven-publish`
        id("myproject.java-conventions")
    }

buildSrc/src/main/groovy/myproject.library-conventions.gradle

    plugins {
        id 'java-library'
        id 'maven-publish'
        id 'myproject.java-conventions'
    }

### Using classes from the main source set

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

In this sample, `myproject.library-conventions` plugin uses a custom task class from `buildSrc/src/main/java` to configure library README checks:

buildSrc/src/main/kotlin/myproject.library-conventions.gradle.kts

    val readmeCheck by tasks.registering(com.example.ReadmeVerificationTask::class) {
        readme = layout.projectDirectory.file("README.md")
        readmePatterns = listOf("^## API$", "^## Changelog$")
    }

buildSrc/src/main/groovy/myproject.library-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 = ['^## API$', '^## Changelog$']
    }

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