Do you want to incorporate a task in your maven build? Discover how to create a maven plugin! In this post we will create a plugin that will alter a protobuf specifications file.

A more in depth guide is made by apache and can be found here. A list of available apache maven plugins can be found here.

Our usecase will be to modify the protobuf file from this earlier post on micronaut and gRPC. We want to add the REST endpoint annotations without doing this by hand. The complete code for the plugin can be found here and the plugin is used in a proto specifications project here.

Setup the project

We start by using a handy maven technology to get started:

mvn archetype:generate \
     -DgroupId=nl.sybrenbolandit.maven \
     -DartifactId=proto-annotations-plugin \
     -DarchetypeGroupId=org.apache.maven.archetypes \
     -DarchetypeArtifactId=maven-archetype-plugin

This maven archetype creates a working maven plugin. Lets inspect what we got. In the pom we find the main dependencies that make the plugin work.

<dependency>
  <groupId>org.apache.maven</groupId>
  <artifactId>maven-plugin-api</artifactId>
  <scope>provided</scope>
</dependency>
<dependency>
  <groupId>org.apache.maven.plugin-tools</groupId>
  <artifactId>maven-plugin-annotations</artifactId>
  <scope>provided</scope>
</dependency>

The main class, where the implementation of the plugin goes, is the MyMojo class. The execute() method is what is executed when the plugin is ran. You can check my implementation on github here but this will not be the focus of this post.

Create the configuration

A maven plugin is ran in a specific phase of the build. For plugins like the maven-compiler-plugin this is staight forward but we have to specify what we want for our need. For more information on phases of the maven lifecycle check out this website.

In the @Mojo annotation we can define this (default) phase. Here we chose the generate-resources phase because we are modifying a specifications file. This has to be done before this specifications file is used to generate the java sources from it.

@Mojo(name = "generate", defaultPhase = LifecyclePhase.GENERATE_RESOURCES)
public class GenerateMojo extends AbstractMojo {

The execute method is inherited from the AbstractMojo class. With the return type viod you are free to do whatever you please in this method.

Next we want to be able to tell the plugin te location of the input file we want to modify and the output directory where we will place the result. We define these values as parameters in GenerateMojo.

@Parameter(property = "outputDirectory", defaultValue = "${project.build.directory}/generated-resources")
private File outputDirectory;

The property outputDirectory can be specified where the plugin is used. If nothing is specified the default value will be used.

These input parameters together with a method to implement are enough to create your own maven plugin specific for your needs.

Test the plugin

To verify the working of your plugin we create a test project in the test directory. This test project contains a protobuf file (test.proto) as input for the plugin and a pom file where the plugin is configured.

<plugin>
    <groupId>nl.sybrenbolandit.maven</groupId>
    <artifactId>proto-annotations-maven-plugin</artifactId>
    <configuration>
        <inputFile>${basedir}/proto/test.proto</inputFile>
    </configuration>
</plugin>

Here we use the group- and artifactId of our plugin and we define the path to our input file.

We will run the test with JUnit where we MojoRule to execute maven.

@Rule
public MojoRule rule = new MojoRule() {};

We are now able to get the pom of the test project, extract the execution class of the plugin and run it.

File pom = new File("src/test/resources/test-project");
GenerateMojo generateMojo = (GenerateMojo) rule.lookupConfiguredMojo(pom, "generate");
generateMojo.execute();

From this point on we can use the normal assertions and verifications to check if the plugin does its job.

Use the plugin

After publishing the plugin as a maven artifact (local or on a repository). We can reference our new plugin in the same way we did in out test. Here we will use this repository containing protobuf specification and use our plugin to add the annotations.

<plugin>
    <groupId>nl.sybrenbolandit.maven</groupId>
    <artifactId>proto-annotations-maven-plugin</artifactId>
    <version>1.0.0</version>
    <configuration>
     <inputFile>${basedir}/src/main/proto/cats.proto</inputFile>
    </configuration>
    <executions>
        <execution>
            <phase>generate-resources</phase>
            <goals>
                <goal>generate</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Here we have to specify the version of the plugin. Furthermore we bind the execution to the phase and goal you see here.

When we run the build we see the resulting file in the build output directory.

Screenshot 2020 11 22 at 16.52.10 - CREATE A MAVEN PLUGIN

And when inspecting the file we see that the annotations are added!

rpc GetCat (CatRequest) returns (Cat)  {
 option (google.api.http) = {
     get: "/cats"
  };
}

Hopefully you are now able to create a maven plugin and implement, test and apply it in your build. Happy plugging!