After managing a number of Java projects with Maven, the 2.1 release of the Twig Cassandra JDBC driver fork has prompted me to revisit the build process. This was a significant rewrite of the existing Apache driver, and incorporated a build process that leveraged the maven-assembly-plugin to produce a JAR with all of the dependencies included.

This is important for a driver class Java assembly, as it is necessary to integrate with third-party tools like DbVisualizer and IntelliJ. If a driver requires a lot of additional dependencies to be installed, it is difficult for a developer to set it up with their favorite JDBC query tool.

Doing this with Maven required the proper configuration of the assembly plugin:

 <plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-assembly-plugin</artifactId>
   <version>2.5.3</version>
   <configuration>
     <descriptorRefs>
       <descriptorRef>jar-with-dependencies</descriptorRef>
     </descriptorRefs>
   </configuration>
   <executions>
     <execution>
       <id>make-assembly</id>
       <phase>package</phase>
       <goals>
         <goal>single</goal>
       </goals>
     </execution>
   </executions>
</plugin>

This is somewhat non-obvious, the jar-with-dependencies is a special value that indicates another JAR should be built using the output of the project and all of its dependencies. There is nothing declarative about this, as the build executes the JAR’s are created when the package phase is invoked.

After some frustration with the file naming strategy following the 2.1.0 release of Twig, this logic was migrated to a Gradle build file:

task jar {
    from {
        configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
    }
}

The declarative nature of Grandle means that there is an updated target jar that can be executed to create the distributable JAR with all dependencies. It is extremely easy to invoke this task and confirm that it is working.

Task dependency makes it straightforward to build new composite tasks that use other tasks to produce output. For example, my project also needs a distribution target that should be fully tested:

task distribution(dependsOn: ['clean', 'test', 'jar'])

Nothing more is needed, the distribution task builds the JAR after running all the tests. Including clean assures that the source code is always rebuilt as part of this target and of course test invokes unit test verification. Of course, Gradle includes build for this purpose but it’s trivial to update or make your own.

This kind of build dependency is very similar to how one might produce a makefile or an ANT build script, and from a build processes standpoint makes way more sense than plugins that are loosely coupled based on phase and goal, or that use cryptic configuration values like descriptorRef to unlock core capability.

As part of Twig 2.1.1, the build process is moving entirely to Gradle. You can follow the progress on this on Github.