Search This Blog

16 August 2014

Writing a custom Gradle plugin: Groovy + buildSrc/

I spent some time looking around at various suggestions (including on the Gradle documentation page) about how to write a custom plugin.  For my specific purpose, it was not intended to be a shared plugin - but rather one to abstract out some of the logic we are doing in our build.  I'll probably actually do another post on writing a Java plugin, but for now let's try the Groovy one.

To show that we are really doing a minimalist design here, let's start with a completely empty directory.

~> mkdir mytest
~> cd mytest

I personally use the Gradle Wrapper. Gradle 1.6 is installed on the system, but once the Gradle Wrapper takes over it's ignored.

Create a build.gradle file:

task wrapper(type: Wrapper) {
    gradleVersion = '1.12'
}

Run it.

~/mytest> gradle wrapper

Now that we have the wrapper installed in a fresh directory, let's look to that plugin.  We'll be adding 1 groovy file and 1 properties file.

Here's what we are aiming for:

.
├── build.gradle
├── buildSrc
│   └── src
│       └── main
│           ├── groovy
│           │   └── GroovinPlugin.groovy
│           └── resources
│               └── META-INF
│                   └── gradle-plugins
│                       └── groovin.properties
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
└── gradlew.bat

In buildSrc/src/main/groovy/ create your new plugin file. In my case, it was called GroovinPlugin.groovy.

We'll still put a Java-style package at the top, even though the source file was not contained in a Java-style package directory. IE: I used package org.eoti.gradle even though the directory was not org/eoti/gradle.

We need to import the Gradle API.

import org.gradle.api.*
import org.gradle.api.plugins.*

Our class (same name as the file) implements Plugin<Project> which means we have:

void apply(Project project) { ... }

In my case I specified two things in this function.

First, an extension to allow parameters to be passed in:

project.extensions.create("groovin", GroovinPluginExtension)

We'll discuss that GroovinPluginExtension in a moment.  The name "groovin" is the name of the configuration block.

Secondly, I specified a task to be added once this plugin is applied:

project.task('groove') << {
    println project.groovin.message
}

That 'groovin' is the configuration name from above. 'message' is the parameter name defined inside GroovinPluginExtension.  The GroovinPluginExtension is just another class defined in the same file (not an inner class though) providing a POGO. A POGO is basically a POJO that automatically creates the getters and setters for you.

In this case, we define a String called 'message', as such:

class GroovinPluginExtension {
    def String message = 'We be groovin\''
}

Put all together, the GroovinPlugin.groovy looks like:

package org.eoti.gradle;

import org.gradle.api.*
import org.gradle.api.plugins.*

class GroovinPlugin implements Plugin<Project> {
    void apply(Project project) {
        project.extensions.create("groovin", GroovinPluginExtension)
        project.task('groove') << {
            println project.groovin.message
        }
    }
}

class GroovinPluginExtension {
    def String message = 'We be groovin\''
}

Pretty small, right?

Now, we need to add the properties file using the same configuration name we chose above.  In this case, buildSrc/src/main/resources/META-INF/gradle-plugins/groovin.properties.

It contains one line that points to our plugin implementation:

implementation-class=org.eoti.gradle.GroovinPlugin

Ok, we're almost done.

In your top-level build.gradle, specify that we want to apply our new plugin by adding this to the top:

apply plugin: org.eoti.gradle.GroovinPlugin

And then from the command line, call the task we defined earlier:

~/mytest> ./gradlew groove

That's great!  But, why did we create a configuration parameter if we aren't going to use it?

Add this to your top-level build.gradle:

groovin{
    message = "We be jammin'"
}

And then re-run it:

~/mytest> ./gradlew groove

There you go.  You can add additional parameters, additional Groovy logic, etc.



3 comments:

  1. If you use Gradle 2.0 as the base install (instead of 1.6) it looks like you can skip the initial step of creating a build.gradle file and just call `gradle wrapper` directly.

    ReplyDelete
  2. Nice! One typo:

    class GroovinPlugin implements Plugin

    ... should be:

    class GroovinPlugin implements Plugin < Project > {

    ReplyDelete