Search This Blog

16 August 2014

Writing a custom Gradle plugin: Java + 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 did another post on writing a Groovy plugin, here's the Java one.

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

~> mkdir mytest
~> cd mytest

1. Gradle Wrapper


I personally use the Gradle Wrapper. Since the last post, I upgraded to Gradle 2.0, but the Gradle Wrapper it installs by default is not currently compatible with the Android plugin.  As such, we'll still manually define one.

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 3 Java classes and 1 properties file.

Here's what we are aiming for:

.
├── build.gradle
├── buildSrc
│   └── src
│       └── main
│           ├── java
│           │   └── org
│           │       └── eoti
│           │           └── gradle
│           │               ├── BrewinPluginExtension.java
│           │               ├── BrewinPlugin.java
│           │               └── BrewTask.java
│           └── resources
│               └── META-INF
│                   └── gradle-plugins
│                       └── brewin.properties
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
└── gradlew.bat


2. POJO Extension


Let's start this time by defining our POJO.  We'll create buildSrc/src/main/java/org/eoti/gradle/BrewinPluginExtension.java.

We'll add our package:

package org.eoti.gradle;

And import the Gradle API:

import org.gradle.api.*;

And create our POJO with getters/setters. 

Our complete POJO:

package org.eoti.gradle;

import org.gradle.api.*;

public class BrewinPluginExtension
{
    private String message = "Wake up!";
   
    public String getMessage(){return message;}
    public void setMessage(String message){this.message = message;}
}

3. Task


Next, we'll define our task.  Create buildSrc/src/main/java/org/eoti/gradle/BrewTask.java.

We'll add our package and imports:

package org.eoti.gradle;

import org.gradle.api.*;
import org.gradle.api.tasks.*;


Our class will extend DefaultTask.

public class BrewTask extends DefaultTask{...}

Next we'll define our task and annotate it:

@TaskAction
public void brew() {...}

In the implementation, we want to find our configuration.  If there isn't one, we'll create a default one.  Then, we'll output our message.  The complete code:

package org.eoti.gradle;

import org.gradle.api.*;
import org.gradle.api.tasks.*;

public class BrewTask extends DefaultTask
{
    @TaskAction
    public void brew() {
        BrewinPluginExtension extension = getProject().getExtensions().findByType(BrewinPluginExtension.class);
        if(extension == null)
            extension = new BrewinPluginExtension();
       
        System.out.format("%s\n", extension.getMessage());
    }
}

Note the getMessage() that we defined in our POJO.

4. Plugin


Next, we'll define our plugin.  Create buildSrc/src/main/java/org/eoti/gradle/BrewinPlugin.java.

Add your package and the Gradle import.  In my case, that is:

package org.eoti.gradle;

import org.gradle.api.*;

Our class implements Plugin<Project> which means we have:

@Override
public void apply(Project project){ ... }

We will specify two things in this function.

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

project.getExtensions().create("brewin", BrewinPluginExtension.class);

The name "brewin" is the name of the configuration block.

Second, a task to be added once this plugin is applied:

project.getTasks().create("brew", BrewTask.class);

That 'brew' is the @TaskAction name from above.

The complete plugin:

package org.eoti.gradle;

import org.gradle.api.*;

public class BrewinPlugin implements Plugin
{
    @Override
    public void apply(Project project)
    {
        project.getExtensions().create("brewin", BrewinPluginExtension.class);
        project.getTasks().create("brew", BrewTask.class);
    }
}


5. Properties


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/brewin.properties.

It contains one line that points to our plugin implementation:

implementation-class=org.eoti.gradle.BrewinPlugin

Ok, we're almost done.

6. Apply it


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.BrewinPlugin

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

~/mytest> ./gradlew brew


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:

brewin{
    message = "Make another pot!"
}

And then re-run it:

~/mytest> ./gradlew brew

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

2 comments:

  1. Thanks for the extension example. This is useful!

    ReplyDelete
  2. Thanks for this!

    Is there a way to trigger the task to run prebuild?

    ReplyDelete