Monday, December 30, 2013

Simple Gradle Java Plugin Customization

As I demonstrated in the post A First Look at Building Java with Gradle, Gradle is particularly concise and easy to apply to the basics of building a Java application when one uses the Java plugin and places files and directories where this plugin expects them (convention-based project layout). However, it is not always possible to have a structure (especially in legacy systems) meeting Gradle's expected conventions. In this post, I look at overriding some of the Gradle Java Plugin's conventions to allow a simple Gradle build to work with different directory structures.

The code listing that follows contains the Gradle code for a build build.gradle. I included comments in the build code to help explain what each type of customization is doing.

build.gradle
// build.gradle
//
// This simple example of a Gradle build file exists primarily to demonstrate
// approaches to overriding Gradle's default conventions related to use of the
// Java plugin.

// The 'java' plugin must be applied before attempting to access the sourceSets
// and other properties defined by the Java plugin to avoid an error message
// similar to the following: "Could not find method sourceSets() for arguments..."
apply plugin: 'java'

// Redefine where Gradle should expect Java source files (*.java)
sourceSets {
    main {
        java {
            srcDirs 'java'
        }
        resources {
            srcDir 'resources'
        }
    }
}

// Redefine where .class files are written
sourceSets.main.output.classesDir = file("dist/classes")

// Redefine where 'jar' task should place generated JAR file.
jar {
   destinationDir = file('dist/jar')
}

// Fully qualified directory/JAR for Guava Release 16 JAR file:
//    C:\\guava16\\guava-16.0-rc1.jar
repositories { 
  flatDir{
    dirs 'C:\\guava16'
  } 
}

dependencies {
   compile 'guava:guava:16.0-rc1'
}

defaultTasks 'clean', 'jar'

The Gradle build file shown above first applies the Java plugin. It then overrides the Gradle conventional locations for Java source files (highest level directory where subdirectories represent packages and files have .jar extensions), changing this directory from the default of src/main/java to simply java. Similarly, the default src/main/resources location for production resources is changed to simply resources.

The build file shown above then changes where the *.class files (with appropriate subdirectories representing their package structure) are placed by specifying that sourceSets.main.output.classesDir is now dist/classes (build/classes/main is conventional default). Similarly, the jar task's destinationDir is overridden to point to dist/jar (build/libs is convention) and this is where the JAR file generated by the jar task is written.

The final customization shown in the simple Gradle build script shown above is the specification of "repositories" and "dependencies" to make Guava Release 16 JAR available to my application (which happens to depend on Guava Release 16). Gradle provides sophisticated support for use of Maven or Ivy repositories including special syntax for Maven Central, but this particular example gets the Guava Release 16 JAR from my local file system (C:\guava16). The dependency itself is expressed with "guava:guava:16.0-rc1" because the JAR in that specified repository directory is called "guava-16.0-rc1.jar."

To make my testing of these customizations easier, I explicitly specified defaultTasks as clean and jar so that all I needed to do was type gradle on the command line as long as I was in the same directory as the build.gradle file shown above and as long as there was a "java" subdirectory at that level with the .java source files in their appropriate package-based directories.

Gradle's builds are most concise and easiest to write and read when Gradle's conventions are followed. However, it is not terribly difficult to override these conventions and specify customized configuration to match legacy systems.

No comments: