Creator of the Apache Tapestry web application framework and the Apache HiveMind dependency injection container. Howard has been an active member of the Java community since 1997. He specializes in all things Tapestry, including on-site Tapestry training and mentoring, but has lately been spreading out into fun new areas including functional programming (with Clojure), and NodeJS. Howard is a DZone MVB and is not an employee of DZone and has posted 79 posts at DZone. You can read more from them at their website. View Full User Profile

Maven: Throwing Out the Bath Water, Keeping the Baby

11.02.2009
| 8209 views |
  • submit to reddit

... in other words, a first step towards using Maven for dependency management but NOT builds. That's the irony of Maven ... they've conflated two things (dependency management and builds) in such as way that they make the useful one (dependency management) painful because the build system is so awful.

As an interrum step between full Maven and (most likely) Gradle, I've been looking at a way to use Maven for dependencies only in a way that is compatible with Eclipse ... without using the often flakey and undependable M2Eclipse plugin.

In any case, rather than assuming that dependencies might change at any point in time at all, let's assume that when I change dependencies (by manually editing pom.xml) I know it and am willing to run a command to bring Eclipse (and my Ant-based build) in line.

First, my pom.xml:

<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"
  xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.example</groupId>
  <artifactId>myapp</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <dependencies>
    <dependency>
      <groupId>org.apache.tapestry</groupId>
      <artifactId>tapestry-hibernate</artifactId>
      <version>${tapestry-version}</version>
      <scope>compile</scope>
      <exclusions>
        <exclusion>
          <artifactId>log4j</artifactId>
          <groupId>log4j</groupId>
        </exclusion>
        <exclusion>
          <artifactId>slf4j-log4j12</artifactId>
          <groupId>org.slf4j</groupId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>org.apache.tapestry</groupId>
      <artifactId>tapestry-test</artifactId>
      <version>5.2.0-SNAPSHOT</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>2.4</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.lucene</groupId>
      <artifactId>lucene-core</artifactId>
      <version>2.4.1</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-search</artifactId>
      <version>3.1.1.GA</version>
    </dependency>
    <dependency>
      <groupId>commons-lang</groupId>
      <artifactId>commons-lang</artifactId>
      <version>2.4</version>
    </dependency>
    <dependency>
      <groupId>commons-beanutils</groupId>
      <artifactId>commons-beanutils</artifactId>
      <version>1.8.0</version>
    </dependency>
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>0.9.17</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.5.8</version>
      <type>jar</type>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>postgresql</groupId>
      <artifactId>postgresql</artifactId>
      <version>8.4-701.jdbc4</version>
    </dependency>
    <dependency>
      <groupId>xerces</groupId>
      <artifactId>xercesImpl</artifactId>
      <version>2.4.0</version>
      <type>jar</type>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>com.howardlewisship</groupId>
      <artifactId>tapx-datefield</artifactId>
      <version>${tapx-version}</version>
    </dependency>
    <dependency>
      <groupId>com.howardlewisship</groupId>
      <artifactId>tapx-prototype</artifactId>
      <version>${tapx-version}</version>
    </dependency>
    <dependency>
      <groupId>org.testng</groupId>
      <artifactId>testng</artifactId>
      <version>5.9</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <repositories>
    <repository>
      <id>tapestry360-snapshots</id>
      <url>http://tapestry.formos.com/maven-snapshot-repository/</url>
    </repository>
    <repository>
      <id>repository.jboss.org</id>
      <url>http://repository.jboss.org/maven2/</url>
    </repository>
  </repositories>
  <properties>
    <tapestry-version>5.1.0.5</tapestry-version>
    <lucene-version>2.4.1</lucene-version>
    <tapx-version>1.0.0-SNAPSHOT</tapx-version>
  </properties>
</project>

(This was adapated from one of my client's POMs).

Next, an Ant build file that compiles this, runs tests and builds a WAR:

<project name="example" xmlns:mvn="urn:maven-artifact-ant">

  <property name="classes.dir" value="target/classes" />
  <property name="test.classes.dir" value="target/test-classes" />
  <property name="web.lib.dir" value="target/web-libs" />
  <property name="webapp.dir" value="src/main/webapp" />
  <property name="webinf.dir" value="${webapp.dir}/WEB-INF" />

  <path id="compile.path">
    <fileset dir="lib/provided" includes="*.jar"/>
    <fileset dir="lib/runtime" includes="*.jar" />
  </path>

  <path id="test.path">
    <path refid="compile.path" />
    <pathelement path="${classes.dir}" />
    <fileset dir="lib/test" includes="*.jar" />
  </path>

  <target name="clean" description="Delete all derived files.">
    <delete dir="target" quiet="true" />
  </target>

  <!-- Assumes that Maven's Ant library is installed in ${ANT_HOME}/lib/ext. -->

  <target name="-setup-maven">
    <typedef resource="org/apache/maven/artifact/ant/antlib.xml" uri="urn:maven-artifact-ant" />
    <mvn:pom id="pom" file="pom.xml" />
  </target>

  <macrodef name="copy-libs">
    <attribute name="filesetrefid" />
    <attribute name="todir" />
    <sequential>
      <mkdir dir="@{todir}" />
      <copy todir="@{todir}">
        <fileset refid="@{filesetrefid}" />
        <mapper type="flatten" />
      </copy>
    </sequential>
  </macrodef>

  <macrodef name="rebuild-lib">
    <attribute name="base" />
    <attribute name="scope" />
    <attribute name="libs.id" default="@{base}.libs" />
    <attribute name="src.id" default="@{base}.src" />
    <sequential>
      <mvn:dependencies pomrefid="pom" filesetid="@{libs.id}" sourcesFilesetid="@{src.id}" scopes="@{scope}" />
      <copy-libs filesetrefid="@{libs.id}" todir="lib/@{base}" />
      <copy-libs filesetrefid="@{src.id}" todir="lib/@{base}-src" />
    </sequential>
  </macrodef>

  <target name="refresh-libraries" depends="-setup-maven" description="Downloads runtime and test libraries as per POM.">
    <delete dir="lib" quiet="true" />
    <rebuild-lib base="provided" scope="provided"/>
    <rebuild-lib base="runtime" scope="runtime,compile" />
    <rebuild-lib base="test" scope="test" />
    <echo>
      
*** Use the rebuild-classpath command to update the Eclipse .classpath file.</echo>
  </target>

  <target name="compile" description="Compile main source code.">
    <mkdir dir="${classes.dir}" />
    <javac srcdir="src/main/java" destdir="${classes.dir}" debug="true" debuglevel="lines,vars,source">
      <classpath refid="compile.path" />
    </javac>
  </target>

  <target name="compile-tests" depends="compile" description="Compile test sources.">
    <mkdir dir="${test.classes.dir}" />
    <javac srcdir="src/test/java" destdir="${test.classes.dir}" debug="true" debuglevel="lines,vars,source">
      <classpath refid="test.path" />
    </javac>
  </target>

  <target name="run-tests" depends="compile-tests" description="Run unit and integration tests.">
    <taskdef resource="testngtasks" classpathref="test.path" />
    <testng haltonfailure="true">
      <classpath>
        <path refid="test.path" />
        <pathelement path="${test.classes.dir}" />
      </classpath>

      <xmlfileset dir="src/test/conf" includes="testng.xml" />

    </testng>
  </target>


  <target name="war" depends="run-tests,-setup-maven" description="Assemble WAR file.">

    <!-- Copy and flatten the libraries ready for packaging. -->

    <mkdir dir="${web.lib.dir}" />
    <copy todir="${web.lib.dir}" flatten="true">
      <fileset dir="lib/runtime" />
    </copy>
    <jar destfile="${web.lib.dir}/${pom.artifactId}-${pom.version}.jar" index="true">
      <fileset dir="src/main/resources" />
      <fileset dir="${classes.dir}" />
    </jar>

    <war destfile="target/${pom.artifactId}-${pom.version}.war">
      <fileset dir="${webapp.dir}" />
      <lib dir="${web.lib.dir}" />
    </war>
  </target>
</project>

The key target here is refresh-libraries, which deletes the lib directory then repopulates it. It creates a sub folder for each scope (lib/provided, lib/runtime, lib/test) and another sub folder for source JARs (lib/provided-src, lib/runtime-src, etc.).

So how does this help Eclipse? Ruby to the rescue:

#!/usr/bin/ruby
# Rebuild the .classpath file based on the contents of lib/runtime, etc.

# Probably easier using XML Generator but don't have the docs handy

def process_scope(f, scope)
 # Now find the actual JARs and add an entry for each one.
 
 dir = "lib/#{scope}"

 return unless File.exists?(dir)
 
 Dir.entries(dir).select { |name| name =~ /\.jar$/ }.sort.each do |name|
   f.write %{  <classpathentry kind="lib" path="#{dir}/#{name}"}
   
   srcname = dir + "-src/" + name.gsub(/\.jar$/, "-sources.jar") 

   if File.exist?(srcname)
      f.write %{ sourcepath="#{srcname}"}
   end
   
   f.write %{/>\n}
 end
end

File.open(".classpath", "w") do |f|
  f.write %{<?xml version="1.0" encoding="UTF-8"?>
<classpath>
  <classpathentry kind="src" path="src/main/java"/>
  <classpathentry kind="lib" path="src/main/resources"/>
  <classpathentry kind="src" path="src/test/java"/>
  <classpathentry kind="lib" path="src/test/resources"/>
  <classpathentry kind="output" path="target/classes"/>
  <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
}

 process_scope(f, "provided")
 process_scope(f, "runtime")
 process_scope(f, "test")
 
 f.write %{
</classpath>
}
    
end

That's pretty good for half an hour's work. This used to be much more difficult (in Maven 2.0.9), but the new scopes attribute on the Maven dependencies task makes all the difference.

Using this you are left with a choice: either you don't check in .classpath and the contents of the lib folder, in which case you need to execute the target and script in order to be functional ... or you simply check everything in. I'm using GitHub for my project repository ... the extra space for a few MB of libraries is not an issue and ensures that I can set up the exact classpath needed by the other developers on the project with none of the usual Maven guess-work. I'm looking forward to never having to say "But it works for me?" or "What version of just about anything do you have installed?" or "Try a clean build, and close and reopen your project, and remove and add Maven dependency support, then sacrifice a small goat" every again.

Next up? Packaging most of this into an Ant library so that I can easily reuse it across projects ... or taking the time to learn Gradle and let it handle most of this distracting garbage.

From http://tapestryjava.blogspot.com/

Published at DZone with permission of Howard Lewis Ship, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Martijn Dashorst replied on Mon, 2009/11/02 - 2:54am

Or you run the commandline maven eclipse plugin (a plugin for maven that generates eclipse project files): mvn eclipse:eclipse Since 2.7 this plugin is able to generate project dependencies for your dependencies if you have them checked out in your workspace. No need to have a maven pom, an ant script and a ruby script.

Mladen Girazovski replied on Mon, 2009/11/02 - 4:23am

Using Maven2, Ant & Graddle for Build & Dependeny Management to be dependent on Eclipse for a build?

Are you sure that this is an improvement? ;)

Edwin Quita replied on Mon, 2009/11/02 - 4:47am

just add the eclipse plugin in your pom.xml ================================= org.apache.maven.plugins maven-eclipse-plugin 2.4 false ================================= to generate the eclipse project meta data files: mvn eclipse:eclipse to delete: mvn eclipse:clean

Yannick Menager replied on Mon, 2009/11/02 - 7:55am

Using maven just for dependency management is rather useless, because ivy (http://ant.apache.org/ivy/) is *way* more powerful and flexible than maven in that area.

My advice, ditch maven, and use ant+ivy :)

Bin yan replied on Mon, 2009/11/02 - 9:42am

Wow, If I ever had one of my devs waste this much time to not learn 1 tool (maven) fully and instead force others to have to to learn 3 (maven, ant and ruby), I would personally assign you to counting the 1's and 0's in the next failed build.  Seriously, maven is a bi*tch to learn because you need to know about poms, filtering, dependencies, settings, plug-in managment and a few others before you can truly fly with it.   The Definitive Maven book would have been really nice to have when I 1st learned it.

Still I have encountered so  many ant builds, that have created their own maven-like ant-based DSL, that it is sad.  I usually replace these with a maven build and educate people on how maven can help.

Howard Lewis Ship replied on Mon, 2009/11/02 - 12:00pm

Yes, this is a huge improvement.

My Eclipse project loads much more quickly without M2Eclipse wasting time looking for updates to snapshots I don't care about, and printing spurious error messages to the console.

I no longer have to do the frequent disable/re-enable Maven to get my dependencies to resolve correctly.

My Ant build (which I consider the canonical build) is now identical to the Eclipse build (except it makes a proper distinction between the production classpath and the test classpath, which is helpful).

The other developers don't need Maven, they need just the Maven ant tasks. If they don't do the command line build, they don't even need that.

 Maven makes many curious assumptions, one of which is that dependencies (especially snapshot dependencies) can change at any time. This makes Maven builds unstable and unpredicatable.

Don't get me wrong ... I've been using Maven since before 1.0 on a variety of open source and properietary projects ... and my experience is that when using Maven you need to devote about .75 time of an engineer to maintaining the build ... usually in very uneven blocks when things suddenly go wonky. By simplifying to using Ant that goes almost completely away (build maintenance occurs when the requirements of the build change).

 I also have found that proponents of Maven are either using it in a very simply way (no multi-projects) or have built up a large amount of "tribal knowledge" to get it to Maven to work their way. These are the same people who dismiss criticism of Maven as evidence that the critic either is "lazy",  or "doesn't understand how Maven works."  I have it on good authority that I'm neither :-)

 Meanwhile, the basic dependency structure of Maven (groups, artifacts, versions, even snapshots, as well as source JARs) are very strong and, too me, natural, as is the flexibility of combining the central Maven repository with additional or local ones. Maven dependencies are the baby I don't want to throw out with the bath water.

Whether Buildr or Gradle or any of the others will actually make good on Maven's promise is an open question I expect to explore. 

Jesse Kuhnert replied on Mon, 2009/11/02 - 12:15pm in response to: Bin yan

I see you are speaking from un-experience. I hope you treat your devs better than your comments make you sound. 

Seriously though,  "learning" maven means learning how to spot and find solutions to its bugs.  It is useful sometimes,  but does have enough bugs crop up often enough to make anyone with any normal amount of experience both appreciate and despise it. 

To be fair,  the bugs are mostly likely coming from the plugins and not as often out of core but I've had enough experience with maven as a user as well as seeing how maven core gets worked on to know that it isn't a matter of learning how to use the tool correctly.  

Jilles Van Gurp replied on Mon, 2009/11/02 - 2:01pm

There's two points to make about maven:

1 - it gets the job done, eventually. Yes, you will end up with about as much pom.xml as you had build.xml (or more). And there will be copy-paste reuse for the 'proper' pom xml incantations to happen at the right time and place since nobody on your project will be 'worthy' enough to actually know how to edit a pom file. In short, no advantage whatsoever authoring a pom.xml vs a build.xml. Unless your project is so trivial you didn't need a build system independent from eclipse to begin with.

2 - It will have a measurable impact on your build time. Think hours per week. In the negative. Yes, you had better enjoy that stupid maven non-info crawl by because you will be seeing a lot of it.http://www.zeroturnaround.com/blog/the-build-tool-report-turnaround-times-using-ant-maven-eclipse-intellij-and-netbeans/

Dependency management is brilliant. But not if it happens 20 times per hour. Maven has been wasting lots more of my time checking for changed (and ultimately not changed dependencies) than I ever spent checking for actual genuine dependencies in need of a change that actually required a minute of my attention to fix. Even, so, I know how to download a dependency and I am potty trained to actually read the changelog that goes with it. Instead, I'm stuck now with a project full of dependencies that nobody ever checked the changelogs for in completely arbitrary versions that happen to work

So, dependency management didn't solve a problem I ever had and it completely demolished a solution I used to have (incremental, ligthning fast builds). So, yes, I hate it with a passion and will fight against it ever used on a project under my control. Ant is not perfect but at least execution time is manageable. 

Seriously, in 1997 I was using a free downloadable incremental compiler by IBM (now known as the eclipse compiler) that was able to incrementally compile my source code in under 1 second. On a 486 33Mhz windows NT 3.51 machine with a whopping 32MB. Now twelve years later I have to suffer maven wasting minutes of my time figuring out which fucking dependencies to use (The same as 3 minutes ago. Unchanged. Guaranteed.), where my files are, how to run my unit tests, etc. Not saving me any time whatsoever but making me wait ages to confirm that. I'm all for test driven design, but 99 times out of 100, I just wanted a quick round trip between my editor and my browser.

Loren Kratzke replied on Mon, 2009/11/02 - 5:03pm in response to: Jilles Van Gurp

To Howard I suggest you evaluate Netbeans as it has excellent Maven and Ant support.

I also wanted to comment on the Build Tool Report (http://www.zeroturnaround.com/blog/the-build-tool-report-turnaround-times-using-ant-maven-eclipse-intellij-and-netbeans/) mentioned above.

As a Netbeans user I noticed that Netbeans, Ant, and Maven were listed in the list of incremental build tools. That's just strange. First of all, Netbeans uses an Ant based build system by default and integrates seemlessly with Maven. In fact it works perfectly with Maven. Anybody who uses Netbeans is by default using an Ant or Maven build system. There IS no Netbeans build system. It does not exist (beyond internal compilation while editing).

In this regard I find the comment, "Eclipse is dominating the landscapewith 32%" as insulting. The 5% Netbeans pie slice needs to be merged into the Ant and Maven slices with the rest of the Netbeans users. Sure, you kick off a build from the IDE but it is all Ant or Maven after that.

The fact that Eclipse uses its own build system and has broken Maven support is at the root of the problem that led to the solution that spawned this article in the first place. So I question the wisdom of bringing an Eclipse dependency into the project as a solution to anything. To do so would be to throw the baby out with the bath water. ;-)

In my Maven based workplace, the Eclipse users are forced to build from the command line. I just shake my head. What a world...If you like Ant, and you like Maven, then why in the world are you using Eclipse?

I agree with Jilles in large part. Why check dependencies 20 times per hour? That said, it only weighs down small projects. Large projects spend the vast majority of build time in javac and jar processes. For our team, Maven did actually solve a huge dependency problem (while creating a few other smaller problems). Some problems can never be solved, they can only be shifted from one domain to another.

Peter Thomas replied on Mon, 2009/11/02 - 8:56pm in response to: Martijn Dashorst

+1 (to first comment)

The Ruby part is totally un-necessary. I blogged about how to supplement a Maven pom.xml with an Ant build.xml (using the same Maven Ant Tasks approach), quite simpler IMHO: Why you should use the Maven Ant Tasks instead of Maven or Ivy

John Ferguson Smart replied on Mon, 2009/11/02 - 9:27pm

Why check dependencies 20 times per hour? If it were so, indeed, it was a grievous fault ;-). However Maven does not check for snapshot updates for every build - by default, it's daily, and you can configure it. Build lifecycle plugins use stable releases, bound to the version of Maven, so no automatic updates there. If you are using snapshot plugins, they will be updated daily, but I would recommend against it, as in this case you are deliberately asking for an unstable build process. As for snapshots in related modules or projects - well, that's what they're for. You want these to be updated to keep in sync with your fellow developers. If not, use a stable release version. So, if your builds are slow, don't blame it on Maven checking for updates - check your configuration or look elsewhere.

Tim O'brien replied on Mon, 2009/11/02 - 10:10pm in response to: Jilles Van Gurp

Jilles, you are right. If you make absolutely no effort to learn about how Maven works, it isn't very useful.

Jason Van Zyl replied on Tue, 2009/11/03 - 1:39am

Rebuttal to Howard's nonsense: http://www.sonatype.com/people/2009/11/maven-howard-how-to-execute-the-perfect-oss-drive-by-shooting/

Yann Cébron replied on Tue, 2009/11/03 - 1:41am

It baffles me why people are working around year-long known defencencies in their preferred IDE/plugins instead of evaluating working different solutions - but please without bringing even more tools to work to get the first one working in an acceptable manner. How about switching to another IDE that fully supports Maven? Or just use another build system (if possible)?

Hardy Ferentschik replied on Tue, 2009/11/03 - 4:31am

I agree that Maven has it shortcomings and I can see that you want to get away from it. Fair enough. But just keeping it for dependency management seems wrong. If you like Ant use Ant+Ivy and if you like Gradle you can relate on its dependency management. Gradle can "talk" to any maven repository.
That said, just arguing around IDE setup problems seems a weak argument against Maven. As some others suggest, there are a lot of parameters to play with. Also, have you considered to turn of auto-synchronization in your IDE. If you really want to argue against Maven you should for example mention the lack of scripting support.

Asif Sehzaad replied on Tue, 2009/11/03 - 6:09am in response to: Martijn Dashorst

mvn eclipse:m2eclipse is even better...

Adam Leggett replied on Tue, 2009/11/03 - 8:37am

We are almost out of the noughties and yet the maven v ant debate rolls on...C'mon guys, are'nt we all just trying to create some good quality byte code that can be interpreted or jit-ed?

Seriously though, the obvious point that people seem to miss is that the Maven eco-system (i'm including Hudson, M2Eclipse, Maven-SCM, Sonar, Nexus etc etc) is an Enterprise development platform for the JVM. As such it is in no way comparable to good ol' Ant and like any platform it will have things that you like/dislike (or hate even as it seems..). But it is something of an all or nothing proposition

To be fair, lets compare apples with apples. Its entirely valid to roll your own platform if you are not happy with the available choices.

Somewhat ironically we are just about to kick-off our sprint planning meeting for Mike which includes user story #371 - Run a Maven Build :-) .

David Lee replied on Tue, 2009/11/03 - 12:08pm

I hate maven.  Why do so many people assume HLS doesn't already know maven ?  I use it everday and feel much same as he does.  I want to use the good parts and scrap all of the non-sense. There are a few things to love about Maven and much to loathe about it. It's still a HUGE mystery how such a tool became so popular. 

Over the last 5 years, I've seen numerous teams was massive amounts of time with Maven 1.x and 2.  Just maybe, it's benefits don't outweigh it's complexity, shortcomings and just plain odd design.

 

 

 

 

 

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.