Recently I’ve been working on an implementation of hybris, a Java e-commerce package whose build system is based on Ant. The Maven Ant Tasks plugin is available for importing dependencies, but I wasn’t happy with the default tasks that wrap this plugin.
They make no attempt to purge old dependencies that have been removed from the POM. The build script keeps your Eclipse projects up to date with most changes, but does not update classpath settings when dependencies change. Most importantly, the Maven plugin uses the excludeTransitive=true option, so Maven will not import the dependencies of your dependencies.
Fortunately, every step in the build process includes a “callback”, which is a hook where you can inject your own customizations. I decided to forgo the default task for updating Maven dependencies, and do it myself in one of the extension’s callbacks.
Cleaning the extension’s /lib directory was easy, as was setting excludeTransitive=false to resolve the full dependency hierarchy. However, overwriting the Eclipse project’s .classpath file was a bit more tricky than I expected. Basically, I needed to:
- Write to .classpath the fixed information that never changes
- Iterate through each dependency in the extension’s /lib directory, and append a line in .classpath for it
- Append the closing </classpath> tag at the bottom of .classpath
Eek! It’s been awhile since I’ve made heavy use of Ant, and I suddenly remembered that it doesn’t really have general loops or iterator constructs. The open-source ant-contrib bundle comes installed with hybris, and it provides useful tasks such as <for> and <foreach>. However, it is poorly-documented, and hasn’t been updated in forever (much like Ant!). Also, the property scoping rules are very quirky, and it looks like I would have to create a completely separate Ant task just for the code within my loop.
There has to be an easier way to put a simple loop within an Ant script, right?
I have been primarily living in the Maven world for some time now. I knew that ad-hoc scripting is a standard feature in more modern build systems, such as Gradle (Groovy), Buildr (JRuby), and sbt (Scala). However, after a little research I was shocked to discover that good-old Ant was there a decade ago!
A few things to notice:
- Scripts can access and create Ant tasks themselves, with project.createTask(“<name>”). See lines 11-15.
- Ant tasks are basically treated as POJO’s, and setter methods are available to set their attributes. Here, echo.setAppend(true) is equivalent to the XML tag <echo append=”true”>.
- After setting any necessary attributes, the perform() method actually invokes the Ant task.
This experience has caused me to take a fresh look at Ant. I still prefer Maven for most greenfield projects, and in a special case I might look for an excuse to gain more experience with Gradle. However, if you are inheriting someone else’s build scripts, or just like the maturity of Ant and how well it integrates with things, then script injection can be a powerful reward to make Ant worth your while.