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
- Building the .war file
- Adding development reports
- Adding Cobertura for test coverage reporting
- Disabling Testing
- Adding integration testing via Jetty
- Getting Help
- Extensibility
- Dependency Analysis
- Performance
- Conclusion
- Appendix
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-ofanddependsattributes oftarget. This is how the Jetty server is started and stopped before and after the integration tests. - The
plugindeclaration has apathsentry that adds the optionaljsp-2.1dependency 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
-projecthelpcommand line option that lists targets available in the current project. - Built-in
list-pluginstarget 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 helpto 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 thetargetandidproperties. e.g.quokka help -Dtarget=jetty:runwill 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:treegoal 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>