Saturday, November 26, 2011

A First Look at Building Java with Gradle

I left JavaOne 2011 with several take-aways. One of the most significant was a renewed interest in learning more about Gradle. In this post, I look at using Gradle and at migrating a simple Ant build script to Gradle.

Gradle installation is easy. Gradle can be downloaded and unzipped into the desired location. I am using Gradle 1.0 Milestone 6 for examples in this post.

Once Gradle is downloaded and unzipped, the environment variable GRADLE_HOME can be set to the directory of the unzipped Gradle installation and the PATH should be set to $GRADLE_HOME/bin or %GRADLE_HOME%\bin. The Gradle installation page tells us that JVM options used by Gradle can be set via either GRADLE_OPTS or JAVA_OPTS. The Grade installation and configuration in the path can be confirmed by running gradle -v at the command line once the environment variable settings are sourced.

Besides verifying correct configuration of the Gradle installation, the output from running the -v option also reminds us that Gradle is built on (and brings the best of) Groovy (1.8.4), Ant (1.8.2), Ivy (2.2.0), and the Java Virtual Machine to our task execution.

The Gradle Build Script Basics documentation describes the default "build configuration script" (something like an Ant build.xml file) called build.gradle. This same page provides a Hello, World example implementation of such a build configuration script. An adapted version of that example is shown in the next code listing.

build.gradle - Hello Gradle Example
task hello_world {
   doLast {
      println 'Hello, Gradle!'
   }
}

The task (roughly equivalent to an Ant target) is defined above with the name "hello_world" and can be invoked by Gradle the same way an Ant task is invoked by Ant (assuming the default build configuration script file names in both cases): gradle hello_world. The next screen snapshot shows this both with and without the -q (for quiet) option.

An interesting observation I made while playing with even this simple build configuration file is the importance of proper curly brace placement. The initial curly brace on the same line as the task declaration must remain on that line. When I tried to move it down to the next line to line up in the same column as the closing brace, I saw the error in the next screen snapshot and the representative text output that follows the image.

Error Message from Opening Curly Brace on Next Line
FAILURE: Build failed with an exception.

* Where:
Build file 'C:\java\examples\groovyExamples\gradleExample\build.gradle' line: 2

* What went wrong:
Could not compile build file 'C:\java\examples\groovyExamples\gradleExample\build.gradle'.
Cause: startup failed:
build file 'C:\java\examples\groovyExamples\gradleExample\build.gradle': 2: Ambiguous expression could be a parameterless closure expression, an isolated open code block, or it may continue a previous statement;
   solution: Add an explicit parameter list, e.g. {it -> ...}, or force it to be treated as an open block by giving it a label, e.g. L:{...}, and also either remove the previous newline, or add an explicit semicolon ';' @ line 2, column 1.
   {
   ^

1 error


* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED

Total time: 2.636 secs

Fortunately, the above error message is easily avoided by placing the opening curly brace of the Gradle task closure on the same line as the Gradle task's definition.

A default target can be specified in an Ant build.xml via the project element's default attribute. Gradle supports a similar specification of default tasks to be run as shown in the next code snippet which builds upon the previous example. Running this task now does not require specification of the task as shown in the next screen snapshot.

build.gradle - Hello Gradle Example with Defaults
defaultTasks 'hello_world'

task hello_world {
   doLast
   {
      println 'Hello, Gradle!'
   }
}

Gradle is intentionally constructed to be flexible and support a wide variety of tasks. For those interested in specific, commonly used tasks, there are Gradle plug-ins available to reduce the need for newly written Grade code. For purposes of this post, the Java plug-in is of particular interest. It is easy to apply this plug-in as shown in the next version of build.gradle which builds upon the previous two examples and adds the application of the Java plug-in.

build.gradle - Hello Gradle Example with Java Plug-in
apply plugin: 'java'

defaultTasks 'hello_world'

task hello_world {
   doLast
   {
      println 'Hello, Gradle!'
   }
}

The simple addition of the single line apply plugin: 'java' brings numerous Java-related tasks automatically to this build configuration file. I think that the easiest way to quickly ascertain which tasks are automatically available via the Java plugin is to run gradle tasks against a build configuration file that does not apply the plugin and then run gradle tasks against a build configuration file with the Java plugin applied. Doing just that is shown in the next two screen snapshots. (Note that gradle tasks is similar to Ant's -p option except that Ant requires the target's description attribute to be specified for a target to show up in response to the -p option.)

Comparing these two screen snapshots helps us see which tasks are made available by using the Java plugin in Gradle. Before looking at the Java specific tasks, I think it's also worth noting some other tasks available in Gradle without a plugin (the "Help tasks"). In particular, the images indicate the "tasks" task (one of the "Help tasks") we used to see all available tasks. There are similar built-in "Help tasks" tasks for viewing build properties ("properties"), viewing project dependencies ("dependencies"), viewing sub-projects ("projects"), and viewing help ("help"). Many of these tasks are available via deprecated command-line options as well.

The only other task listed for the build configuration file that did not apply the Java plugin was the custom defined "hello_world" task. However, the build configuration file that added the single line to apply the Java plugin had numerous other tasks besides the custom-provided "hello_world" task and besides the always-available "help tasks." These tasks include the "Build tasks" ("assemble", "build", "buildDependents", "buildNeeded, "classes", "clean", "jar", "testClasses"), "Documentation tasks" ("javadoc"), and the "Verification tasks" ("check" and "test").

It is also interesting to see the color syntax used in the output from running gradle tasks. This is a nice touch that adds to the readability of the output. If for some reason the color syntax is undesirable, the --no-color option can be specified to turn it off.

Convention over Configuration is an increasingly popular software development tenet that tools and frameworks such as Ruby on Rails, Maven, and JPA ("configuration by exception") have all embraced. Gradle similarly adopts conventions with the Java plugin tasks to reduce need for explicit configuration specification.

The Gradle Java Quickstart Page describes the Gradle conventions for the Java plugin (I have added the emphasis):

Gradle expects to find your production source code under src/main/java and your test source code under src/test/java. In addition, any files under src/main/resources will be included in the JAR file as resources, and any files under src/test/resources will be included in the classpath used to run the tests. All output files are created under the build directory, with the JAR file ending up in the build/libs directory.

Gradle's conventions for Java builds may look suspiciously similar to conventions Maven users are already familiar with.

For purposes of this blog post, I have changed my personal NetBeans-inspired directory convention to work with Gradle's expected convention. In other words, instead of using dist for the constructed JAR and using src and test for source and test code respectively, I move the files into src/main/java and test/main/java and plan for the JAR to be assembled into the build directory rather than in the dist directory.

With the source code in the appropriate expected directory, I show (in the next screen snapshot) the building of the Java code with the "build" task and the generation of Javadoc with the "javadoc" task. Again, the color syntax makes the output more readable.

The next screen snapshot shows the generated files under the build directory. None of this was specified as it relied upon Gradle's conventions.

The two folders "blogScriptConfigFiles" and "images" are not part of the Gradle build and are simply folders I created for managing versions of the build.gradle files and screen snapshots for this blog post. The other folders are either source or generated artifacts.

In case it wasn't clear above, I wanted to add a reminder here that to build and document this simple Java application, I only really needed the following Gradle build configuration file:

build.gradle - Minimum Required To Build Simple Java Application
apply plugin: 'java'

As long as the source files are in the appropriate location according to Gradle conventions and the build is not too complex, I don't need to specify any tasks (similar to Ant targets) at all. I included a simple build.xml example in my blog post Learning Java via Simple Tests that I often use as a starting point for building simple Java examples. However, even the simplest Ant build file doesn't get as easy as the one-liner Gradle build configuration file just shown!

Conclusion

Gradle appears to have great potential as a build tool. By combining the power of Groovy with the power of the JVM Ant, and Ivy, Gradle seems poised to provide a winning combination that should be difficult to beat when it comes to building Java applications. I look forward to Gradle 1.0 leaving Milestone status behind, hopefully in the very near future.

3 comments:

Ken Sipe said...

Nice intro gradle coverage...

Dustin said...

Ken,

Thanks for providing that nice feedback. Also, thanks for your JavaOne 2011 presentation on Gradle (Rocking the Gradle); it was what finally convinced me to move past vague awareness of Gradle's existence to actually trying it out. So far, I've liked what I've seen.

Dustin

Dustin said...

JAXenter's Java Tech Journal #14 ("Gradle: Advanced Insights") is focused on Gradle. The edition contains three articles on Gradle: "Gradle SOAP," "Plugging in to Gradle Plugins," and "Interview with Hans Dockter."

The 13-page electronic (PDF) magazine is free of any monetary charge, but does require registration and checking of the box allowing for further communications from the publisher.