Reply to comment
Ant, Gradle and Maven - comparison - install script
Submitted by Tomek Kaczanowski on Tue, 11/03/2009 - 20:58This 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
deployfolder, - does some more file-related stuff - creates directories and copies files,
- produces ready-to-unpack-and-use file:
tar.gz(for Linux) andzip(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.xmlverbosity...).
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 ofbuild.xml) to ~7100 ofbuild.gradle, - lines count: from ~740 (~130 of
pom.xml+ ~610 ofbuild.xml) to ~720 ofbuild.gradle.
Some more comments on this:
- I could easily reduce the numbers of lines in
build.gradlebut 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
forloops (from ant-contrib), then thebuild.xmlfile 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 ofbuild.xml&pom.xmlduo, - 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
- Ant
- Gradle
- Gradle userguide
- Maven
- Groovy
- Ivy
- more on Ant loops, for example:
- Ant Concat task
- Maven Resource plugin