cybo42

Turning caffeine into code...

My New Favorite Thing: Gradle

I like other Java developers have a love / hate relationship with Maven. While on most days it leans more towards the love side, there are times when the love just isn’t there. Usually those times are when I want to do something not quite standard. It seems that I end up spending the afternoon sacrificing XML in order to appease the Maven gods.

For a current project I decided to take a hard look at Gradle and was pleasantly surprised.

Getting started

If you have a simple java project using the standard convention of putting your source under src/main/java then all you need is one a one line build script.

1
apply plugin: 'java'

Thats it. Ok so that’s not super impressive, your not really doing much more then javac. The little more you are doing is working with the “standard” directory structure, and you can automaticly package up your code into a .jar by running gradle assemble

1
2
3
4
src/main/java
src/main/resources
src/test/java
src/test/resources
Stepping it up a notch

Recently I’ve been researching some frameworks to help expose some metrics about an application. I came across an aptly named project by codeahale called metrics and wanted to kick the tires on it.

So I wrote a quick little app that would demo each of the metric types. In order to get my code to compile, I needed to pull down some dependencies from codeahale’s maven repository, as well as some others from the standard maven repositories.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apply plugin: 'java'

repositories{
  mavenLocal() // searches your downloaded dependencies ($HOME/.m2/repository)
  mavenCentral() // searches standard maven repositories

  // searches custom repository
  mavenRepo name: 'repo.codahale.com', urls: 'http://repo.codahale.com'
}

dependencies{
  compile group: 'com.yammer.metrics', name: 'metrics-core', version: '2.0.0-BETA15-SNAPSHOT'
  compile group: 'com.yammer.metrics', name: 'metrics-graphite', version: '2.0.0-BETA15-SNAPSHOT'
}

The above defines a few repositories to search dependencies. The second block defines which dependencies are required. There is also a more concise way to list you dependencies.

1
2
3
4
dependencies{
  compile 'com.yammer.metrics:metrics-core:2.0.0-BETA15-SNAPSHOT'
  compile 'com.yammer.metrics:metrics-graphite:2.0.0-BETA15-SNAPSHOT'
}

Wait there’s more

Part of my metrics research was reporting metrics from different hosts. I wanted to run my little demo application from multiple hosts and have them all reporting back to the same graphite server. Like any good (lazy) developer I wanted a nice bundle that I could just drop on the server and run without building or setting my classpath etc.

What I need is a nice way to compile my app, package up the dependencies, and write a script that can execute my application. I’ve done this in the past in Maven using the assembly plugin. It worked but required a good amount of configuration and template files. (this was with maven2, it may be easier now with maven3, or with newer plugins).

I was amazed when I found to do this would only require two new lines in my gradle build file.

1
2
apply plugin: 'application'
mainClassName = 'com.cybo42.StatsDemo' // Name of the class which has the main() method to execute

After that all need to do is run and your done.

1
gradle dist

When this completes you have a zip file which looks something like this when exploded:

1
2
3
4
5
6
7
8
9
statsdemo/
statsdemo/lib/
statsdemo/lib/statsdemo.jar
statsdemo/lib/metrics-core-2.0.0-BETA15-SNAPSHOT.jar
statsdemo/lib/metrics-graphite-2.0.0-BETA15-SNAPSHOT.jar
statsdemo/lib/slf4j-api-1.6.1.jar
statsdemo/bin/
statsdemo/bin/statsdemo
statsdemo/bin/statsdemo.bat

Inside the bundle you have a lib directory with all your dependencies, including a jar file containing your compiled classes. Along side the lib directory there is a bin directory which contains two scripts. A .bat file for windows and shell script for UNIX. The great thing about these scripts is they can be run from any location providing you give the full path. The script will take care of setting up the CLASSPATH for all your libraries. Not bad for only two lines in a build script.

Flexibility

The other thing I really liked about Gradle is it’s flexibility. You get a lot for free if you follow standard conventions, but you are allowed to break those conventions without too much pain.

Say for example I want to keep my source in a directory called src instead of the maven convention src/main/java

I would add the below snippet to my build file.

1
2
3
4
5
6
7
sourceSets {
  main {
    java {
      srcDir 'src'
    }
  }
}

Now lets say I have jar dependency that that doesn’t exist in the standard maven or other repository for some reason. I can manually install it to my local maven repository, or upload it to a custom repository, but instead I want to keep it in a directory alongside my source.

If I have the below directory structure:

1
2
3
build.gradle
src/
libs/

I can add the following into my build.gradle to tell to resolve dependencies from the libs directory. The best part is you can use this in addition to standard maven repositories.

1
2
3
4
5
repositories {
  flatDir name: 'localRepository', dirs: 'lib'
  mavenLocal()
  mavenCentral()
}

The fine print

Unfortunately Gradle isn’t all sunshine and rainbows. It does have a few issues, however I don’t think any of them are a deal breaker. this post was written referencing gradle-1.0-milestone-3, recent release have resolved some of these issues

  • Errors / Stacktraces - I love the fact that Gradle is based on Groovy. Which means you can write full out code in your build scripts, instead of trying to program in XML or write a plugin, when you only need a couple lines of code. However one of the side effects it that it inherits Groovy’s very verbose stacktraces, which unfortunately don’t always contain the details to let you know what is wrong.
  • Speed - I’ve noticed that on larger projects primarily ones with many dependencies builds take take quite a long time. From speaking with people close to the Gradle project, it seems to be partially an issue with dependency management, something that is due to be fixed in upcoming releases.
  • Misconception that Gradle is just for groovy - Using Gradle doesn’t require your project be using Groovy. In addition to Groovy projects, Gradle can build Java, Scala and Clojure to name a few. You can also have projects which combine multiple languages. While the build file itself is written in Groovy, if you wish to extend Gradle by writing you own Plugin or Task it need not be in Groovy. You can write a Gradle Plugin or Task in any JVM language, anything that compiles out to Java bytecode.

Happy building…