Kickstart your plugin development with a documented example
A practical guide to writing, testing and publishing custom Gradle plugins, complete with a demo repository for a running start!
Intro
Plugins are a great way of distributing Gradle functionality, whether they be public plugins offering tool integrations, like the Artifactory plugin, or corporate plugins sharing common tasks internally, like those we help our customers develop.
Unfortunately, I found the documentation and examples on writing a custom Gradle plugin to be somewhat scattered. I decided to write a very basic Gradle plugin as a bootstrap, intended to give me, and now you, a running start when developing a custom Gradle plugin. It contains Hello, world! tasks, tests and the means of publishing. I wrote this blog in tandem, as a form of documentation. Enjoy!
The plugin repository
You can find the Gradle plugin bootstrap on GitHub at Praqma/gradle-plugin-bootstrap. It’s a fully functional Gradle plugin. Feel free to use it as a starting point, just clone it and edit it to suit your needs.
The bits and pieces
I’ll briefly go over the individual pieces that makes up a Gradle plugin. You’ll find everything mentioned in the repository, but I’ll cover them in greater detail here than I could in the comments.
The entry point
Example found in:
src/main/groovy/com/praqma/demo/DemoPlugin.groovy
This is the heart of the plugin. You’ll find the apply
method here, as it implements org.gradle.api.Plugin
. Gradle calls this method when it applies your plugin to a project. This is where you can add your tasks, extensions, and so forth.
In the example plugin, you’ll find I moved such things to a separate module. This is only to prevent the main plugin class from exploding in size. Think of it as a recommendation when you’re expecting to add many different tasks to your plugin.
Registering the entry point
Example found in:
build.gradle
Before Gradle can apply your plugin, you’ll have to tell it where it can find it. Do this by applying and configuring the java-gradle-plugin
Gradle plugin in the build.gradle file. Apply the plugin through the plugins
closure at the top of your build.gradle file:
plugins {
id 'java-gradle-plugin'
}
Configure your plugin in the gradlePlugin
closure, provided by the java-gradle-plugin
. In the plugins
closure, you can add an entry for each plugin in the project. We keep it simple and only have one plugin, so we add an arbitrarily named closure under it to configure our plugin and set two properties:
-
The
id
, is the identifier of your plugin, which is used to apply it your plugin:plugins {
id: 'com.praqma.demo'
} -
The
implementationClass
points to the entry point’s class, so Gradle can find it. Which, in this case, iscom.praqma.demo.DemoPlugin
.
The complete configuration looks something like this:
gradlePlugin {
plugins {
demoPlugin {
id = 'com.praqma.demo'
implementationClass = 'com.praqma.demo.DemoPlugin'
}
}
}
Adding tasks
Example found in:
src/main/groovy/com/praqma/demo/greeting/GreetingModule.groovy
I added two tasks here, one showcasing the use of properties from project extensions, the other using project properties. Adding tasks to the plugin differs very little from adding tasks on a regular Gradle project, just call the task
method on the project.
Again, splitting tasks into modules is by no means required, it’s just a habit of mine to prevent the plugin class from devolving into a two thousand line abomination.
Adding task types
Example found in:
src/main/groovy/com/praqma/demo/greeting/GreetingTask.groovy
src/main/groovy/com/praqma/demo/greeting/GreetingModule.groovy
Creating a custom task type allows plugin users to base their custom tasks off yours, much like how we base our tasks off the Zip
and Copy
tasks. To distribute these, simply add have these task classes be a part of your plugin. Just having them there will allow users to define tasks of that type by using its fully qualified name. For example:
import com.praqma.demo.greeting.GreetingTask
task myGreetingTask(type:GreetingTask) {
message = "Howdy"
}
To prevent your users from having to import the task, add the task class to the project’s ExtraPropertiesExtension
when applying the plugin. With this clever trick, your users can access your task by the name you specify here, without having to import it.
project.ext.GreetingTask = com.praqma.demo.greeting.GreetingTask
Adding extensions
Example found in:
src/main/groovy/com/praqma/demo/greeting/GreetingExtension.groovy
src/main/groovy/com/praqma/demo/greeting/GreetingModule.groovy
Extensions expose properties that plugin users can set. They’re great for allowing users to configure values you rely on in the tasks. In the demo plugin, I allow users to configure the greeting used in the helloWorld
task by setting the greeting.message
property in their build.gradle file.
To add your own extensions to your plugin, create a simple class containing some properties, and add it as a project extension in your plugin’s apply
method:
project.extensions.create('greeting', GreetingExtension)
These properties are accessible from the project’s extensions, for example:
project.extensions.<extensionName>.<propertyName>
Testing the plugin
I’ll not dive into unit testing the Gradle plugin, as it’s no different from unit testing in a regular Groovy project, and there are many great resources available on that topic (See the resource section below). I will, however, cover functional tests and how to test your plugin with a local publish.
Functional tests
Example found in:
src/test/groovy/com/praqma/demo/greeting/GreetingModuleTest.groovy
Writing functional tests for a Gradle plugin is child’s play, thanks to the GradleRunner
. Using JUnit’s @Rule
and @Before
annotations makes it trivial to, for each test, set up a temporary directory containing a build.gradle file which applies your plugin. The GradleRunner
adds your plugin to the temporary project’s classpath, so it can actually apply your plugin under test. Running your tasks through the GradleRunner
grants you access to the build result and text output. This, combined with access to the temporary directory, allows you to check and assert everything ran as expected.
Publishing and testing on your local machine
It is possible to publish your plugin to your machine’s local maven repository. This allows you to apply your local test publication to a Gradle project on your machine, so you can test your changes without publishing them to a shared repository. It requires a tad of configuration, but it’s fairly straightforward.
Publishing to the local maven repository
Apply the maven-publish
plugin, which contains the publishToMavenLocal
task. This task publishes all your defined publications to your local repository, so let’s set up a publication for our plugin:
publishing {
publications {
pluginPublication (MavenPublication) {
from components.java
groupId project.group
artifactId "demo"
version project.version
}
}
}
To publish the plugin to your local repository in its current state, call gradle publishToMavenLocal
.
Applying a locally published plugin
To apply a locally published plugin to your project, you’ll have to add your local maven repository as a trusted repository in your Gradle project. Luckily, Gradle allows you to do this by calling mavenLocal()
in the repository
closure in your build.gradle file. For example:
buildscript {
repositories {
mavenLocal()
}
dependencies {
classpath "com.praqma:demo:1.0.0"
}
}
apply plugin: 'com.praqma.demo.DemoPlugin'
Now you can run your Gradle builds, and your project will use your local distribution of the plugin.
Distributing the plugin
I’ll discuss publishing both to the public Gradle plugin repository, as well as to an arbitrary Artifactory server. The project contains the necessary configuration for both. Just delete the one you won’t end up using, and tweak the one you will be.
Via the Gradle plugin repository
Configuration
Example found in:
build.gradle
Apply the com.gradle.plugin-publish
plugin to your plugin project. Configure it through the pluginBundle
closure, where you supply useful information to about your plugin, such as the repository location, a description and relevant tags.
pluginBundle {
website = 'https://github.com/Praqma/gradle-plugin-bootstrap'
vcsUrl = 'scm:git@github.com:Praqma/gradle-plugin-bootstrap.git'
tags = ['demo', 'example', 'quickstart']
plugins {
demoPlugin {
id = 'com.praqma.demo.DemoPlugin'
displayName = 'Gradle Multi Git plugin'
description = 'Demo plugin to use as a starting point for custom plugin development'
}
}
}
Publishing
The com.gradle.plugin-publish
plugin supplies the necessary tasks to publish your plugin to the plugin portal.
Run gradle login
in your plugin repository and follow instructions to authorize the machine to publish your plugin.
Run gradle publishPlugins
to actually publish your plugin to the portal.
Applying
Once the plugin has been published, it can be applied to other gradle projects through the plugins
closure, using the plugin’s id and version. For example:
plugins {
id 'com.praqma.demo.DemoPlugin', version '1.0.0'
}
Via Artifactory
Configuration
Example found in:
build.gradle
Apply and configure the com.jfrog.artifactory
plugin. In the artifactory
closure, you’ll configure everything the plugin needs to publish the plugin to your Artifactory server. For example:
artifactory {
contextUrl = "http://devops.acmeindustries.com/artifactory"
publish {
repository {
repoKey = 'plugins-release'
username = 'joe'
password = 's3cr3t-p4ss'
maven = true
}
defaults{
publications("pluginPublication") // Publication defined below
}
}
}
We still need to define what we’re publishing, so apply the maven-publish
plugin, allowing you to declare a MavenPublication
that contains our plugin. For example:
publishing {
publications {
pluginPublication (MavenPublication) {
from components.java
groupId project.group
artifactId "demo"
version project.version
}
}
}
Publishing
Publish your plugin by calling gradle artifactoryPublish
.
Applying
To apply the plugin, you’ll have to fetch it first. Register your Artifactory server as a repository, and add your plugin as a dependency. Then apply it like you would any other plugin. Here’s an example configuration from a project applying our plugin:
buildscript {
repositories {
maven {
url = "http://devops.acmeindustries.com/artifactory/plugins-release"
credentials {
username = "joe"
password = "s3cr3t-p4ss"
}
}
}
dependencies {
classpath "com.praqma:demo:1.0.0"
}
}
apply plugin: "com.praqma.demo.DemoPlugin" // Apply with the plugin id
Closing comments
This should cover the basics of getting a custom Gradle plugin up and running. I hope this helps you writing and distributing your own cool Gradle plugins. Thoughts, questions and suggestions are very welcome, just leave them in the comment section below!
References
- gradle.org - Writing Custom Plugins
- gradle.org - The Gradle TestKit
- gradle.org - Writing Custom Tasks
- gradle.org - ExtraPropertiesExtension
- gradle.org - How do I add my plugin to the plugin portal?
- gradle.org - java-gradle-plugin User Guide
- mrhaki.blogspot.se - Gradle Goodness: Define a short plugin id for custom plugins
- jfrog.com - Gradle Artifactory Plugin
- tutorialspoint.com . Groovy unit testing
- groovy-lang.org - Core Testing Guide
- Various references are also linked to in the source documentation
Published: Jul 14, 2017
Updated: Nov 5, 2024