Gradle Plugin Quick Bootstrap

Mikołaj Karebski
VirtusLab
Published in
5 min readMar 11, 2019

Introduction

Required:

  • IntelliJ IDEA
  • Java 1.8
  • Maven Repository

Not so long ago, I had a need to write a gradle plugin for internal usage at our company. While searching for information on how to do this, I came across many different sources, and not all of them were consistent with each other. For this reason I decided to start developing a simple “hello world” plugin just to find out which source is the best.

What confused me initially was where to start. I didn’t want to write everything in one groovy file as the tutorials suggested. I wanted to create a simple proof of concept, but still with properly organized code.

The results are presented in the form of instructions on how to quickly bootstrap a working plugin.

You can download the working example from here.

Let’s start

Let’s start by creating a simple gradle-java project. I used a gradle wrapper, so I left this option checked.

Copy the code below into your build.gradle.

plugins {   
id 'java-gradle-plugin'
}
project.description = 'Hello world plugin'
project.group = 'com.virtuslab'
project.version = '1.0-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
gradlePlugin {
plugins {
helloWorldPlugin {
id = 'HelloWorldPlugin'
implementationClass = 'com.virtuslab.HelloWorldPlugin' // main plugin class
}
}
}

In this procedure we use java-gradle-plugin. I wanted to keep things simple, and this plugin gave me that opportunity.

Java Gradle Plugin

“Setting up a Gradle plugin project should require as little boilerplate code as possible.“ — https://guides.gradle.org/implementing-gradle-plugins/

The java-gradle-plugin applies the java plugin for us, adds the gradleApi() dependency, and produces a *.properties file. All of the above is required, so gradle, which imports this, knows it is a plugin. Configuration happens in the gradlePlugin block.

gradlePlugin {
plugins {
helloWorldPlugin {
id = 'HelloWorldPlugin'
implementationClass = 'com.virtuslab.HelloWorldPlugin'
}
}
}

Id field will be used to name the properties file. In that case it will be META-INF/HelloWorldPlugin.properties

ImplementationClass tells gradle which class is the starting point for the plugin.

More info about the java-gradle-plugin can be found here.

Main plugin class

Create class com.virtuslab.HelloWorldPlugin and provide it with the code below.

package com.virtuslab;import org.gradle.api.Plugin;
import org.gradle.api.Project;
public class HelloWorldPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.task(“hello”).doLast(task -> System.out.println(“Hello world from the plugin!”));
}
}

Plugin is an interface provided by the gradle dependency. It applies the new configuration to the project.

Publishing

For the sake of simplicity, we will publish the plugin to the local repository.

In order to do so, build.gradle should look like this. Bolded parts in the snippet below are responsible for publishing.

plugins {   
id 'java-gradle-plugin'
id 'maven-publish'
}
project.description = 'Hello world plugin'
project.group = 'com.virtuslab'
project.version = '1.0-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
gradlePlugin {
plugins {
helloWorldPlugin {
id = 'HelloWorldPlugin'
implementationClass = 'com.virtuslab.HelloWorldPlugin' // main plugin class
}
}
}
publishing {
publications {
pluginPublication(MavenPublication) {
from components.java
groupId project.group
artifactId "hello-world-plugin"
version project.version
}
}
}

After running ./gradlew publishToMavenLocal you should see a SUCCESS message and the plugin should be placed in your local maven repository. Usually it can be found at $HOME/.m2.

Unit testing

Many books and articles have been written on the importance of testing our software. The 2nd chapter in the Gradle documentation (here) is worth reading if you want to get basic knowledge on plugin testing.

To test our plugin we will use Groovy and Spock. Spock is super-flexible and can be used to write very readable test specifications. Groovy is the language used by Spock as its test implementation language.

Update your build.gradle with a dependencies block.

plugins {   
id 'groovy'
id 'java-gradle-plugin'
id 'maven-publish'
}
project.description = 'Hello world plugin'
project.group = 'com.virtuslab'
project.version = '1.0-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
testCompile gradleTestKit()
testCompile group: 'junit', name: 'junit', version: '4.12'
testCompile group: 'org.spockframework', name: 'spock-core', version: '1.2-groovy-2.4'
}
gradlePlugin {
plugins {
helloWorldPlugin {
id = 'HelloWorldPlugin'
implementationClass =com.virtuslab.HelloWorldPlugin' // main plugin class
}
}
}
publishing {
publications {
pluginPublication(MavenPublication) {
from components.java
groupId project.group
artifactId "hello-world-plugin"
version project.version
}
}
}

In your test directory in IntelliJ, create a groovy folder and mark it as the test sources’ root (https://www.jetbrains.com/help/idea/creating-and-managing-modules.html). Next, create a com.virtuslab.HelloWorldPluginSpec.groovy file and paste the following code into it:

package com.virtuslabimport org.gradle.testkit.runner.GradleRunner
import org.gradle.testkit.runner.TaskOutcome
import org.junit.Rule
import org.junit.rules.TemporaryFolder
import spock.lang.Specification
class HelloWorldPluginSpec extends Specification { @Rule
final TemporaryFolder testProjectDir = new TemporaryFolder()
private File buildFile
private static final String BASIC_PLUGIN_IMPORT = '''
buildscript {
repositories {
mavenLocal()
}
dependencies {
classpath 'com.virtuslab:hello-world-plugin:1.0-SNAPSHOT'
}
}
apply plugin: 'HelloWorldPlugin'
'''
def setup() {
prepareBuildGradle()
}
def "hello task should print hello message"() {
when:
def result = GradleRunner.create()
.withProjectDir(testProjectDir.root)
.withArguments('hello')
.build()
then:
result.output.contains('Hello world from the plugin!')
result.task(":hello").outcome == TaskOutcome.SUCCESS
}
private def prepareBuildGradle() {
buildFile = testProjectDir.newFile('build.gradle')
buildFile << BASIC_PLUGIN_IMPORT
}
}

Our basic test is shown in bold. The whole thing uses Gradle Runner from TestKit to simplify testing as much as possible. Basically, in a temporary folder we need to create a build.gradle file which imports our plugin. Next, with given implementation we test our task and receive a result, optional error messages etc. which later can be compared with expected values.

HINT: During test execution you might get a java.lang.ExceptionInInitializerError (no error message):

:compileTestGroovy FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ‘:compileTestGroovy’.
> java.lang.ExceptionInInitializerError (no error message)

This is the result of a conflict between the existing groovy version and the one the plugin tries to download and use. There are many ways to fix this issue. The one I chose involves a change in the build.gradle line:

testCompile group: ‘org.spockframework’, name: ‘spock-core’, version: ‘1.2-groovy-2.4’

to

testCompile(“org.spockframework:spock-core:1.0-groovy-2.4”) {
exclude group: ‘org.codehaus.groovy’, module: ‘groovy-all’
}

Conclusion

In a few minutes we have bootstrapped a simple plugin which can be easily expanded into something more useful.

--

--