Another way to Integrate Maven 2 with Ant (and NetBeans)

I use Spring, Spring WebFlow, Acegi, Hibernate etc. and it all adds up to about 30 or so jar-file dependencies. Finally I got around to exploring Maven 2 and maybe it’s just me – but I got the feeling that the Maven folks want you to throw away Ant and use Maven for everything. But I really do want to use Ant, it does what I want and is flexible, and I have a lot of Ant snippets I want to re-use. Besides the excellent Ant support in IDEs that we take for granted, with NetBeans specifically, I can call an Ant target and pass the currently selected file in the IDE package explorer as a parameter (this is a time-saver when running things like Checkstyle). Another problem when considering Maven: there is no Maven 2 plugin yet for EMMA – the code coverage tool which I use.

I was sure that I wanted Maven only for dependency management and that I would use Ant to do everything else. One option was to have Ant snippets embedded within the Maven POM file. I quickly ruled out this approach, as it would result in a large “hybrid” pom.xml file and require me to use Maven for all build operations.

The other option was to use the Maven Ant Tasks which I guess is what most people do (you can see real life examples here and here). This approach requires some initial setup for it to run, you have to fix your Ant installation to include the maven-ant library and then modify your build.xml to include the Maven “artifact” namespace.

One problem I found with the Maven Ant Tasks is this: if you use Ant from within your IDE, it would use the version bundled with the IDE – not the Ant that is in your PATH. So you would need to modify all the Ant installations that you use. Or you could reference the path of the jar directly when declaring the Maven Ant Tasks in your build file. But the whole point of Maven is doing away with the project “lib” folder right? :P

The “non-invasive” approach I devised to use Maven for dependency management goes like this:

First I wrote a Maven 2 plug-in (see the source here) whose only responsibility is to pass control to a Beanshell script. The beanshell script also gets handed references to Maven helper objects that can do the dependency management heavy-lifting. Once this plug-in is installed you are freed from the Maven world of mojo compiling and installing – and you can focus on tweaking your beanshell script instead of worrying about the Maven build phases :)

The job of the beanshell script is to automatically generate a standard properties file that lists down all dependencies – and with separate sections for the “test” and “runtime” classpaths. This is how the generated properties file looks like: (see the real file here)


# *** generated file - not created by hand! ***
m2.repo=C:/Documents and Settings/pt34469/.m2/repository
test.jars=\
  ${m2.repo}/javax/servlet/jsp-api/2.0/jsp-api-2.0.jar:\
  ${m2.repo}/javax/servlet/servlet-api/2.4/servlet-api-2.4.jar:\
  ${m2.repo}/commons-logging/commons-logging/1.0.4/commons-logging-1.0.4.jar:\
  ${m2.repo}/org/springframework/spring-mock/2.0-m3/spring-mock-2.0-m3.jar:\
  ${m2.repo}/org/springframework/spring/2.0-m3/spring-2.0-m3.jar:\
  ${m2.repo}/log4j/log4j/1.2.13/log4j-1.2.13.jar:\
  ${m2.repo}/javax/servlet/jstl/1.1.2/jstl-1.1.2.jar:
runtime.jars=\
  commons-logging/commons-logging/1.0.4/commons-logging-1.0.4.jar,\
  org/springframework/spring/2.0-m3/spring-2.0-m3.jar,\
  log4j/log4j/1.2.13/log4j-1.2.13.jar,\
  javax/servlet/jstl/1.1.2/jstl-1.1.2.jar,

And then my Ant compile and war targets look like this: (Note how the war target picks up the required jars from your local Maven repository.)

    
    <property file="build-deps.properties"/>

    <target name="compile">
        <mkdir dir="target/classes"/>
        <javac srcdir="src/main/java" 
            destdir="target/classes" debug="true" classpath="${test.jars}"/>
        <copy todir="target/classes">
            <fileset dir="src/main/resources"/>
        </copy>
    </target>

    <target name="war-exploded" depends="compile">
        <copy todir="target/jtrac">
            <fileset dir="src/main/webapp"/>
        </copy>
        <copy todir="target/jtrac/WEB-INF/classes">
            <fileset dir="target/classes"/>
        </copy>
        <copy todir="target/jtrac/WEB-INF/lib" flatten="true">
            <fileset dir="${m2.repo}" 
                includes="${runtime.jars}" casesensitive="false"/>
        </copy>
    </target>

Those who use Maven with Eclipse, may relate to this approach which is quite similar to running “mvn eclipse:eclipse” to generate the “.classpath” file. The nice thing about the familiar Ant-and-properties-file based approach is that it is IDE neutral. By the way, it also avoids the extra “eclipse:add-maven-repo” step needed to set the M2_REPO environment variable for your Eclipse workspace.

You can download the zip file of the “maven-bshrunner-plugin” source here. If you have Maven 2 installed, you should be able to “mvn install” this into your local repository easily.

[Edit 2006-09-29: this approach has now changed to not depend on BeanShell and you can check out the latest source code for this plugin. For more details, go here.]

The complete beanshell script that does all the work can be viewed here. (You can also have a look at the complete Ant build file here.)

I could very well have had all the work done in the Maven plugin (mojo) instead of delegating to a beanshell script but I really like the advantage of not having to compile my build automation while tweaking it. If you look at the last part of the beanshell script you will see that it actually “dependency manages” EMMA and Checkstyle as well – and that too on-the-fly. (I did try to make this generic but that was too much work :) Note that the beanshell script has a couple of Java 5 specifics (mainly the much nicer “for-loop”s) but that should be easy to change.

Now the bonus part: if you use NetBeans (or plan to switch :), there is a way to use external properties files in your project.xml. So the relevant parts of my project.xml (see the whole file here) look like this: (note how the classpath elements point to “${test.jars}” )

    
    <java-data xmlns="http://www.netbeans.org/ns/freeform-project-java/2">
        <compilation-unit>
            <package-root>src/main/java</package-root>
            <classpath mode="compile">${test.jars}</classpath>
            <source-level>1.5</source-level>
        </compilation-unit>
        <compilation-unit>
            <package-root>src/test/java</package-root>
            <unit-tests/>
            <classpath mode="compile">${test.jars}</classpath>
            <source-level>1.5</source-level>
        </compilation-unit>
    </java-data>
    <web-data xmlns="http://www.netbeans.org/ns/freeform-project-web/1">
        <web-module>
            <doc-root>src/main/webapp</doc-root>
            <classpath>${test.jars}</classpath>
            <context-path/>
            <j2ee-spec-level>1.4</j2ee-spec-level>
        </web-module>
    </web-data>

And it works like a charm, the big list of Mavenized jars show up as expected in the NetBeans dialog for configuring your project “Java Sources Classpath”. In fact the webapp & JSP editing / syntax coloring support works fine – with even the JSTL and tag libraries available on the classpath picked up smoothly.

I think this approach is a nice migration path to Maven because you can opt to use Maven straight away for things that it does well (like site-generation). The only thing “extra” in my Maven project file are the last few lines declaring the “beanshell runner” plugin. Once you have the plugin installed, you can execute the following command to run any valid beanshell script:

mvn bshrunner:bshrunner -Dbsh.file=path-to-your-beanshell-file

7 Responses to Another way to Integrate Maven 2 with Ant (and NetBeans)

  1. robert says:

    if you want dependencyhandling together with ant I’d suggest you checkout ivy at http://jayasoft.org/ . It also integrates smothly with eclipse.

    //Robert

  2. Peter Thomas says:

    I did find Maven a bit difficult to integrate with Ant to start with – but I decided to stick with it and work out a custom approach. Maven clearly does some things well like site-generation and now I get the best of both worlds. Some people feel that Maven may become the de-facto build tool (like Ant is now) in the long term although I’m personally not so sure about that.

    Note that beyond the normal install of Maven and Ant, the approach discussed here does not require a single extra jar in either the Ant installation or a relative path (“./lib” for example) – which is an advantage over Ivy. All jars are handled by Maven, including those for non-core tasks such as Checkstyle and Code Coverage (EMMA).

    Also you don’t need to add sections to your existing build file to do dependency management (or introduce a second Ant script).

  3. Pingback: Incremental Operations » Maven2 site-deploy: now that’s a useful plugin

  4. Pingback: Exporting a Maven build for users who don’t have Maven « Incremental Operations

  5. interested one says:

    hi!

    I just found your article about maven ant integration. I am currentliy working on a ant project, where I want to use my existing maven repo. I found your approach interesting, but it seems, the article is not up to date anymore. Regarding the fishEye comments about leaving out BeanShell…
    Is this correct?

    best regards,
    Andreas

  6. Peter Thomas says:

    Yes, I dropped BeanShell and made the approach as generic as possible. You can look at the JTrac pom.xml and see how it is used. The source for the plugin is now here:

    http://fisheye3.atlassian.com/browse/j-trac/trunk/maven-antprops-plugin

  7. Pingback: An alternative Maven plugin for Ant and NetBeans « Incremental Operations

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: