Why you should use the Maven Ant Tasks instead of Maven or Ivy

Like many others I really like Ant and I intend to keep using it until something better comes along. And I strongly feel that when you try to use *only* Maven for everything, you give up control over a few things.

Are there things that Ant does better than Maven? Oh yes, IMHO. Here are a few examples of typical Java web-app build related stuff that turn out to be “edge cases” in Maven meaning that you have to struggle, read up on obscure plugins or resort to ugly hacks in order to get things to work.

Replacing text in files only for development mode

<target name="jetty-setup-dev" unless="production.mode">
	<echo>making webapp changes for development mode...</echo>       
	<replace file="target/${war.name}/WEB-INF/web.xml" 
		token="org.apache.wicket.protocol.http.WicketFilter" 
		value="info.jtrac.wicket.devmode.ReloadingWicketFilter"/>                  
</target>

The example above shows how I switch to a custom Wicket filter in web.xml that supports hot-class-reloading and debug logging – but only in development mode. The Ant replaceregexp task can give you even more regex driven find-and-replace goodness.

Capturing input from the user

<target name="confirm">
	<input message="Are you sure?" validargs="y,n" addproperty="input"/>
	<condition property="abort">
		<equals arg1="n" arg2="${input}"/>
	</condition>
	<fail if="abort">User aborted.</fail>       
</target>

All the best trying to do that in Maven. Oh and using the maven-antrun-plugin doesn’t count – that’s cheating ;)

ZIP a bunch of files from where *you* want into what *you* want

<target name="dist-jtrac-src">
	<mkdir dir="target"/>
	<zip destfile="target/jtrac-src.zip">
		<zipfileset dir="nbproject" prefix="jtrac/nbproject"/>
		<zipfileset dir="etc" prefix="jtrac/etc"/>
		<zipfileset dir="src" prefix="jtrac/src"/>
		<zipfileset dir="." includes="*.*" prefix="jtrac"/>
	</zip>         
</target>    

Maven apologists will promptly point you to the great Maven Assembly Plugin. Sorry, no thanks. I sincerely tried to understand how to use *that* piece of work a few times in the past but gave up in despair.

Execute an arbitrary Java class with control over everything (args, classpath, etc.)

<target name="hbm-export" depends="compile">
	<input message="Hibernate Dialect:" addproperty="dialect"/>
	<java classname="org.hibernate.tool.hbm2ddl.SchemaExport" fork="true">
		<classpath>
			<path refid="test.classpath"/>
			<path path="target/classes"/>           
		</classpath>            
		<jvmarg value="-Dhibernate.dialect=${dialect}"/>
		<arg value="--text"/>
		<arg value="--delimiter=;"/>
		<arg value="--output=target/schema.sql"/>
		<arg value="src/main/resources/*.hbm.xml"/>
	</java>
</target>

This is a target I keep around to forward-engineer the DDL from my Hibernate entities on demand, for any database of the user’s choosing – just enter the dialect at the prompt.

There are times when even Ant may not have all the answers and a technique I use (or misuse) effectively is to use a custom Java class to extend Ant. So I don’t even need to learn how to create custom Ant tasks and mess around with the Ant classpath ensuring that the build remains “xcopy” friendly. Now, the “Maven way” to handle custom stuff is to write a custom plugin. Which is the easiest thing in the world right? Heh – don’t get me started.

There are quite a few more examples I can think of but that’s not the main point of this blog post. Hopefully I have made a case for “why Ant” above. A deal-clincher for me is the fact that all popular IDE-s have excellent support for Ant. NetBeans (which I use) takes it to a whole different level of tight integration – for e.g. you can map Ant targets to toolbars, shortcuts and things like that. Yes, IDE support for Maven is getting there but not as good as Ant IMO.

But am I saying that you shouldn’t use Maven at all? Not really. Let me explain.

For Java projects, the essential one thing that Maven does well and Ant cannot – is dependency management. So IMO the way to go is to use *both* Maven and Ant. The problem is that Maven obviously goes out of the way to *not* use Ant.

Fortunately, there *is* a way to get the information about dependencies from your Maven “pom.xml” project file across to your Ant “build.xml” file. I am referring to the Maven Ant Tasks. The thing is – there is very little documentation out there on how to use this. My conspiracy theory is that the Maven guys don’t want you to know about the existence of the Maven Ant Tasks. This is backed up by the fact that the two options for using Ant mentioned here in the official Maven FAQ cunningly avoid linking to the actual Maven Ant Tasks! (update: more than half a year after this blog post was published, the Maven FAQ was updated with a new entry “How can I use Maven features in an Ant build?” :)

I’ve explored custom plugin based approaches for bridging Maven and Ant with some success, and I’ve gotten some good feedback and even patches for the NetBeans specific integration that I tried for. I wasn’t aware of the Maven Ant Tasks until recently because as I said, the Maven guys have done a good job of making it hard to find. So I hope this post helps improve things on this front.

How to use the Maven Ant Tasks
I’ll use my Seam / JSF vs Wicket comparison project as a reference. First create a standard pom.xml like you would normally as in this example, just focus on dependencies.

This is the magic snippet that goes into build.xml that establishes the link between Maven and Ant:

<target name="mvn-init" unless="compile.classpath" xmlns:artifact="urn:maven-artifact-ant">    
    <typedef resource="org/apache/maven/artifact/ant/antlib.xml" uri="urn:maven-artifact-ant" 
        classpath="lib/maven-ant-tasks.jar"/> 
    <condition property="maven.repo.local" value="${maven.repo.local}" else="${user.home}/.m2/repository">
        <isset property="maven.repo.local"/>
    </condition>           
    <echo>maven.repo.local=${maven.repo.local}</echo>
    <artifact:localRepository id="local.repository" path="${maven.repo.local}"/> 
    <artifact:pom file="pom.xml" id="maven.project"/>
    <artifact:dependencies pathId="compile.classpath" filesetId="compile.fileset" useScope="compile">
        <pom refid="maven.project"/>
        <localRepository refid="local.repository"/>
    </artifact:dependencies>
    <artifact:dependencies pathId="test.classpath" filesetId="test.fileset" useScope="test">
        <pom refid="maven.project"/>
        <localRepository refid="local.repository"/>
    </artifact:dependencies>
    <artifact:dependencies pathId="runtime.classpath" filesetId="runtime.fileset" useScope="runtime">
        <pom refid="maven.project"/>
        <localRepository refid="local.repository"/>
    </artifact:dependencies>
</target>
  • You need just the maven-ant-tasks JAR in “lib” as per line #03, if you are picky about that, you can even consider downloading it automatically as part of the build. You can find details on how to do that in this blog post on how to use the Maven Ant Tasks instead of Ivy by Josh Suereth (who deserves credit for some of the ideas in this post).
  • I prefer bootstrapping the Maven integration within a target instead of globally for the build so that targets that don’t depend on Maven can run faster. Also, I don’t have to “pollute” the entire build.xml with the XML namespace stuff, it is isolated to just within this one target.
  • The unless=”compile.classpath” in line #01 is a nice way to ensure that the bootstrapping happens only once even when Ant executes multiple targets that depend on each other.
  • Lines #04 – 06 allows the user to override the default local repository location by having a “maven.repo.local” property entry set for e.g. in “build.properties”. You can even use relative paths here – so all your dreams of having Maven builds that work even when you copy the project folders across to some other machine – can come true ;)
  • The rest intializes classpath and fileset variables for compile, test and runtime scopes – which should be sufficient for most WAR projects.
  • Something that really surprised the heck out of me is that you don’t need Maven installed for all this to work! You are getting all the benefits from a single teeny tiny JAR file. Spread the word, people!

Now you can write your compile target like this, note the ‘classpathref” on line #03:

<target name="compile" depends="mvn-init">
    <mkdir dir="target/classes"/>        
    <javac srcdir="src/main/java" destdir="target/classes" classpathref="compile.classpath"/>
    <copy todir="target/classes">
        <fileset dir="src/main/java" excludes="**/*.java"/>
    </copy>         
    <copy todir="target/classes" failonerror="false">
        <fileset dir="src/main/resources"/>
    </copy>              
</target>

I normally use Jetty for development (like this) so I don’t need to assemble a WAR most of the time. People who haven’t tried this really don’t know what they are missing, huge time saver – not to mention tight IDE integration for things like debugging. But when the time comes to build a WAR this is how you could do it (note the reference to the Maven fileset at line #11):

<macrodef name="war-exploded">
    <attribute name="deployDir" default="target/${war.name}"/>
    <sequential>
        <copy todir="@{deployDir}">
            <fileset dir="src/main/webapp"/>
        </copy>
        <copy todir="@{deployDir}/WEB-INF/classes">
            <fileset dir="target/classes"/>
        </copy>
        <copy todir="@{deployDir}/WEB-INF/lib" flatten="true">
            <fileset refid="runtime.fileset"/>         
        </copy>
    </sequential>
</macrodef>          

<target name="war-exploded" depends="compile">
    <war-exploded/>        
</target>

<target name="war" depends="war-exploded">       
    <zip destfile="target/${war.name}.war" basedir="target/${war.name}"/>
</target>

Note that I don’t even use the Ant “war” task, I just ZIP ;) Maven evangelists would have you believe that Ant leads to un-maintainable, verbose build scripts that are hard to re-use across projects. I call bullsh*t. Look at the Ant “macrodef” usage above which means that I can reuse the WAR routine and do something like this for Tomcat later:

<target name="tomcat-war-exploded" depends="war-exploded">
    <war-exploded deployDir="${tomcat.home}/webapps/${war.name}"/>
</target>

What about Ivy?
Whoa, I never thought that this would turn out to be such a long post, let me end by addressing the question: should projects use Ivy instead?

My opinion is NO. Actually, the trigger for this blog post is that I recently joined a project-team already using Ivy and I’m having a hard time adjusting. Anyway, trying to be objective, here are my reasons:

  • One of the good things about Maven is that it enforces the closest thing we have in the Java world to a standard directory layout structure. I find it hilarious that Ivy falls into the same trap as Maven in that they can’t say good things about Maven just like the Maven guys avoid saying good things about Ant. So you won’t find any recommendations to use a standard project structure in the Ivy “best practices” documentation ;) Ivy projects tend to use ad-hoc directory structures, this is what I have observed.
  • Maven obviously has been around longer than Ivy, this translates into more documentation, more users who are used to working (and struggling ;) with it etc.
  • IDE project descriptor creation support: For example, Ivy has no official way to create your Eclipse project descriptor files for you. This itself would be a reason for me to avoid Ivy. There are some ways to manage your Eclipse classpath like IvyDE which honestly doesn’t have much documentation and something like this and this Google code project which don’t look too stable. If you have a Maven pom.xml and Maven installed, just do “mvn eclipse:eclipse” and you can even add “-DdownloadSources=true” – and you can ensure that even things like the “output folders” like “target/classes” and “target/test-classes” – are standard across your team.
  • Needless to say, using the Maven Ant Tasks approach gives you a smooth migration path to Maven. If you do it right, nothing prevents Maven die-hards on your project team from using only the pom.xml and avoiding Ant completely. This is how I have set up the JTrac build for the last 3 years.
  • Maven is positioned as a “project management and comprehension” tool (whatever that means ;) and there is certainly a good ecosystem of plugins that for e.g. can generate your project web-site, run Checkstyle reports, etc.
  • As someone already using Maven for a while, I found it really unsettling that having to use Ivy for another project was resulting in a ton of JAR duplication on my hard disk. And why on earth did the Ivy guys decide to re-invent the structure in which JAR files are stored in the local repo? So you can’t reuse your Maven repo or copy stuff around.
  • Something that the Ivy guys would hate to admit is that ultimately you end up downloading JARs from the Maven public repositories only. So all that talk of doing a better job than Maven kind of sounds flat. People are quick to blame Maven when resolving of dependencies fails to work as expected but this is usually the fault of whoever setup the metadata in the repositories. There’s not much that Ivy can do to solve that problem.
  • I think Maven multi-project support where you keep common stuff in a parent POM and have projects depend on each other is better than how you would do it in Ivy (although I haven’t explored this fully yet).
  • The only thing that appears to be compelling about Ivy is that doing “exclusions” is far less verbose than how you would have it in Maven. But if the Maven guys just fix this one issue

As an example of the kind of thinking that Ivy encourages which I don’t agree with is this quote from a blog post by Dan Allen (Seam committer and author of “Seam In Action”):

But the most absolutely vital feature of Ivy, and what makes it infinitely more useful than Maven 2, is the fact that you can disable transitive dependencies.

I’m outspoken about my position on transitive dependencies. I see them as both evil and a silly device designed for novices (and people with way too much time on their hands). It makes your build non-reproducible and unstable and in the end causes you more work than the work you were attempting to eliminate by moving to Maven 2. This “feature” really is Maven’s boat anchor. Trust me on this one, it’s trivial to define which libraries your application depends on. There really aren’t that many! And you can put all the worrying aside about exclusions.

All I’ll say is that if you disable transitive dependencies, then what is the point. You might as well write a script that downloads a bunch of JAR files listed in a properties file. And about the statement that “there really aren’t that many!” – I really don’t agree. Take Seam itself as an example of a framework that has so many dependencies (and sub dependencies) and expecting developers to keep track of the sub-dependencies (instead of the project-owners who should be updating the Maven repository metadata correctly) – just seems wrong to me.

Did I miss any reasons for not using Ivy? Do let me know in the comments!

An alternative Maven plugin for Ant and NetBeans

A while back I wrote a custom Maven plugin to “escape” from Maven because I personally prefer Ant for build scripting. I now generally use Maven only for JAR file dependency management and my plugin acts as a bridge between the Maven and Ant worlds. The plugin can automatically generate an Ant build.xml file from the Maven pom.xml file and it supports web-application (WAR) projects. A few people aware of the existence of this ‘underground’ plugin have been asking me for more details, and since I got a chance to tweak things recently, I’m putting down some information in this blog post on how to get the plugin and try it out. Maybe you will find it useful.

Some of the features the plugin provides are:

  • Generated build file takes care of WAR packaging and deploying to Tomcat
  • Once generated, Ant build is independent of Maven and offline
  • Build file is IDE-independent, portable and includes targets for running JUnit tests as well

NetBeans users may find some of the advanced features interesting:

  • Generation of the following different types of NetBeans projects:
    • Java Free Form Project
    • NetBeans ‘native’ Java SE Project
    • NetBeans ‘native’ Java EE (web) project
  • The Ant build (for Free Form project mode) is simple, human-readable and easily customizable instead of the horribly complex “build-impl.xml” that NetBeans users are familiar with
  • In addition to the standard clean, compile, run actions etc., the generated Free Form project supports the following NetBeans-specific IDE-integration when a file is selected in the project explorer window:
    • run single file
    • debug single file
    • run JUnit test for single file
    • run JUnit test in debug mode for single file
    • debug web application
    • Hot Deploy single file into debug session

You just need the light-weight Java SE version of NetBeans in order to start and stop Tomcat, debug and even hot-deploy classes for your web-app. No extra plugins are required for e.g. Maven or Tomcat support. You can try this out for yourself by following the instructions below.

I’ll use the Wicket “quickstart” Maven Archetype as an example of how you can quickly get up and running using Ant on a Maven web-project. You need Maven 2 installed as a pre-requisite. The Wicket “quickstart” can be found here:

http://wicket.apache.org/quickstart.html

Open a command prompt, cut and paste the magic command and you get a simple web-application project along with a Maven POM definition, ideal for testing out the plugin features. This is the command I used for this example:

mvn archetype:create -DarchetypeGroupId=org.apache.wicket -DarchetypeArtifactId=wicket-archetype-quickstart -DarchetypeVersion=1.3.4 -DgroupId=com.mycompany -DartifactId=myproject

This will create a directory called “myproject” which contains a “pom.xml” file as well as some source code. We need to add a few lines to the pom.xml in order to use the custom plugin. First declare the repository for the plugin as follows by adding this snippet just above the <dependencies> section:

<pluginRepositories>
    <pluginRepository>
        <id>jtrac.info</id>
        <url>http://j-trac.sourceforge.net/repository</url>
    </pluginRepository>
</pluginRepositories>

And within the <plugins> section, just add these four lines:

<plugin>
    <groupId>info.jtrac</groupId>
    <artifactId>maven-antprops-plugin</artifactId>
</plugin>

That’s all that needs to be added to the POM, just 10 lines that don’t get in the way of any other Maven stuff you may want to do. To generate the Ant script, change to the newly created project folder and run the following command:

mvn antprops:generate

This will create a build.xml file and a “build-deps.properties” file. The properties file is a Plain Old Java Properties file that contains classpath information extracted from Maven as well as a list of dependencies with “runtime” scope which is used to create the “WEB-INF/lib” part of your WAR.

If you have Tomcat and Ant available on your system you can start Tomcat and deploy the WAR right away from the command line. But first you will need to point the build-file to where Tomcat is installed and this is a simple one-time matter of creating a “build.properties” file with a single line on it. Something like this:

tomcat.home=C:/peter/opt/apache-tomcat-6.0.14

From the command prompt, change to the “myproject” root directory (which should contain build.xml and build.properties by now) and type the following command:

ant tomcat-start-debug

Once Tomcat starts you should be able to verify that the app was successfully deployed by pointing your browser to: http://localhost:8080/myproject/

Eclipse users can use the “mvn eclipse:eclipse” command to open the project in Eclipse and start working with the Ant targets.

The command to create a NetBeans free-form project from the Maven POM is as follows:

mvn antprops:nbfreeform

This will create the “nbproject” folder and the NetBeans project file as well as an extra Ant script for NetBeans integration targets. Now you can open “myproject” as a NetBeans free-form project.

The “tomcat-start-debug” target is conveniently mapped to the NetBeans “run” IDE action, so just clicking on the big green “play” button should deploy the web-app running on Tomcat. If you right click on the project root “myproject” node, a “Stop Tomcat” context menu item should helpfully appear as well.

You can try the clean, compile, and war Ant targets etc. – which should work. The Wicket archetype includes a sample JUnit test also in “TestHomePage.java”. After clicking on this file to select it within the NetBeans project explorer window – go to the Run –> Run File –> Test “TestHomePage.java” toolbar menu (or hit CTRL + F6) and you should be able to run only the selected test. But it will fail now with a “java.lang.NoClassDefFoundError: javax/servlet/ServletException”.

This problem actually gives us a good example for demonstrating how the “build-deps.properties” file can be updated when dependencies change or are added to the POM. Now add the missing Servlet API dependency to the “pom.xml” file by adding this snippet within the <dependencies> section:

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>servlet-api</artifactId>
    <version>2.4</version>
    <scope>provided</scope>
</dependency>

Then re-run the command to generate build-deps.properties. Note that this command is designed so that if a “build.xml” file already exists, it will not be over-written.

mvn antprops:generate

Now run the test case again from NetBeans and it should pass. Unit testing a UI component is easy with Wicket!

One of the neat things about the Wicket archetype is that it embeds a Jetty web-app server using a small Java class called “Start.java” lying in the “test” source structure. The POM is already set with the right dependencies for Jetty and so you can simply right-click on this Java file within the NetBeans project explorer and choose “Run”. This will start a Jetty instance and deploy the WAR as well. You will be able to see the app running at http://localhost:8080/. Look ma, no Tomcat!

One more thing you can try is right-click on “Start.java” and do “Debug” instead of run. This will kick off a proper NetBeans debug session bringing up some sub-windows for viewing breakpoints, call stack etc. You can now try hot-deploying a single class without re-starting the app-server (Jetty). Try changing the text of the Label in HomePage.java to something else and saving the file. With the file selected in the NetBeans project window, look for the “Apply Code Changes” menu option – one place to find it is within the “Run” toolbar menu. A message should appear in the log saying that a class was reloaded. Refresh the browser and you should see the change in the text displayed.

Hot deploy should also work with Tomcat after initiating a debug-session (Attach Debugger) and then using the “Apply Code Changes” menu option.

The plugin is still experimental so if you find problems, do let me know in the comments. The commands for creating “native” NetBeans projects are “mvn antprops:nbjavase” and “mvn antprops:nbjavaee”. You need NetBeans with Java EE support to try the second option.

The plugin also should make it possible to package a project along with dependencies and hand-off to someone without Maven or internet access – but I haven’t completed this part yet.