Rocinante – project automation

After the Rocinante project has mastered some basic functionalities of Protocol Buffer, the project automation is now to be improved by a Maven plugin.

Why should an automation tool be integrated at such an early stage? A tool like this speeds up the use of Rocinante and allows the individual functions to be tested more quickly and, above all, more realistically. Up to now, the output of the generator has been tested against some static texts. By using our own Maven plugin, the generated sources can be translated directly by the Java compiler and tested via integration tests. The texts could still contain one or two syntax errors. However, the Java compiler finds what the developer misses.

Before we look at a first Rocinante Maven plugin, I have to mention a small correction to the Rocinante implementation.

The first version of Rocinante coded the record tag as one byte. The field number and the wiretype are coded in the record tag. Since the wiretype consists of three bits, only the values 1 to 31 remain for the field numbers. At first glance, this looks quite correct, because what message should have more than 31 direct attributes? Unfortunately, however, the record tag is stored in the protocol buffer as a VarInt value. this leaves the values between 1 and 536870911 for the field number, minus 1000 reserved field numbers between 17000 and 17999. Perhaps a VarShort could have been invented at this point with a still considerable 8191 potential field numbers. In the meantime, Rocinante has learned to read and write correct record marks.

But back to our Maven plugin. The Rocinante Maven plugin is given the task of generating suitable Java sources from the Protocol Buffer sources in the generate-sources phase of the Maven lifecycle.

<plugin>
  <groupId>de.schegge</groupId>
  <artifactId>molecule-maven-plugin</artifactId>
  <version>0.1.0-SNAPSHOT</version>
  <executions>
    <execution>
      <goals>
        <goal>generate-code</goal>
      </goals>
    </execution>
  </executions>
</plugin>

The Maven plugin recognizes a single goal generate-code. This is implemented by the following MoleculeMojo class. This class as a Maven Mojo is annotated with @Mojo, which specifies the desired default phase LifecyclePhase.GENERATE_SOURCES and the name of our Mojo.

@Mojo(name = "generate-code", threadSafe = true, defaultPhase = LifecyclePhase.GENERATE_SOURCES)
public class MoleculeMojo extends AbstractMojo {

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

    @Parameter(defaultValue = "${project.basedir}/src/main/rocinante")
    private File inputDirectory;

    @Parameter(defaultValue = "${project.basedir}/src/main/rocinante")
    private File importDirectory;

    private String[] inputs = new String[]{ "**/*.proto" };

    @Parameter(name = "inputs")
    public void setInputs(String[] inputs) {
        this.inputs = inputs;
    }

    public void execute() throws MojoExecutionException {
        DirectoryScanner directoryScanner = new DirectoryScanner();
        directoryScanner.setBasedir(inputDirectory);
        directoryScanner.setIncludes(inputs);
        directoryScanner.scan();

        ProtoGenerator protoGenerator = new ProtoGenerator();
        for (String input : directoryScanner.getIncludedFiles()) {
            createSources(protoGenerator, inputDirectory.toPath().resolve(input));
        }
    }

    private void createSources(ProtoGenerator protoGenerator, Path inputPath) throws MojoExecutionException {
        try {
            Files.createDirectories(outputDirectory.toPath());
            PathProtoDestinations destinations = new PathProtoDestinations(outputDirectory.toPath());
            protoGenerator.generate(inputPath.toString(), new PathProtoSources(inputPath, importDirectory.toPath()), destinations);
        } catch (IOException | RuntimeException e) {
            getLog().error(e.getMessage(), e);
            throw new MojoExecutionException(e);
        }
    }
}

The Mojo knows the directories for Rocinante input and import directories and target directory for the generated Java sources with their respective default values. The inputs and imports are searched for by default in the directory ${project.basedir}/src/main/rocinante and the outputs are written to the directory ${project.build.directory}/generated-sources/rocinante. These values can all be overwritten, as is usual with other Maven plugins.

The input files can be specified individually as inputs parameter; if this information is missing in the configuration, the pattern **/*.proto is used.

A DirectoryScanner is used to search for all input files under the input directory and pass them to the Rocinante ProtoGenerator. This generates the corresponding Java sources as explained in the previous articles and writes them to the output directory.

This brings us to the end of the implementation and the blog post. In fact, thanks to the extensive Maven framework, there really isn’t much to do to significantly improve your project workflows.

If you would like to take a look at the progress of the Rocinante project so far, you can now follow it on GitLab.

Leave a Comment