Quokka vs Maven - Side by Side

Introduction

This document provides a comparison between Quokka and Maven for building a simple web application. Throughout this document, Quokka configuration will be shown in this style, while Maven configuration will be in this style.

This document is divided into the following sections:

About the web app

The web application in this example will consist of a static page, a simple servlet and a JSP page. It has a single dependency on log4j, just to provide a sample dependency. The source code structure of the application is as follows:

src/
    main/
        java/
            sample/
                HelloWorld.java
                HelloWorldHelper.java
        webapp/
            index.html
            snoop.jsp
            WEB-INF/
                web.xml
    test/
        java/
            sample/
                HelloWorldHelperTest.java
                HelloWorldITest.java

Building the .war file

Given the following quokka configuration file (build-quokka.xml), building the .war file is done by quokka package:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE project PUBLIC "quokka.ws/dtd/project-0.2" "http://quokka.ws/dtd/project-0.2.dtd">
<project>
    <artifacts group="mycompany.myproject" version="0.1-ss">
        <artifact type="war"/>
    </artifacts>

    <dependency-set>
        <dependency group="junit" version="3.8.2" paths="test-compile"/>
        <dependency group="sun.servlet.spec.dist" version="2.5" paths="compile"/>
        <dependency group="apache.log4j" version="1.2.15" paths="compile, war"/>
        <dependency-set group="quokka.depset.jar" version="0.3-ss"/>
        <plugin group="quokka.plugin.jee" version="0.1-ss" targets="war, war-exploded"/>
    </dependency-set>
</project>

In comparision, here is the Maven configuration file (pom.xml), that builds a .war file using mvn package -Dmaven.test.failure.ignore=true:

<?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>mycompany</groupId>
  <artifactId>myproject</artifactId>
  <packaging>war</packaging>
  <version>0.1-SNAPSHOT</version>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.14</version>
    </dependency>
    <dependency>
      <groupId>org.apache.geronimo.specs</groupId>
      <artifactId>geronimo-servlet_2.5_spec</artifactId>
      <version>1.2</version>
      <optional>true</optional>
    </dependency>
  </dependencies>
</project>

Analysis:

Maven packaging versus Quokka dependency sets: Maven uses the concept of packaging to to introduce plugin dependencies automatically (via the <packaging>war</packaging> declaration). Unfortunately, this is basically voodoo as there is no easy way to determine what dependencies are introduced via this mechanism. Maven packaging binds certain goals to lifecycle phases (as shown here). These goals then force the dynamic loading of plugins from certain well-known locations. In the past, this took the latest available version making maven builds unreproducible, but I believe as of maven 2.0.9 it locks down the versions of certain plugins.

In contrast, Quokka uses a nested dependency-set (via the <dependency-set group="quokka.depset.jar" version="0.3-ss"/> declaration). The nested dependency set is in exactly the same format as the project dependency set, and is basically included, or merged into the project dependency set. The versions of any plugins are set explicitly in the nested set, so the build remains reproducible. Furthermore, it is easy to see what was included using the quokka help system. Dependency sets are a core concept of Quokka replacing Maven packaging and project inherientence with a more general and flexible mechanism. It should also be noted that there is no hard-wired concept of a lifecycle in Quokka. Instead there is a lifecycle plugin that defines abstract targets that other plugins are free to implement.

Maven has hard-wired paths: Maven has hard-wired scopes for dependencies including compile, provided, test, runtime, system and import. It then applies a complicated set of rules as to what these paths mean in different contexts (see here for details).

In constrast, Quokka has no hard-wired paths in the core at all. Both plugins and the project can define arbitrary paths. In the case above the lifecycle plugin defines the compile path and jee plugin defines the war path. When a dependency is declared it can be assigned to one or more paths, with each assignment able to set options as to whether transitive dependencies are included, if the dependency is optional and what optional dependencies should be included. Finally, plugins can group (or merge) paths together. e.g. to run unit tests the compile and test are merged (for exact details see the lifecycle plugin).

Unlike Maven, Quokka does not store all of these paths in the repository. Instead, the project defines which paths are to the exported to the repository (which also supports arbitrary paths).

Essentially, Quokka has generalised all of Maven's special rules into a single mechanism. Furthermore, the same mechanism is available to projects and plugins making Quokka much more extensible. Although this simple example doesn't show it, path handling is a big differentiator between Quokka and Maven. See Paths and Path Specifications for more information.

Testing voodoo: Maven automatically discovers that there are jUnit tests and runs them, presumably via maven's testing plugin automatically detecting jUnit on the classpath. Quokka does not try to guess what you want - you need to include the jUnit plugin (see the next section) if you want your tests to be run with jUnit

Conciseness: The quokka definition is much more concise, thanks largely due the use of XML attributes instead of elements.

Adding development reports

We'll now add a test report to both configurations. In Quokka, the following is added to the dependency set in build-quokka.xml:

<plugin group="quokka.plugin.devreport" version="0.3-ss"/>
<plugin group="quokka.plugin.junit" version="0.3-ss"/>
<plugin group="quokka.plugin.junitreport" version="0.3-ss"/>

The jUnit report can now be generated using quokka junitreport, or an index of all reports can be generated via quokka reports. (The devreport plugin is not required if you don't want an index).

For Maven, testing is done via the sure-fire plugin and is configured as follows in pom.xml. The report can be generated with mvn surefire-report:report or all reports can be generated using maven site

<reporting>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-report-plugin</artifactId>
            <version>2.4.3</version>
        </plugin>
    </plugins>
</reporting>

Analysis:

Surefire verus native test frameworks: Testing in maven is forced go through the sure-fire plugin which supports a fixed set of frameworks. This has a number of important consequences in maven. Firstly, if you are the author of a testing framework, you have to lobby to get the surefire plugin patched to support your framework. Secondly, the surefire plugin produces the same reports regardless of the test framework used. This means you have to lobby to get features added to the surefire report that may only be appropriate for your testing framework. At present, the surefire report appears to support the lowest common demoninator and is inferior to any of the native reports from testing frameworks.

In constrast, any framework can perform testing in quokka - all it requires is a supporting plugin that implements the abstract test target from the lifecycle plugin. Test frameworks are free to generate whatever reports they wish and be configured as is appropriate to the framework. The net result in this case is that the quokka junit test reports are far better than the Maven equivalents.

Maven muddles developer reports with site generation: To get a summary of all reports in Maven, you need to perform mvn site. This generates a web site for your project that as a side effect includes your developer reports. Web sites and developer reports have completely different audiences. In fact, in most cases your developer reports should not be available on your web site. Furthermore, to generate the summary, you have to suffer the overhead of generating site information that you are not interested in.

In constrast, in Quokka there is a plugin dedicated to producing a useful summary of developer reports, namely the devreport plugin. Site generation is completely separate and unrelated.

Maven gives reports a special status: In Maven, there is a dedicated reports section in the pom.xml. In Quokka, reports are just plugins - there is nothing special about them apart from them implementing devreport targets so that they appear in the reports index.

Plugin versions are optional in Maven: In the snippet above, the Maven plugin version has been set. However, this is optional in Maven and if not set it will find the latest available plugin by default. This is a big problem for Maven and is one of the reasons that Maven builds are not reproducible. i.e. If you forget to add the version and then check out your source a month later, it may not build as a different plugin version has been used.

In constrast, plugins (and all other dependencies) in Quokka must be explicitly versioned to avoid this problem. Quokka also provides tools such as quokka list-plugins to help you find compatible plugin versions

Adding Cobertura for test coverage reporting

Cobertura is an excellent open source test coverage framework. To enable it in Quokka, add the following to build-quokka.xml

<plugin group="quokka.plugin.cobertura" version="0.3-ss">

The Cobertura report can now be generated using quokka cobertura and as before it will be available via quokka reports.

For Maven, the cobertura plugin is added as a report and is configured as follows in pom.xml. The report can be generated with mvn cobertura:cobertura or all reports can be generated using maven site

<reporting>
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>cobertura-maven-plugin</artifactId>
            <version>2.2</version>
        </plugin>
    </plugins>
</reporting>

Analysis:

Maven forking runs tests twice: When generating the site for maven to view both the jUnit test results and the Cobertura reports, Maven runs all of the tests twice. This is due to Maven forking the build when it runs the Cobertura plugin. It does this so that it can use the instrumented classes instead of the normal classes.

In constrast, Quokka automatically detects when instrumentation is enabled and automatically adds the instrumented classes to the classpath during testing. This means the tests are only executed once.

Disabling Testing

To disable testing, Quokka relies on profiles. A profile can be used to modify the configuration of Quokka by enabling or disabling configuration elements. The following configuration shows how to disable testing using profiles in quokka:

<dependency-set>
    <profile id="skiptest" description="Skips tests, disabling all test-related targets"/>
     ...
    <plugin group="quokka.plugin.junit" version="0.3-ss" profiles="!skiptest"/>
    <plugin group="quokka.plugin.junitreport" version="0.3-ss" profiles="!skiptest"/>
    <plugin group="quokka.plugin.cobertura" version="0.3-ss" profiles="!skiptest"/>
</dependency-set>

Testing can then be disabled by setting the profiles property to skiptest. This is often done on the command line via quokka -Dprofiles=skiptest <target>

Maven supports profiles, but it is not possible to disable testing via it as the test targets are included via the war packaging. The simplest way to disable the tests in Maven is to set the maven.test.skip property to true. This is often done on the command line via mvn -Dmaven.test.skip=true <target>

Analysis:

Reduced overhead in Quokka: As the profiles eliminate all of the plugins at initialisation, there is no overhead associated with them. Maven too can achieve most of this via profiles, with the exception of the test goals themselves.

Adding integration testing via Jetty

The following describes adding Jetty support to the project so that integration tests can be run against a running web container. The goal is to start a Jetty server instance prior to integration testing starting, run the tests against it and then shut down the instance when the tests complete.

In quokka, the following configuration achieves what we want:

<property name="q.jetty.defaults.stopKey" value="foo"/>
<property name="q.jetty.defaults.stopPort" value="9999"/>
<plugin group="quokka.plugin.jetty" version="0.1-ss" paths="(jsp-2.1)">
    <target name="start-jetty" template="run-template" profiles="!skiptest"
            dependency-of="lifecycle:initialise-integration-test"
                  depends="lifecycle:pre-initialise-integration-test">
        <property name="daemon" value="true"/>
        <property name="scanIntervalSeconds" value="0"/>
        <property name="includeInstrumentedOuput" value="true"/>
    </target>
    <target name="stop-jetty" template="stop-template" profiles="!skiptest"
        dependency-of="lifecycle:finalise-integration-test"
              depends="lifecycle:pre-finalise-integration-test"/>
</plugin>

There are several important things to note with the quokka configuration:

  • It uses template targets to start and stop Jetty. Templates allow instances of themselves to be added to the project as separate targets with their own properties and names.
  • It demonstrates how targets can be inserted into the lifecycle using the dependency-of and depends attributes of target. This is how the Jetty server is started and stopped before and after the integration tests.
  • The plugin declaration has a paths entry that adds the optional jsp-2.1 dependency to the Jetty plugin's runtime path, effectively adding support for JSP 2.1. This is an example of a Path Specification which gives great control over what dependencies your project includes.

With the above configuration, Jetty will now be automatically started prior to integration testing. By default, the jUnit plugin treats anything that ends in ITest.class as an integration test. So now, the previously failing HelloWorldITest will now succeed. As an added bonus, the includeInstrumentedOuput property means that Cobertura classes are added to the Jetty container classpath along with the instrumented classes, meaning that code coverage data will be collected for the integration tests running inside the Jetty server instance.

For Maven, the following configuration needs to be added to the pom.xml:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <configuration>
                <excludes>
                    <exclude>**/*ITest.java</exclude>
                </excludes>
            </configuration>
            <executions>
                <execution>
                    <id>surefire-it</id>
                    <phase>integration-test</phase>
                    <goals>
                        <goal>test</goal>
                    </goals>
                    <configuration>
                        <excludes>
                            <exclude>none</exclude>
                        </excludes>
                        <includes>
                            <include>**/*ITest.java</include>
                        </includes>
                    </configuration>
                </execution>
            </executions>
        </plugin>
        <plugin>
            <groupId>org.mortbay.jetty</groupId>
            <artifactId>maven-jetty-plugin</artifactId>
            <version>6.1.11</version>
            <configuration>
                <scanIntervalSeconds>10</scanIntervalSeconds>
                <stopKey>foo</stopKey>
                <stopPort>9999</stopPort>
            </configuration>
            <executions>
                <execution>
                    <id>start-jetty</id>
                    <phase>pre-integration-test</phase>
                    <goals>
                        <goal>run</goal>
                    </goals>
                    <configuration>
                        <scanIntervalSeconds>0</scanIntervalSeconds>
                        <daemon>true</daemon>
                    </configuration>
                </execution>
                <execution>
                    <id>stop-jetty</id>
                    <phase>post-integration-test</phase>
                    <goals>
                        <goal>stop</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

Note that the surefire plugin configuration is required to make the test phase exclude integration tests (those ending with ITest.java) and run them in the integration-test phase.

Analysis:

Maven configuration violates the schema: Maven target configuration allows arbitrary elements for configuration of targets. However, this violates the schema making tooling support irritating. It does however allow complex objects to be configured.

In constrast, Quokka target configuration is via property elements and therefore the quokka configuration file can be validated against a DTD. It also allows configurations to be tweaked from other locations, such as command line properties, user properties and project level properties. While properties are awkward for representing complex types, Quokka supports build resources that allow arbitrary XML files to be bundled with a dependency set. This is preferred mechanism for complex types in Quokka.

Maven configuration limitations: The Maven configuration has several issues. Firstly, only the test phase tests are included in the surefire report. It may be possible to configure the surefire-report plugin to fix this, but I can't find any documentation. Secondly, code coverage data is not collected for the integration tests.

Getting Help

Both Quokka and Maven plugins come with many configuration options. Furthermore, it can become difficult to track project paths due to transitive dependencies (dependencies of dependencies). It is therefore important to be able to get some help to work out what is going on.

Quokka provides the following help mechanisms:

  • A detailed Quokka reference, including Getting Started and User Guides, available in HTML form on the web site and in PDF format with the distribution.
  • Built-in -projecthelp command line option that lists targets available in the current project.
  • Built-in list-plugins target that list plugins available in the repository, including the versions that are compatible.
  • The help plugin. Once included in a project, you can now use quokka help to generate a detailed HTML report that includes information on all project paths, plugin paths, dependency sets, targets and properties. Help is also bundled inside plugins. This allows you to get help on targets or plugins, using the target and id properties. e.g. quokka help -Dtarget=jetty:run will bring up HTML help for the version of the Jetty plugin you are using.

Maven provides the following help mechanisms:

  • The maven website has various guides
  • I believe there's a free book available
  • A help plugin that shows how a project looks after profiles and settings are applied
  • A dependency plugin, that provides the dependency:tree goal for listing project dependencies.

Analysis:

Maven plugins don't bundle help: The biggest issue with Maven is that help is not bundled with plugins and that the web site only shows help for the latest version of the plugin. Therefore, it is impossible to get help for a plugin version that isn't the latest.

In constrast, Quokka bundles the help with plugin, ensuring it is always avaiable.

Extensibility

Although not required for this particular project, it is common for projects to require ad hoc targets for which no plugins exist. Quokka offers several options. Firstly, you can place a standard Ant build.xml file along side the build-quokka.xml file and it will be imported automatically. You are then free to use any Ant tasks you like. Some additional tasks from AntContrib have also been bundled, such as if, var, and for to provide some limited imperative support.

If standard Ant targets are too limited or tedious, you can also use the script plugin. The script plugin allows you to write code in any scripting language support by Apache BSF, or JSR 223. The following is an example of using Javascript via script plugin:

<dependency group="apache.ant" name="commons-net" version="1.7.1" paths="ant-types"/>
<plugin group="quokka.plugin.fop" version="?" templates="transform"/>

<plugin group="quokka.plugin.script" version="?">
    <target name="myscript" template="javascript">
        <property name="script"><![CDATA[
          // Example of built-in tasks (echo)
          for (i=1; i<=10; i++) {
            echo = project.createTask("echo");
            echo.setMessage(i*i);
            echo.perform();
          }

          // Example of optional Ant task (ftp) - dependencies are added to ant-types
          ftp = project.createTask("ftp")
          ftp.setUserid("ftp.crap.com")

          // Example of using a a quokka plugin (fop) - the plugin and template must be declared
          props = new java.util.Properties()
          props.put("in","src/fop/fo.xml")
          props.put("out","${q.project.targetDir}/fop/fo.pdf")
          quokka.execute("quokka.plugin.fop", "transform", props)
        ]]></property>
    </target>
</plugin>

Maven's extensiblity appears to be limited to the antrun plugin. It allows arbitrary Ant tasks to be executed during any lifecycle phase.

Analysis:

Maven extensions are tied to lifecycle phases: From my understanding, Maven (via the antrun plugin) only allows tasks to piggy back on lifecycle phases. In constrast, Quokka allows arbitrary targets to be added. The may be stand-alone or participate in the lifecycle.

Quokka provides scripting support: As far as I'm aware there is no equivalent of the scripting capabilities of Quokka in Maven.

Quokka supports arbitrary paths: The ability to add arbitrary project paths coupled with the ability to add arbitrary targets is a powerful combination that lets an ad hoc target have similar functionality to a proper Quokka plugin.

Maven has no native concept of tasks: Maven isn't internally constructed of tasks that can be reused from ad hoc targets. This means it has to look elsewhere for a solution and ends up using Ant. It's a little ironic that having rewritten a lot of Ant tasks internally, it then relies on Ant for extensibility.

In constrast, Quokka is an extension to Ant and it's plugins are largely built on Ant tasks. It is there a natural mechanism for extensibility.

Dependency Analysis

Quokka has a very strict policy regarding dependencies. Specifically, the core has no dependencies other than the Ant core. Furthermore, plugins never use any dependencies that are not required by the task itself. e.g. the Cobertura plugin will naturally have dependencies on Cobertura itself, but would never use Apache commons classes to ease development of the plugin itself.

The list below includes all dependencies required to build and run this project, including project and plugin dependencies. With this set the project can run offline:

apache.ant 1.7.1 junit jar
apache.ant 1.7.1 trax jar
apache.log4j 1.2.15 log4j jar
apache.oro 2.0.8 oro jar
eclipse.jdt.core 3.1.1 core jar
junit 3.8.2 junit jar
mortbay.jetty 6.1.11 jetty jar
mortbay.jetty 6.1.11 jsp-2.1 jar
mortbay.jetty 6.1.11 util jar
ow2.asm 2.2.1 asm jar
quokka.depset 0.3-ss jar depset
quokka.plugin.cobertura 0.3-ss cobertura plugin
quokka.plugin.devreport 0.3-ss devreport plugin
quokka.plugin.help 0.3-ss help plugin
quokka.plugin 0.3-ss jar plugin
quokka.plugin.javac 0.3-ss javac plugin
quokka.plugin.jee 0.1-ss jee plugin
quokka.plugin.jetty 0.1-ss jetty plugin
quokka.plugin.junit 0.3-ss junit plugin
quokka.plugin.junitreport 0.3-ss junitreport plugin
quokka.plugin.lifecycle 0.3-ss lifecycle plugin
quokka.plugin.resources 0.3-ss resources plugin
quokka.plugin.standard-lifecycle 0.3-ss standard-lifecycle plugin
sf.cobertura 1.8 cobertura jar
sun.jsp.spec.dist 2.1 dist jar
sun.servlet.spec.dist 2.5 dist jar

This is pretty good, it basically contains the plugins we are using plus the project dependencies. However, Quokka has another trick available to cut down dependencies even further, namely Bundled Repositories. Bundled repositories basically a zipped up repository. Quokka publishes one at present named quokka.bundle.extensions that contains all of the quokka extensions (which includes plugins, dependency sets and archetypes). It's primary design is to allow a project to keep all of it's dependencies in version control along side the project, without clogging it up with quokka related jars. e.g. The project can be configured to use a bundled repository by adding the following properties to the project dependency set:

<property name="q.repo.project.url" value="delegate:core,bundle,local,shared"/>
<property name="q.repo.bundle.url" value="bundle:quokka.bundle.extensions:2008.1;repository=shared"/>
<property name="q.repo.local.url" value="file:${basedir}/lib;hierarchical=false;parents=shared"/>

Note: it is best to copy the bundled repository to the lib directory and set the bundle repository's repository property to local after running with this configuration the first time to ensure the bundle itself is local.

Now when anlaysing the complete dependencies of our project we have:

apache.ant 1.7.1 junit
apache.ant 1.7.1 trax
apache.log4j 1.2.15 log4j
apache.oro 2.0.8 oro
eclipse.jdt.core 3.1.1 core
junit 3.8.2 junit
mortbay.jetty 6.1.11 jetty
mortbay.jetty 6.1.11 jsp-2.1
mortbay.jetty 6.1.11 util
ow2.asm 2.2.1 asm
bundle:quokka.bundle.extensions 2008.1 extensions
sf.cobertura 1.8 cobertura
sun.jsp.spec.dist 2.1 dist
sun.servlet.spec.dist 2.5 dist

This is essentially the miminum dependencies you can possibly have regardless of the build system you use (well, maybe you could remove the Ant dependencies and of course the quokka bundle). Also, it is trivial to work out where a particular dependency is used - just use quokka help and search the plugin paths for the dependency you are interested in.

Maven on the other hand, seems to have no policy on dependencies and dependencies are included freely. Here is the list of dependency Maven requires (this was obtained by clearing the local Maven repository and executing mvn clean install; mvn site

ant ant 1.6.5
asm asm 2.2.1
asm asm-tree 2.2.1
ch ethz ganymed ganymed-ssh2 build210
com thoughtworks xstream xstream 1.2.2
commons-beanutils commons-beanutils 1.7.0
commons-collections commons-collections 3.1
commons-collections commons-collections 3.2
commons-digester commons-digester 1.6
commons-io commons-io 1.4
commons-lang commons-lang 2.1
commons-logging commons-logging 1.0.4
commons-validator commons-validator 1.1.4
commons-validator commons-validator 1.2.0
doxia doxia-core 1.0-alpha-4
geronimo-spec geronimo-spec-jta 1.0.1B-rc4
jakarta-regexp jakarta-regexp 1.4
javax mail mail 1.4
junit junit 3.8.1
log4j log4j 1.2.14
log4j log4j 1.2.9
nekohtml nekohtml 1.9.6.2
nekohtml xercesMinimal 1.9.6.2
net sourceforge cobertura cobertura 1.9
org apache ant ant 1.7.0
org apache ant ant-launcher 1.7.0
org apache bcel bcel 5.2
org apache geronimo specs geronimo-annotation_1.0_spec 1.0
org apache geronimo specs geronimo-servlet_2.5_spec 1.2
org apache maven doxia doxia-core 1.0-alpha-10
org apache maven doxia doxia-core 1.0-alpha-11
org apache maven doxia doxia-decoration-model 1.0-alpha-10
org apache maven doxia doxia-decoration-model 1.0-alpha-11
org apache maven doxia doxia-module-apt 1.0-alpha-10
org apache maven doxia doxia-module-apt 1.0-alpha-11
org apache maven doxia doxia-module-fml 1.0-alpha-10
org apache maven doxia doxia-module-fml 1.0-alpha-11
org apache maven doxia doxia-module-xdoc 1.0-alpha-10
org apache maven doxia doxia-module-xdoc 1.0-alpha-11
org apache maven doxia doxia-module-xhtml 1.0-alpha-10
org apache maven doxia doxia-module-xhtml 1.0-alpha-11
org apache maven doxia doxia-site-renderer 1.0-alpha-10
org apache maven doxia doxia-site-renderer 1.0-alpha-11
org apache maven maven-archiver 2.2
org apache maven maven-plugin-tools-api 2.0
org apache maven plugins maven-clean-plugin 2.2
org apache maven plugins maven-compiler-plugin 2.0.2
org apache maven plugins maven-install-plugin 2.2
org apache maven plugins maven-project-info-reports-plugin 2.1
org apache maven plugins maven-resources-plugin 2.2
org apache maven plugins maven-site-plugin 2.0-beta-6
org apache maven plugins maven-surefire-plugin 2.4.2
org apache maven plugins maven-surefire-report-plugin 2.4.3
org apache maven plugins maven-war-plugin 2.1-alpha-1
org apache maven reporting maven-reporting-impl 2.0
org apache maven reporting maven-reporting-impl 2.0.4.1
org apache maven scm maven-scm-api 1.0
org apache maven scm maven-scm-manager-plexus 1.0
org apache maven scm maven-scm-provider-cvs-commons 1.0
org apache maven scm maven-scm-provider-cvsexe 1.0
org apache maven scm maven-scm-provider-cvsjava 1.0
org apache maven scm maven-scm-provider-perforce 1.0
org apache maven scm maven-scm-provider-starteam 1.0
org apache maven scm maven-scm-provider-svn-commons 1.0
org apache maven scm maven-scm-provider-svnexe 1.0
org apache maven shared file-management 1.2
org apache maven shared maven-dependency-tree 1.1
org apache maven shared maven-doxia-tools 1.0.1
org apache maven shared maven-shared-io 1.1
org apache maven shared maven-shared-jar 1.0
org apache maven skins maven-default-skin 1.0
org apache maven surefire surefire-api 2.4.2
org apache maven surefire surefire-booter 2.4.2
org apache maven surefire surefire-junit 2.4.2
org apache maven wagon wagon-file 1.0-beta-3
org apache maven wagon wagon-http-lightweight 1.0-beta-3
org apache maven wagon wagon-http-shared 1.0-beta-3
org apache maven wagon wagon-ssh 1.0-beta-3
org apache maven wagon wagon-ssh-common 1.0-beta-3
org apache velocity velocity 1.5
org codehaus mojo cobertura-maven-plugin 2.2
org codehaus plexus plexus-archiver 1.0-alpha-7
org codehaus plexus plexus-compiler-api 1.5.3
org codehaus plexus plexus-compiler-javac 1.5.3
org codehaus plexus plexus-compiler-manager 1.5.3
org codehaus plexus plexus-digest 1.0
org codehaus plexus plexus-i18n 1.0-beta-7
org codehaus plexus plexus-utils 1.0.4
org codehaus plexus plexus-utils 1.1
org codehaus plexus plexus-utils 1.4.5
org codehaus plexus plexus-utils 1.4.7
org codehaus plexus plexus-utils 1.4.9
org codehaus plexus plexus-utils 1.5.5
org codehaus plexus plexus-velocity 1.1.7
org eclipse jdt core 3.1.1
org mortbay jetty jetty 6.1.11
org mortbay jetty jetty 6.1.5
org mortbay jetty jetty-annotations 6.1.11
org mortbay jetty jetty-management 6.1.11
org mortbay jetty jetty-naming 6.1.11
org mortbay jetty jetty-plus 6.1.11
org mortbay jetty jetty-util 6.1.11
org mortbay jetty jetty-util 6.1.5
org mortbay jetty jsp-2.1 6.1.11
org mortbay jetty jsp-api-2.1 6.1.11
org mortbay jetty maven-jetty-plugin 6.1.11
org mortbay jetty servlet-api-2.5 6.1.11
org mortbay jetty servlet-api-2.5 6.1.5
org mortbay jetty start 6.1.11
org netbeans lib cvsclient 20060125
oro oro 2.0.7
oro oro 2.0.8
regexp regexp 1.3
urbanophile java-getopt 1.0.9
xml-apis xml-apis 1.0.b2
xpp3 xpp3_min 1.1.3.4.O xpp3_min-1.1.3.4.O.jar

Note: This list is being quite generous to Maven as it only includes .jar file dependencies. In reality, Maven requires a lot of .pom dependencies as well to build.

Analysis:

Maven dependencies are excessive: To build the simple web application, Maven requires 116 .jar file dependencies (and many more .pom dependencies). Even worse, the bulk of the dependencies are not related to the project, they are there to support Maven developers.

In constrast, Quokka offers the same functionaly with a total of 26 or 14 dependencies (dependinging on whether you use bundled repositories or not).

Maven doesn't make use of optional dependencies: If you compare the Jetty dependencies between the Quokka and Maven list, you will see that the Maven version includes many more Jetty dependencies such as annotations, naming, management and plus. Quokka supports the notion of including optional dependencies. We have seen an example of this where the Jetty plugin included the jsp-2.1 dependency. This allows Quokka to exclude optional things by default, but allow them to be easily included by the user. Maven doesn't support this model and therefore included additional dependencies that the user has to exclude (if it is possible at all for plugin dependencies).

Maven's automatic conflict resolution introduces more dependencies: Maven traverses all dependencies in path and then applies conflict resolution rules on the resulting set. This means that even though a dependency won't end up being used, it is still required to build the project.

In contrast, quokka uses overrides to resolve conflicts. Overrides apply as a path is being traversed, so dependencies that are overridden are never traversed.

Maven exports it's project model to the repository: Although not listed above, there are many more dependencies required for the Maven build to work, namely Project Object Model (.pom) files.

In constrast, Quokka's repository layout is independent of the project model - projects export their artifacts and paths to the repository. So even if your project used a dozen nested dependency sets internally, that complexity is not exported to the repository.

Performance

Virtually no optimisation of Quokka has been done at present. However, in it's current form it appears to significantly outperform maven for this project. Each of the following commands was run 3 times with time on an idle Core 2 Duo 2.4GHz processor under OS X 10.5.4, JDK 1.6.0_07 and the 3rd result was taken (from real). It compares Quokka 0.3 against Maven 2.0.9

Packaging, skipping tests
quokka clean package -Dprofiles=skiptest 2.169s
mvn -o clean package -Dmaven.test.skip=true 4.479s
Quokka is 2.07x faster
Installing, skipping tests
quokka clean install -Dprofiles=skiptest 2.170s
mvn -o clean install -Dmaven.test.skip=true 8.338s *1
Quokka is 3.84x faster
Installing, including tests
quokka clean install 7.162s
mvn -o clean install -Dmaven.test.failure.ignore=true 9.144s
Quokka is 1.28x faster
Generating developer reports
quokka clean reports 9.539s
mvn -o clean site -Dmaven.test.failure.ignore=true 14.790s
Quokka is 1.55x faster

*1: This is unfair to maven as it includes the time to start and stop the Jetty server. This could be fixed by setting up a profile in Maven and disabling it for the test run. It is however extra configuration that most users would not perform.

Conclusion

Quokka and Maven are very similar in their functionality. Both are largely declarative build frameworks focusing on Java. Quokka has some clear advantages, in particular the number of dependencies it requires to operate, the fact that dependencies are always set to a specific version to make Quokka builds reproducible, extensibility and performance. There are numerous other advantages that this example doesn't cover, such as flexible multi-project builds, aggregated development reports, bootstrapping and support for multiple artifacts. See Quokka vs Maven for more information.

Appendix

Following is the complete Quokka build-quokka.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE project PUBLIC "quokka.ws/dtd/project-0.2" "http://quokka.ws/dtd/project-0.2.dtd">
<project default-target="package">
    <artifacts group="mycompany.myproject" version="0.1-ss">
        <artifact type="war"/>
    </artifacts>

    <dependency-set>
        <profile id="skiptest" description="Skips tests, disabling all test-related targets"/>

        <dependency group="junit" version="3.8.2" paths="test-compile"/>
        <dependency group="sun.servlet.spec.dist" version="2.5" paths="compile"/>
        <dependency group="apache.log4j" version="1.2.15" paths="compile, war"/>

        <dependency-set group="quokka.depset.jar" version="0.3-ss"/>

        <plugin group="quokka.plugin.junit" version="0.3-ss" profiles="!skiptest"/>
        <plugin group="quokka.plugin.jee" version="0.1-ss" targets="war, war-exploded"/>

        <plugin group="quokka.plugin.devreport" version="0.3-ss"/>
        <plugin group="quokka.plugin.junitreport" version="0.3-ss" profiles="!skiptest"/>
        <plugin group="quokka.plugin.cobertura" version="0.3-ss" profiles="!skiptest"/>
        <plugin group="quokka.plugin.help" version="0.3-ss"/>

        <property name="q.jetty.defaults.stopKey" value="foo"/>
        <property name="q.jetty.defaults.stopPort" value="9999"/>
        <plugin group="quokka.plugin.jetty" version="0.1-ss" paths="(jsp-2.1)">  <!- Adds JSP 2.1 support ->
            <target name="start-jetty" template="run-template" profiles="!skiptest"
                    dependency-of="lifecycle:initialise-integration-test"
                          depends="lifecycle:pre-initialise-integration-test">
                <property name="daemon" value="true"/>
                <property name="scanIntervalSeconds" value="0"/>
                <property name="includeInstrumentedOuput" value="true"/>
            </target>
            <target name="stop-jetty" template="stop-template" profiles="!skiptest"
                dependency-of="lifecycle:finalise-integration-test"
                      depends="lifecycle:pre-finalise-integration-test"/>
        </plugin>
    </dependency-set>
</project>

Following is the complete Maven pom.xml:

<?xml version="1.0"?>
<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>mycompany</groupId>
<artifactId>myproject</artifactId>
<packaging>war</packaging>
<version>0.1-SNAPSHOT</version>
<name>myproject Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>3.8.1</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.14</version>
    </dependency>
    <dependency>
        <groupId>org.apache.geronimo.specs</groupId>
        <artifactId>geronimo-servlet_2.5_spec</artifactId>
        <version>1.2</version>
        <optional>true</optional>
    </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <configuration>
                <excludes>
                    <exclude>**/*ITest.java</exclude>
                </excludes>
            </configuration>
            <executions>
                <execution>
                    <id>surefire-it</id>
                    <phase>integration-test</phase>
                    <goals>
                        <goal>test</goal>
                    </goals>
                    <configuration>
                        <excludes>
                            <exclude>none</exclude>
                        </excludes>
                        <includes>
                            <include>**/*ITest.java</include>
                        </includes>
                    </configuration>
                </execution>
            </executions>
        </plugin>
        <plugin>
            <groupId>org.mortbay.jetty</groupId>
            <artifactId>maven-jetty-plugin</artifactId>
            <version>6.1.11</version>
            <configuration>
                <scanIntervalSeconds>10</scanIntervalSeconds>
                <stopKey>foo</stopKey>
                <stopPort>9999</stopPort>
            </configuration>
            <executions>
                <execution>
                    <id>start-jetty</id>
                    <phase>pre-integration-test</phase>
                    <goals>
                        <goal>run</goal>
                    </goals>
                    <configuration>
                        <scanIntervalSeconds>0</scanIntervalSeconds>
                        <daemon>true</daemon>
                    </configuration>
                </execution>
                <execution>
                    <id>stop-jetty</id>
                    <phase>post-integration-test</phase>
                    <goals>
                        <goal>stop</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>
<reporting>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-report-plugin</artifactId>
            <version>2.4.3</version>
        </plugin>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>cobertura-maven-plugin</artifactId>
            <version>2.2</version>
        </plugin>
    </plugins>
</reporting>
</project>