Ant, Gradle and Maven - comparison - install script

This is a part of "Ant/Gradle/Maven comparison" series.

A common task during development is the creation of an installable version of software. The one I mention in this post is a real one - this is something I've been working working with since few months. It does few things related to Fuse ESB:

  • unpacks Fuse sources,
  • updates some config files,
  • retrieves few JARs from Maven repository and put them into deploy folder,
  • does some more file-related stuff - creates directories and copies files,
  • produces ready-to-unpack-and-use file: tar.gz (for Linux) and zip (for Windows).

I started to write this with Ant/Maven, and then switched to Gradle. I'll present few code snippets here, that should give you a decent understanding of difference that Gradle makes. Please judge for yourself if the switch from Ant/Maven to Gradle was worth the effort.

The code was written with Maven 2.0.9, Ant 1.7.1 and Gradle 0.8.

Artifacts download

The task is: download few JARs from Nexus Maven repository and put them int deploy dir of the installation package.

Ant + Maven

There is no straightforward way of doing this with Ant, so I used Maven for this. Probably, I could also use Ivy, but I don't know Ivy, and I had no time to learn it. The solution I implemented was, that on of the Ant targets used exec task to execute mvn command with some parameters. The pom-dependencies.xml had two tasks to achieve:

  • download the artifacts (listed in <dependencies> section),
  • copy them to given dir - using maven-resource-plugin

It works well, but is ugly - the developer is required to understand both Ant and Maven, and important information are scattered among two build files - build.xml and pom-dependencies.xml - which is obviously bad.

Here is a fragment of build.xml that calls Maven:

<target name="download bundles to deploy"
    description="downloads bundles and copies them to deploy folder">
  <exec dir="." executable="${mvn.exec}">
    <arg line="-f pom-dependencies.xml -Dtmp=../${deploy.dir} install" />
  </exec>
</target>

(in fact few more parameters were passed to Maven, but I removed them for the sake of simplicity).

The pom-dependencies.xml file that was called by Ant, used maven-resource-plugin to do the job. It looked roughly like this (you will noticed the ${tmp} variable that was passed by Ant):

<project>

... a lot of boilerplate code

  <dependencies>

... a lot of dependencies here

  </dependencies>

  <build>
    <defaultGoal>generate-sources</defaultGoal>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-dependency-plugin</artifactId>
        <configuration>
          <outputDirectory>${tmp}</outputDirectory>
          <overWriteReleases>true</overWriteReleases>
          <overWriteSnapshots>true</overWriteSnapshots>
          <overWriteIfNewer>true</overWriteIfNewer>
          <includeArtifactIds> here some patterns specified </includeArtifactIds>
        </configuration>
        <executions>
          <execution>
            <phase>generate-sources</phase>
              <goals>
                <goal>copy-dependencies</goal>
              </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

[UPDATED 2009-11-08: As Koziołek pointed out, Maven3 will offer a DSL syntax, so the new POMs will be much more concise. Good for them. Anyway, in case of Maven 3, it is only a syntactic sugar (as far as I know), which is not much compared to Gradle.]

Gradle

Gradle offers "best of both worlds", so implementing such scenario is straightforward:

repositories {
    mavenRepo urls: ["http://secret.repo.pl/content/groups/public"]
}
configurations {
    jarFiles { transitive = false }
}

dependencies {
  jarFiles "pl.kaczanowscy.tomek:abc:1.2"
  jarFiles "pl.kaczanowscy.tomek:xyz:1.1"
  ...
}

...

task deployBundles (description: "copies bundles to deploy folder") << {
  copy {
    from configurations.jarFiles
    into "${deployDir}"
  }
}

The Gradle solution has two advantages:

  • the whole logic is in one place,
  • it is very concise (especially compared to Maven pom.xml verbosity...).

Copying with(-out) loops

We provide two installation versions: for Linux and for Windows. The files that are used to create each version are gathered in two directory trees - winDir and linuxDir. This means, that each file should be added to both of them. There are several files copied in different tasks (documentation, licenses, config files etc.).

Ant

In Ant, the loops are not easily available. In fact, I was not aware of their existence (now I know, that you have to use ant-contrib, but still it doesn't look nice). So, my Ant script snippets looked as follows (a lot of copy&paste, which makes me so angry):

<target name="copy-readme-files">
  <copy dir="notes" todir="${winDir}/docs" />
  <copy dir="notes" todir="${linuxDir}/docs" />
</target>

Similar code was repeated few times in the build.xml.

[UPDATED 2009-11-09: I see that others also don't use loops from ant-contrib. See TestNG build.xml for an example. I wonder if Cedric is not aware of this possibility (as I was) or maybe he decided that it is not very handy ?]

Gradle

In Gradle, loops are natural, because you have access to everything that Groovy offers - including sweet .each iterators:

osDirs = [winDir, linuxDir]
...
task copyReadmeFiles << {
  osDirs.each { osDir ->
    copy {
      from notesDir
      into "${osDir}/docs"
    }
  }
}

In other places in the real build script, many more things were done for each directory, and then the loops offered by Gradle proved to be very handy.

Config files update

The task is: add a line to the one of the configuration files of Fuse ESB (the similar task is to replace some data (e.g. few lines) in some configuration files).

Ant

<target name="log-level-add" description="adds log level">
  <concat destfile="${winDir}/etc/config.properties"
    append="true">#felix.log.level=${logLevel}</concat>
  .. same for linux here
  </concat>
</target>

Gradle

task addLogLevel(description: "adds log level") << {
  osDirs.each { osDir ->
    ant {
      File configFile = new File("${osDir}/etc/config.properties")
      configFile.append("#felix.log.level=${logLevel}")
    }
  }
}

For me it is very easy to understand what is the meaning of Gradle code, but the ant "concat" task is more cryptic. And once again the each loop proves it usefulness.

Summary

The new version of the build script - written with Gradle - has given me some benefits. Here they come:

Total control

Using Gradle, I have this very nice feelings that:

  • the build script behaves the way I want it to,
  • I'm in total control of the build process,
  • I can change it with ease anytime I feel like changing it.

Smaller build script

The build script shrunk significantly (mainly thanks to elimination of boilerplate code of pom.xml and introduction of iterations in build.xml):

  • characters count: from ~12200 (~2100 of pom.xml + ~10100 of build.xml) to ~7100 of build.gradle,
  • lines count: from ~740 (~130 of pom.xml + ~610 of build.xml) to ~720 of build.gradle.

Some more comments on this:

  • I could easily reduce the numbers of lines in build.gradle but I like it the way it is; anyway, the difference in number of characters (drop by ~40%) says it all,
  • yes, I admit, if I used for loops (from ant-contrib), then the build.xml file would have been smaller,
  • at the same time, Gradle version of the build script is enhanced with some additional comments and even minor new functionalities.

Improved readability

The new building script - build.gradle - is much more readable:

  • there is no boilerplate code,
  • DSL is much more readable than XML,
  • some cryptic Ant tasks were replaced with easy to understand file operations,
  • there is only one build file now - build.gradle - instead of build.xml & pom.xml duo,
  • the build script does more than previously - I can now build only Windows or Linux versions, or both of them - previously I had no choice, both versions were created all the time,
  • thanks to improved readability I was able to introduce some changes, that I hesitated to add in Ant/Maven version.

Painless migration

It is also worth mentioning, that the migration from Ant to Gradle was rather painless. I started by importing of the whole build.xml file into Gradle script and gradually translated it task by task (please refer to Gradle userguide to learn more about Ant/Gradle cooperation) .

Links

maven assembly plugin

FYI, check out the maven "assembly" plugin (assembly:assembly). It's a standards plugin that's designed to do essentially what you're doing (package together classes, dependencies and such into some format and zip/tar it up).
Note that it requires yet another XML file for configuration, so it won't win any verbosity competitions, and it's still not as flexible as Groovy, but if you were stuck in the Maven world, I think it would do what you want.

assembly plugin is not enough here

hi,

thanks, I'm aware of assembly plugin. Yes, it can be used for packing, but that is only a subset of what I need.

--
Tomek

Tomek, Maven 3 support Groovy

Tomek, Maven 3 support Groovy like pom file. You can write pom in Groovy, Scala, Ruby or use XML.
But... At this moment Maven 3 is in beta phase.

Koziołek

Maven 3 Groovy pom is only a syntactic sugar !

Hi Koziołek,

don't be fooled by Maven "Groovy like pom files". It is only a shortcut notation, a syntactic sugar, that will reduce the size of pom files but nothing more. It does not add any flexibility to rigid structure and build lifecycle imposed by Maven. They change the skin, but the underlying mechanism unfortunately stays the same...

So, there is no comparison between this syntactic sugar and the power and flexibility that Gradle offers. The common denominator is use of Groovy, but that is all.

BTW, I already discussed it here: http://weblogs.java.net/blog/johnsmart/archive/2009/10/21/writing-your-p...

--
Tomek

ant-box

maven is not as flexible as ant/groovy/install script, because it is not ant/groovy/install script ? The idea is not very fresh.
think you spare time on maven pointlessly and in the same ant-box.

I don't compare apples to oranges

Hi ant-box,

thanks for your comment, but I'm afraid you didn't get the point. I'm not saying that Maven is no match to Ant/Groovy/Shell scripts when it comes to writing install scripts - because it is obvious.

The original script was 90% Ant and 10% Maven (for dependency handling only). It is now much readable and maintainable after moving to Gradle. What I'm trying to demonstrate, is that Gradle is much more powerful than Ant & Maven together for writing such stuff.

--
Tomek

Please comment using