Sunday, February 18, 2007

Hating Maven less than Ant

I've been writing Java code since 1996 and struggling with build tools at least as long. Ant is the most comprehensive and Maven is the most ambitious. I hate both of them, mostly because they each require me to describe my project's build requirements using XML, which should never be used as a programming language, though often it's used as a configuration file format, but because all configuration files ultimately will evolve into programming languages... I digress.

For different reasons, Ant and Maven are both terrific and terrible at once. Ant allows you to describe your build however you please, but you must effectively do it from scratch for each project. Maven encourages many best practices by providing a lot of build features out-of-the-box, adopting a sort of "convention over configuration" philosophy popularized by the Ruby on Rails project, among others.


So for my money, Maven is the logical choice for a typical Java project. Unfortunately, few Java projects are typical, especially few enterprise Java projects. And for those projects, Maven can be frustrating because, like a child or a turtle, it's difficult to make it do something it doesn't want to do.

For example, let's say I'm debugging a build and I want to echo the value of a property to the console during a build. With Ant, I could add a target to build.xml:

<project>
<target name="debug">
<echo>The value is ${some.property}</echo>
</target>
</project>

With Maven 1, I can invoke Ant tasks within a goal defined in maven.xml:

<project>
<goal name="debug">
<echo>The value is ${some.property}</echo>
</goal>
</project>

Maven 1 provides easy access to Ant tasks, as you can see. Unfortunately, doing anything non-trivial with Maven 1 involves Jelly, which is pure "executable XML hell". Maven 2 did away with Jelly, but...

With Maven 2, it's bewilderingly complicated to echo a simple property to the console. You must include the "antrun" plugin in your pom.xml, configure an execution to run Ant's echo task, and bind that execution to a particular phase of your build (but see comments for other options). Ready? Here we go:

<project>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>debug</id>
<phase>validate</phase>
<configuration>
<tasks>
<echo>The value is ${some.property}</echo>
</tasks>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

Note that our choice of the "validate" phase is completely arbitrary, but in order for our task to execute, it must be bound to some phase. Unlike our Ant and Maven 1 examples above, Maven 2 provides no way to invoke a single task without invoking all other tasks bound to the same phase along with all those before it. Maven 2 introduced the concept of profiles to address this perceived limitation, but I'll save that discussion for a future blog post.

Clearly, the Maven designers have violated a fundamental tenet: "the solution cannot be more abstract than the problem!" In trying to define and encourage best practices, Maven unapologetically sacrifices simplicity and flexibility, arguably two of the most important qualities of any software build tool, even for those projects exhibiting all accepted best practices.

Somewhat surprisingly, I generally still think it's worthwhile to convert your Ant projects to Maven, no matter how difficult. Maven's pain is concentrated, sort of like filling a cavity early in the project, resulting in a relatively pain-free existence once your project leaves the dentist's office. Ant is more like a minor toothache annoying you for the entire duration of your project, and then spreading to the other projects in your mouth as each begins its life with a copy of some other's build.xml file. Yes, of course it's simpler to do simple things with Ant, but adding enough of those simple things to projects over time can (and will, in my experience) lead to a pretty darn complex, brittle, and incomprehensible build.

In the long run, I think you'll end up hating Maven less than Ant, too. But if you insist on using Ant, use Ivy to manage your dependencies.

UPDATED Maven 2 example, removing unnecessary <property> element to reflect Eric's comment.

11 comments:

Eric Redmond said...

I like your post - kindof - but your example of Maven 2 is incorrect. You don't HAVE to bind a goal to a phase... ever. You can run particular goals in the same way you execute particular ant tasks. This saves you from having to create an "execution". Once configured, just type:

mvn antrun:run

The second problem is, you don't have to bind a property to echo the value in the antrun plugin... you can drop the whole "property" element thing.

Finally - and this is a minor thing - you don't need the groupId for core plugins. This turns your example into the much more concise:

<project>
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-antrun-plugin</artifactId>
        <configuration>
          <tasks>
            <echo>The value is ${some.property}</echo>
          </tasks>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

Jim Crossley said...

Hi Eric. Thanks for the clarifications. Omitting the execution restricts you to a single antrun configuration in the pom, right? Or else, which one would it run? All of them or an arbitrary one?

As far as the "property" element goes, you're right, and I'll update the post to reflect that. When contriving the example, I was thinking of having to set the property prior to invoking an external build.xml file with the "ant" element.

Thanks again.

Eric Redmond said...

You can configure under the "plugin" element, which is a short hand that is inherited by all goals. Or you can configure under "execution", which configures only the goals given in that particular execution. Usually "plugin" scope is enough (and in the majority of cases desired).

Just configuring a plugin won't run it. If you bind the execution to a phase, it will run at that phase. Otherwise a goal will only run if you explictly run it... hence the example I gave:

mvn antrun:run

Anyway - still a good post.

jimbojw said...

> In trying to define and encourage best practices, Maven unapologetically sacrifices simplicity and flexibility...

This is exactly why I hate Maven more than Ant. Ant makes the easy things easy and the hard things possible.

Maven makes the easy things complicated and the hard things so complex it takes more time to figure out how to use it then to write the code it's supposed to build in the first place!

Eugene Kuleshov said...

If you insist on using Ant, use Antlib for Maven to manage your dependencies. This is basically set of Ant tasks that can work with the same metadata as Maven.

redsolo said...

I would say that I hate maven more than Ant. Maven looks great on paper, as it has dependencies, create reports, etc. But when I start to use it the xml file looks like hell, as there are so many things that has to be setup.

Ive actually gone back to ant, as maven 2 went overboard with all the new features.

Ant is a build tool, and it works out greate for me

Unknown said...

Jim, I had similar feelings in the beginning. It took me half a year of Maven usage until I dared to dive into writing my own Mojos - and surprise, surprise: It's really simple - apart from some api-atrocities, and, of course, you can do anything you want. No xml involved.

Jim Crossley said...

Marcus, I agree Mojos are fairly straightforward to implement, but they're still relatively heavy compared to Ant tasks, IMHO. So yes, I'd prefer Java to XML, but neither is ideal for a build scripting language. Maybe Raven will pleasantly surprise us. (http://raven.rubyforge.org)

Eric Redmond said...

I write most of my Maven Mojos in Ruby - actually:

http://mojo.codehaus.org/jruby-maven-plugin/howto.html

Kenan Sevindik said...

I definetely agree that few enterprise projects are typical, as a result maven tries to make you wear an iron cloth.

Moreover, if you are trying to use it within Eclipse, it is highly possible that your IDE will function no more than Notepad because of unmature Eclipse plugin support.

I still don't want to give up ant's flexibility, and if you are a software producer, it is possible that you will come up with some generic project structures, still sufficiently different than other enterprise projects, and ant tasks that could be reused over and over.

Finally, I want to emphasize that if you use ant, use it with ivy dependency management. Ivy is a really satisfactory dependency management tool. It is also very useful to construct a corporate repository.

Blogscab said...

Ant is a perfect example of a first order solution whose legs are little stubs. Sure, you can become an ant guru and make your midget run faster than the others, but you are still siring a midget. Kenan's argument is a common one: it let's me put things where I want. You can do that in Maven!? Personally, I don't find that to be so important, especially when the resulting structure is what matters and that is also, as a rule, dictated (JEE), so what's the point?

I do agree that the eclipse support for Maven is anemic.