Custom Gradle plugin in Java

We, as busy Android developer, all know this kind of tasks. These tasks, which are there in every project and just need to be done. Did you know, that it’s super simple to write a custom Gradle plugin to automate some of these tasks? You even don’t need to learn Groovy for this.

This article provides a quick overview on how to write a custom Gradle plugin in Java.

Project setup

To start writing a custom Gradle plugin just create a standard Gradle project and apply this plugin in your build.gradle:

apply plugin: 'java-gradle-plugin'

This plugin conveniently does a couple of things for you. It adds Java support and the Gradle API. It furthermore helps you to setup plugin metadata and (the most complex part) setup the Gradle TestKit.

Gradle uses certain metadata in order to pick up its plugins. Thanks to the aforementioned plugin, configuring this metadata is quite straightforward. Just add this closure to your build.gradle:

gradlePlugin {
plugins {
yourPluginName {
id = 'your-plugin-id'
implementationClass = 'your.pkg.YourPlugin'
}
}
}

That’s nearly pretty much it. There are only two things missing: the implementing plugin class and the implementing task class. Thanks to the java-gradle-plugin plugin these classes can be written in Java:

public class YourPlugin implements Plugin<Project> {
static final String TASK_NAME = "yourTask";

@Override
public void apply(Project target) {
target.getTasks().create(TASK_NAME, YourTask.class);
}
}

Now your custom plugin adds a task to the project, where it’ll be applied to. When executing “gradle yourTask” the code implemented in YourTask.class will be executed:

public class YourTask extends DefaultTask {

@TaskAction
public void yourTask() throws Exception {
//do your stuff
}
}

To tell Gradle which method should be executed just annotate it with @TaskAction. That’s it. That’s the basic project setup to write a custom Gradle plugin.

Project structure

The previous section described the basic project setup how to write a custom Gradle Plugin in Java.

I recommend to wrap all your implementation logic into dedicated Java classes. This classes should then be used by the previously shown task action method. That way it’s easier to test and maintain your code.

The resulting project structure of your custom Gradle plugin could exemplarily look like this:

|src/
|-main
|--java/your.pkg/
|---internal/
|----TaskImpl.java
|---YourTask.java
|---YourPlugin.java
|-test
|--java/your.pkg/
|---internal/
|----TaskImplTest.java
|---YourPluginTest.java
|build.gradle

There are four important parts. First, the build.gradle which configures the project and plugin. Second, the code which is needed by Gradle to execute your code (YourPlugin.java and YourTask.java). Third, the actual implementation of your plugin logic (TaskImpl.java). And last but not least there are the tests for your plugin.

There are two different types of tests which will be described in the next section.

Testing

Testing is a crucial factor for good software quality. It’s maybe even more crucial for a Gradle plugin which will be applied on other (people’s) projects. So it’s important to test your custom Gradle plugin.

As already mentioned in the previous section there are two types of tests.

Unit test

The class TaskImplTest holds standard JUnit tests. That’s why I recommend to decouple the actual implementation logic from the “plugin configuration code” required by Gradle.

I assume you all know how to test your code with JUnit so I’m not going into more detail here.

Integration test

Something which is very important for Gradle plugins is to test their behavior when being applied to real projects. Even if all unit tests pass you might have missed something.

Fortunately Gradle provides a dedicated TestKit for this. It features a GradleRunner which allows you to execute your plugin in a real project context.

public class YourPluginTest {
@Rule public final TemporaryFolder testProjectDir =
new TemporaryFolder();

@Test public void test() throws Exception {
setUpTestProject();

BuildResult result = GradleRunner.create()
.withProjectDir(testProjectDir.getRoot())
.withPluginClasspath()
.withArguments(TASK_NAME, "--stacktrace")
.build();

assertThat(
result.task(":" + TASK_NAME).getOutcome(),
equalTo(SUCCESS));
}
}

You need to write a build.gradle file to the test project directory which applies your plugin. It’s mandatory to use the “plugins { id ‘…’ }” syntax.

private void setUpTestProject() throws Exception {
File buildFile = testProjectDir.newFile("build.gradle");
writeFile(buildFile, "plugins { id 'your-plugin-id' }");
}

This exemplary test only checks if the task execution succeeds. However, you might want to do further assertions for your current use case.

Usage

As you could see, it’s very straightforward to implement and test a custom Gradle plugin. Especially with the described unit and integration tests you can explore and play with your plugin before applying it to a real project.

In general, to use your custom Gradle plugin there are three steps:

  1. specify the location of your plugin
  2. declare the buildscript dependency
  3. apply the plugin

The exact usage of your custom Gradle plugin depends on your deployment strategy. You can use it as a JAR copied to the libs folder or deploy it to a local or central respository. Another alternative is to use JitPack.

buildscript {
//step 1
repositories {
flatDir {dirs 'libs'} //as JAR in libs folder
jcenter() //central repository
mavenLocal() //local repository
maven { url "https://jitpack.io" }
}

//step 2
dependencies {
classpath 'your.pkg:your-plugin-id:1.0.0' //varies for JitPack
}
}
//step 3
apply plugin: 'your-plugin-id'

Conclusion

Gradle is the engine of each Android build. It’s super simple to write a custom Gradle plugin to intercept the build process and automate certain reoccurring tasks (within the project or across projects). Thanks to Gradle’s TestKit and JUnit the plugin could conveniently be evaluated and tested before applying to real projects. As Android developer there is even no need to struggle with Groovy and just write everything in Java.

There is a simple example project for writing custom Gradle plugins by Gradle. We recently published a custom Gradle plugin for extracting Android string resources from layouts which may help you to write your own custom Gradle plugin.