From Maven to Gradle – part 1

Everytime I have to do something with Maven, I get frustrated. Especially POM inheritance and dependency management have frequently given me the urge to throw my computer out the window. And then I’m not even talking about the hundreds and hundreds of lines of XML you need to write for even the simplest projects. Unfortunately, for a long time it was the lesser of all evils. I do not want to go back to writing build files in Ant, and besides, once you can get a parent pom up and running, you don’t have to deal with the Maven intricacies all that much anymore.

At the 2008 Devoxx conference, I attended a talk about Gradle, which looked promising, so I made up my mind to investigate it in the near future. Due to lots of work and tight deadlines, that near future became a year later, so here we are.

What is Gradle?

Gradle is a build system, based on Groovy. Its developers promise that it gives you the best of both worlds between Ant and Maven, and then some. You get the build-by-convention, dependency management and multi-project support from Maven, the flexibility of Ant and the power of Groovy. It is now at version 0.8 and has a very nice user guide, which is already more than 200 pages long. It took Maven years to get that kind of coverage!

The experiment

I am currently working on a completely new version of my Java development framework, so I decided the time was right to try and ditch Maven.

As I’m building the new framework, I’m testing it with an example project called BeeWorks MoneyPile, a simple accounting program I use for creating invoices, tracking expenses and handling contacts. BeeWorks MoneyPile is a multi-project erm… project, consisting of a domain model, a middle tier and a web application. Every one of those projects has specific build system requirements. These requirements are, in broad strokes:

  • dependency management
  • build profiles (development, production)
  • custom plugins (hibernate tools, embedded Tomcat or Jetty)
  • compilation
  • unit tests
  • keyword substitution in resource files
  • release management
  • javadocs and documentation
  • packaging

The first part of MoneyPile I will try to switch to Gradle while writing the next 2 blog posts is the domain model, together with the MoneyPile parent build of course. This should be fairly straightforward, since all I’m doing here is compiling, running unit tests, packaging and generating DDL files using Hibernate tools. Oh, and generating Eclipse project files would be nice too. So let’s get to it.

Installing Gradle

Installing Gradle is quite simple, much like Ant or Maven. Just download the distribution from the Gradle website and unzip it. Then add an environment variable called GRADLE_HOME pointing to the Gradle installation directory (in my case c:/java/gradle-0.8), and add %GRADLE_HOME%/bin to your PATH environment variable (on Linux this would be $GRADLE_HOME/bin). Then fire up a command prompt, and run ‘gradle -v‘. If installed correctly you should get some information about Groovy, Ant and Java versions.

gradle command prompt 1

By the way: if you’re on Mac OS X, you can use MacPorts to install gradle. You still need to set the environment variables though:

export GRADLE_HOME=/opt/local/share/java/gradle
export PATH=$PATH:$GRADLE_HOME/bin

After installing on Mac OS X, my first build failed, because of a permissions problem on the ~/.gradle directory. The simplest solution is to just delete ~/.gradle first:

sudo rm -rf ~/.gradle

First things first

The first thing to do is to create the Gradle build file, in the project folder. The name of this file is build.gradle. The file can remain empty for now.

Dependencies

The domain model of MoneyPile consists of a number of POJOs, annotated with JPA, Bean Validation (JSR 303) and Hibernate Search annotations. In the unit tests, I use the Bean Validation framework for testing valid and invalid instances of model classes.

This means we will need to specify a number of dependencies before considering compilation. These are the compile scope dependencies:

  • ejb3 persistence
  • hibernate annotations
  • hibernate commons annotations
  • validation api
  • hibernate search
  • log4j
  • slf4j
  • slf4j log4j12
  • commons logging

The test scope needs some more dependencies:

  • junit 4
  • hibernate validator 4

The good thing about Gradle dependency management is that you can use the familiar Maven repositories. Adding the Maven central repository to your build is as simple as this:

repositories {
  mavenCentral()
}

I’m using some dependencies that are not yet available in the central repository however, so let’s take a look how we can add the JBoss repository to our build as well. According to the manual, adding this line to the repositories section should do the trick:

mavenRepo urls: "http://repository.jboss.com/maven2"

That was simple. Now let’s add the dependencies:

dependencies {
  compile group: 'org.slf4j', name: 'slf4j-log4j12', version: '1.5.2'
  compile group: 'log4j', name: 'log4j', version: '1.2.14'
  compile group: 'commons-logging', name: 'commons-logging', version: '1.1.1'
  compile group: 'org.hibernate', name: 'ejb3-persistence', version: '1.0.2.GA'
  compile group: 'org.hibernate', name: 'hibernate-annotations', version: '3.4.0.GA'
  compile group: 'org.hibernate', name: 'hibernate-commons-annotations', version: '3.3.0.ga'
  compile group: 'javax.validation', name: 'validation-api', version: '1.0.CR5'
  compile group: 'org.hibernate', name: 'hibernate-search', version: '3.1.0.GA'
  testCompile group: 'junit', name: 'junit', version: '4.4'
  testCompile group: 'org.hibernate', name: 'hibernate-validator', version: '4.0.0.CR1'
}

Nice and concise isn’t it? Each line corresponds to a <dependency> tag in a Maven POM. ‘compile’ and ‘testCompile’ are the dependency scope, ‘group’ is the groupId, ‘name’ is the artifactId and ‘version’ is the version.

Building

We should now be able to compile the project. The project source tree follows the Maven convention, so building it takes only one line in the build file:

usePlugin 'java'

Now, when running the command ‘gradle build’, we see that Gradle does about the same as a Maven build: compile sources, process resources, process classes, compile test sources, process test resources, process test classes, run unit tests, package a jar and assemble a project archive.

gradle command output 2

Our build still needs some finetuning however. The jar file that was generated is called model-unspecified.jar because we didn’t specify a project name or version. Also, we want to be sure we’re compiling for java 1.6. Finally, we want to specify some default build tasks. To do all this, we add the following to the top of our build file, right below the usePlugin line:

defaultTasks 'clean', 'build'

sourceCompatibility = 1.6
version = '1.0.0-SNAPSHOT'
jar.baseName = 'moneypile-model'
manifest.mainAttributes(
   'Implementation-Title': 'BeeWorks MoneyPile - Domain Model',
   'Implementation-Version': version
)

Thanks to the default tasks, we can now just run the command ‘gradle’, which will perform both the clean and build tasks. The resulting jar-file will be named ‘moneypile-model-1.0.0-SNAPSHOT’.

Dependencies again

So far so good. Now let’s look in a little more detail at what gradle does with the project dependencies. Transitive dependencies are nice, but you often end up downloading jar-files you don’t really want.

The compile and testCompile classifiers you see on each line of the dependency section above are actually artifact configurations, used to group the dependencies in what Maven calls scopes. You can define scopes yourself, but the java plugin already defines some typical ones: compile, runtime, testCompile, testRuntime, archives and default. Check the Gradle manual for details on all of those. The ones we are interested in right now are compile, testCompile, runtime and testRuntime. Note that by default, the compile (and testCompile) configuration does not use transitive dependencies.

When using Maven, one of the most useful plugins is the dependency plugin, which can generate a dependency tree for your project. Luckily, Gradle has this as well:

gradle -q --dependencies

This spits out the dependency tree in the console, but on Windows that’s rather useless, so I prefer to have a report. To get this, add this line to the top of the build file:

usePlugin 'project-reports'

Then, using the command ‘gradle dependencyReport’, you get a file dependencies.txt in the folder build/reports/project, which contains the dependency tree. Looking at the runtime dependencies of the domain model project, you’ll notice that there is more than one version of hibernate-core in the tree. Gradle determines which version to use in a very simple way: it just takes the newest version. In this case, this is just fine. If you want to fine-tune your dependencies, Gradles offers a lot of options: you can work with excludes, you can turn off transitive dependencies for certain modules, or you can put your dependencies in a Subversion repository. Whichever way you choose, it’s certain to be more concise than using excludes in a Maven POM.

To really be sure which jar-files gradle will put in your runtime classpath, the Gradle Cookbook offers a nice trick: just copy all artifacts from the runtime configuration in a special build folder using this task:

task copyRuntimeDependencies(dependsOn: configurations.runtime.buildArtifacts, type: Copy) {
    into('build/output/lib')
    from configurations.runtime
    from configurations.runtime.allArtifacts*.file
}

For the MoneyPile domain model project, this results in the following files:

antlr-2.7.6.jar
asm-1.5.3.jar
asm-attrs-1.5.3.jar
cglib-2.1_3.jar
commons-collections-3.1.jar
commons-logging-1.1.1.jar
dom4j-1.6.1.jar
ehcache-1.2.3.jar
ejb3-persistence-1.0.2.GA.jar
hibernate-3.2.1.ga.jar
hibernate-annotations-3.4.0.GA.jar
hibernate-commons-annotations-3.3.0.ga.jar
hibernate-core-3.3.1.GA.jar
hibernate-search-3.1.0.GA.jar
jta-1.1.jar
log4j-1.2.14.jar
lucene-core-2.4.0.jar
persistence-api-1.0.jar
slf4j-api-1.5.2.jar
slf4j-log4j12-1.5.2.jar
validation-api-1.0.CR5.jar
xml-apis-1.0.b2.jar

That looks about right for now. I’m sure I’ll need more control over the dependencies further down the line, but for now, let’s consider the dependency management of MoneyPile as done.

OK, so what’s next? We have compiling, unit tests and packaging covered. That leaves us with generating Eclipse project files and DDL files using Hibernate Tools.

Eclipse support

Generating Eclipse project files is very easy: add the eclipse plugin to the build file:

usePlugin('eclipse')

Then run ‘gradle eclipse’, and you’re ready to import your project in Eclipse. There is no such support for NetBeans yet, but apparently IntelliJ IDEA 9.0 will have native Gradle support.

Hibernate DDL generation

I saved the hardest part for last of course. For my Maven builds, I wrote a custom plugin that uses Hibernate Tools to generate DDL files for a number of databases, configurable with a plugin property. As far as I can see, there are several options for doing this with Gradle, but I’ll explore those in the next part of this series.

You can download the build file for this post here. It’s only 37 lines. Just to give you an idea: The POM file for this project was about 350 lines of XML.

Day 1 conclusion

My first experience with Gradle was surprisingly pleasant. The manual is very good, and everything I tried worked as advertised. The biggest advantage Gradle has is obviously its use of Groovy, rather than XML. This makes build files much more concise and easier to read, while being extremely powerful. The architecture its creators built on top of that seems well thought out. I can’t wait to start with the next part of my quest!

Reacties op “From Maven to Gradle – part 1”

  • Thanks for the heads up on this. I too had some issues with Maven so I’ll have to check this out.

  • Great write-up man. Totally breaks down the barrier to entry for Gradle for people working with Maven, especially when working with the Ivy DSL they provide. I haven’t run into this as much with Java, but when working with Maven and other languages (i.e. – Flex), it’s usually a train wreck. If you can’t find a plugin to do what you want, you end up doing something similar to what you had to do for Hibernate, and write your own plugin. The beauty of Ant was that I could tackle those one-offs in my build script without having to construct infrastructure to support it (and the ant-run plugin Maven doesn’t count :) ). Maven still has me locked in because of three things, dependency management, pom inheritance, and multi-module projects. Gradle is the first real contender I’ve seen that may offer a flexible enough solution to getting rid of the pain I experience with Maven. Looking forward to the 2nd post.

  • This is awesome. The time you wrote this is perfect for me.
    I have a hate-love relationship to maven, so I hope I can now switch to sth. better with my timefinder.de project.

    Thanks!

    > Gradle is the first real contender I’ve seen

    There is also buildr. Check out this picture:
    http://karussell.wordpress.com/2009/09/29/evolution-of-build-systems/

  • Adding your blog post to the External Resources on the Gradle wiki: http://docs.codehaus.org/display/GRADLE/External+Resources

  • [...] the first part of this series, we discovered Gradle and tried to convert a simple Maven project, the domain model [...]

  • The missing part from all these (for 0.8+ gradle) is how to exclude dependencies.

    All the old blog posts use addDepedency([], {exclude:}) or some other styntax which doesn’t run anymore

    any ideas :) ?

  • [...] the first 2 parts of this series on Gradle, we have migrated a simple project from Maven to Gradle, and [...]

Reageer