Build Script Length - Maven 3, Polyglot Maven, Gradle and Ant

A short comparison of length of the build files of Maven 3, Polyglot Maven (Groovy version), Ant and Gradle. Please notice that this is NOT a full comparison of these build frameworks!

[UPDATED 2010-11-18 - I implemented changes requested by some commentators, i.e. made maven pom.xml shorter by using properties instead of full maven-compiler-plugin configuration].

The source code is available at github. Enjoy !

Let us see what is required by each of popular build tools - Ant, Maven and Gradle - to build a very simple project. The project consists of few Java 1.6 classes that require only one dependency for compilation (commons-lang 2.5) and one for tests (junit 4.8.2). Each build ends up with generation of a valid coc-comparison-1.0-SNAPSHOT.jar file.

If you notice possibility to make some of build files shorter (but still readable and without any nasty hacks), please let me know.
I tried to make build files as concise as it is possible but at the same time readable and written in a spirit of this particular tool. For example, build.xml of Ant could be few lines shorter, but I follow the unwritten convention of clean-init-compile tasks.

This blog post is written rather to show how build files evolved over time than to compare build tools. Please bear this in mind when commenting.

Ant


Old, good Ant. No Convention over Configuration (CoC) at all. You need to specify everything by hand. This results in very bloated build.xml files. On the other hand, reading of build.xml is enough to understand what the build does, where the sources are etc. Nothing hidden, nothing happening "by magic".

The lack of built-in CoC resulted in several community-standards, like keeping all libraries in lib subdirectory, having clean and init tasks etc. This helped to make Ant builds more "standard" and easier to digest. However these are not rules but rather community recommendations and they are not supported, checked or forced by Ant in any way.

Build file (build.xml):

<?xml version="1.0"?>
<project name="simple" default="dist" basedir=".">
  <property name="src" location="src/main/java"/>
  <property name="srcTest" location="src/test/java"/>
  <property name="build" location="build"/>
  <property name="dist" location="${build}/lib"/>
  <property name="version" value="1.0-SNAPSHOT" />
  <path id="classpath.compile">
    <pathelement location="libs/commons-lang-2.5.jar"/>
  </path>
  <path id="classpath.test">
    <pathelement location="libs/junit-4.8.2.jar"/>
    <pathelement location="libs/commons-lang-2.5.jar"/>
    <pathelement location="${srcTest}"/>
    <pathelement location="${build}/classes"/>
    <pathelement location="${build}/test-classes"/>
  </path>
  <target name="init">
    <mkdir dir="${build}/classes"/>
    <mkdir dir="${build}/test-classes"/>
  </target>
  <target name="compile" depends="init">
    <javac srcdir="${src}" destdir="${build}/classes">
      <classpath refid="classpath.compile"/>
    </javac>
  </target>
  <target name="testCompile" depends="compile">
    <javac srcdir="${srcTest}" destdir="${build}/test-classes">
      <classpath refid="classpath.test"/>
    </javac>
  </target>
  <target name="test" depends="testCompile">
    <junit fork="yes" haltonfailure="yes">
      <batchtest fork="yes">
        <fileset dir="${srcTest}">
          <include name="**/*Test.java"/>
        </fileset>
      </batchtest>
      <classpath refid="classpath.test"/>
      <formatter type="plain"/>
    </junit>
  </target>
  <target name="dist" depends="test">
    <mkdir dir="${dist}"/>
    <jar jarfile="${dist}/coc-comparison-${version}.jar" basedir="${build}/classes"/>
  </target>
  <target name="clean">
    <delete dir="${build}"/>
  </target>
</project>

Maven 2

This is how CoC in Java build tools started! Maven uses default (conventional) values for many aspects of your build. It assumes that sources are in src/main/java, test resources in src/test/resources, web files in src/main/webapp and so on. This makes build files (named pom.xml) to be much smaller than Ant counterparts. The other benefit is that any new developer knows what to expect when she/he joins any team that uses Maven.

No wonder that Maven was so successful and some of its defaults (like putting all Java sources into src/main/java) were accepted by Java community (and other build tools as well).

Still many complains about the XML format of build files, which makes them much longer than they could be. Also the need to specify that Java 1.5 is required seems rather outdated to me.

Build file (pom.xml):

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>grId</groupId>
  <artifactId>coc-comparison</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <dependencies>
    <dependency>
      <groupId>commons-lang</groupId>
      <artifactId>commons-lang</artifactId>
      <version>2.5</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <properties>
          <maven.compiler.target>1.6</maven.compiler.target>
          <maven.compiler.source>1.6</maven.compiler.source>
  </properties>
</project>

Maven 3


Pom.xml file of Maven 3 is similar to pom.xml of Maven 2, but Java 1.5 is used by default so 4 properties lines can be ommitted.

Build file (pom.xml):

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>grId</groupId>
  <artifactId>coc-comparison</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <dependencies>
    <dependency>
      <groupId>commons-lang</groupId>
      <artifactId>commons-lang</artifactId>
      <version>2.5</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

Polyglot Maven

I thought that writing POMs using script languages will be a major change in new Maven 3, but I was wrong. Now I know, that this feature is still not in Maven core but you have to install a fork called Maven Polyglot.

As you can see it is a very nice feature that reduces POMs size significantly. Now you can see that a substantial part of Maven pom.xml files was never really needed. Definitely this is a step in the right direction and I hope this feature will be merged into Maven core.

Still when comparing to Gradle (see below) one can observe that the legacy of old XML format is still present in this new approach. Also being Maven compatible means that Java 1.4 is used by default (and has to be changed by configuration of maven-compiler-plugin).

Build file (pom.groovy)

project {
  modelVersion '4.0.0'
  artifactId 'coc-comparison'
  groupId 'grId'
  version '1.0-SNAPSHOT'

  dependencies {
    dependency('commons-lang:commons-lang:2.5')
    dependency('junit:junit:4.8.2')
  }

  properties {
    'maven.compiler.target' '1.6'
    'maven.compiler.source' '1.6'
  }
}

Gradle


Gradle does it right. Build files - named by default build.gradle - contain only information that are really necessary. No cryptic tags, no bloated XML structures, "no fluff just stuff". :)

Build file (build.gradle):

apply plugin: 'java'

version="1.0-SNAPSHOT"
group="grId"
archivesBaseName="coc-comparison"

repositories {
    mavenCentral()
}

dependencies {
  compile 'commons-lang:commons-lang:2.5'
  testCompile 'junit:junit:4.8.1'
}

Conclusion

First a little summary of file lengths (as returned by wc tool):

tool language lines characters
ant 1.8 XML 50 1733
maven 2.0 XML 28 904
maven 3.0 XML 24 765
polyglot maven (groovy) 0.8-SNAPSHOT groovy 16 303
gradle 0.9-rc-2 groovy 15 224

Nothing surprising here but still some interesting observations regarding build tools can be made.

  1. XML is a evil. No one likes it anymore and new tools avoid it. . Poor Maven needs to be still compatible with its legacy XML format which does not allow him to grow and compete with newer frameworks. Polyglot Maven seems like a move in the right direction.
  2. DSLa are taking over. Gradle beats the competition in this aspect but Polyglot Maven achieves very similar result. XML-based tools have no chances.
  3. Convention over Configuration gives a huge gain in terms of build script conciseness. All tools except grandpa Ant take use of CoC.
  4. Being backward compatible is painful. Polyglot Maven suffers from its 100% compatibility with Maven which does not allow its build files to use the full potential of script languages.
  5. Haven't included buildr script in my comparison, but I guess it would be similar to Gradle. Another example of good DSL.

One interesting question is if you can get any better than Gradle in terms of conciseness? I remember seeing somewhere (probably on Gradle mailing list) some ideas regarding automatic recognition of dependencies which would made build files even shorter. Sounds nice, but this idea is rather hard to be implemented, isn't it?

Please be sensible when drawing conclusions from this simple comparison. The fact that Maven's pom.xml is several times bigger than Gradle's build.gradle doesn't mean that Gradle is few times better! (Well, in fact it is, but for many other reasons). For example when running the Maven version one can easily generate a project site which is not possible when running presented Gradle build.

Presentation

There is also a short presentation available if you are interested:

Links

Arbitrary code in Polyglot Maven

Tomek, awesome comparison. Specially the presentation.
Tell me, do you have any idea when arbitrary code that I write in pom.groovy (for example) executes?

Thanks!
Baruch

my knowledge of polyglot maven is very limited

sorry, can't help you, my knowledge of polyglot maven is very limited

--
Cheers,
Tomek

A more "fair" comparison

With maven you can use properties instead of "<build>...</build>" :

<properties>
<maven.compiler.target>1.6</maven.compiler.target>
<maven.compiler.source>1.6</maven.compiler.source>
</properties>

Or with polyglot :

properties {
'maven.compiler.target' '1.6'
'maven.compiler.source' '1.6'
}

And now, polyglot isn't that far from graddle. And remember that graddle compiles with java 1.5, so to be really fair, you should set "sourceCompatibility" and "targetCompatibility" to 1.6

changes introduced

Hello,

I introduced the changes you requested.

It occured that maven 3 uses java 1.6 by default so no need for properties so I splitted "maven" into "maven 2" and "maven 3" and treat them separately. Seems like gradle also uses 1.6 by default.

--
cheers,
Tomek

interesting, thanks

> With maven you can use properties instead of "..." :
I was not aware of this. Thanks.

Regarding 1.5 and 1.6. Right, it would be more fair if I used 1.6. I took 1.5 without much thinking about it - I simply assumed that 1.5 is the most widely used right now.

I will update the code and the post, but unfortunately my laptop is broken and will have to wait till its fixed before I can do this.

Thank you for you comment.

--
Tomek Kaczanowski

Not updated

Your code still isn't updated to reflect the use of 1.6. :-)

true, totally forgot about this one :(

oh my, this never made it to my "ToDo" list and I forgot to fix it

Optimization

Please note that, at least for the XML format but I suppose this is the case of all Maven Polyglot formats, the groupId org.apache.maven.plugins is optional as it is the default assumed by Maven

usually you add groupId, don't you?

You are right, groupId is not required, but at the same time you usually specify it explicitly, don't you? BTW. it could be also omitted for Gradle but I think it is a bad idea to omit this, so I will leave it the way it is.

Thanks for comment.

--
Tomek Kaczanowski

PolyglotMaven Atom Grammar

The POM in the Atom format created by Dhanji Prasanna @ Google would be as follows:

repositories << "http://maven.org/central"

project "Google Guice" @ "http://code.google.com/p/google-guice"
id: com.google.inject:guice:2.0-SNAPSHOT
deps: [ commons-lang:commons-lang:2.5 junit:junit:4.0:test ]

PolyglotMaven 1.0 will be released on December 1st, and will include support for the Atom format.

Interesting

The comparison is interesting, but a bit unjust. Well, you warned...
The article shows the interest of CoC, that's why Ant looses by a large margin.
But it could have been interesting also to compare the tools on a custom setting: having the library files on a local, distinct place (not automatically downloaded), having a custom directory structure. It would have been fairer to Ant and at the same time, shown the capabilities of the tools to handle these cases.

CoC is perfect for new projects: as you wrote, the conventions are no-nonsense, and diverging from them would require good reasons...
But sometime you have to write build scripts for existing projects, and in some shops (like mine), they don't see lightly major refactorings... So you have to adapt to the existing layout and usages.
I wonder if Maven is flexible with regard to this constraint?

I explored a bit the capabilities of Gradle with regard to this flexibility: Building and running Scala programs with Gradle
I like it, and I suppose I will learn to omit more stuff, relying more on defaults. But I appreciate we can override these defaults if we want/need!

Please comment using