Sharing build logic in a multi-repo setup Sample
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:
├── 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
...
├── 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:
plugins {
`kotlin-dsl`
}
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.
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")
}
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:
pluginManagement {
repositories {
gradlePluginPortal()
maven {
// replace the path with the actual path to the repository
url = uri("<path-to>/convention-plugins/build/repo")
}
}
}
plugins {
id("com.myorg.service-conventions") version "1.0"
}
pluginManagement {
repositories {
gradlePluginPortal()
maven {
// replace the path with the actual path to the repository
url = uri('<path-to>/convention-plugins/build/repo')
}
}
}
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:
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")
}
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:
plugins {
java
checkstyle
// NOTE: external plugin version is specified in implementation dependency artifact of the project's build file
id("com.github.spotbugs")
}
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:
plugins {
`java-library`
`maven-publish`
id("com.myorg.java-conventions")
}
plugins {
id("com.myorg.java-conventions")
}
plugins {
id 'java-library'
id 'maven-publish'
id 'com.myorg.java-conventions'
}
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:
val readmeCheck by tasks.registering(com.example.ReadmeVerificationTask::class) {
readme = layout.projectDirectory.file("README.md")
readmePatterns = listOf("^## Service API$")
}
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.