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) 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.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 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.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 thebuild.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 ofbuild.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
- Ant
- Gradle
- Gradle userguide
- Maven
- Groovy
- Ivy
- more on Ant loops, for example:
- Ant Concat task
- Maven Resource plugin
This used to be my blog. I moved to http://tomek.kaczanowscy.pl long time ago.
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