Maven: Trove of Tips
by Andreas Schaefer08/04/2004
When I looked at Maven the first time, I thought it was an overblown and complex tool that would never replace Ant. So far, Ant was doing its job quite well and I thought, "Why should I bother to delve into an unknown tool when there's no immediate need?" How little did I know.
Nevertheless, projects like AspectWerkz, XDoclet and others were using Maven, and I was still wondering if they would know something I did not. So when I started my JDoppio project and started writing my Ant build script, I eventually got this deja vu feeling of doing the same thing over and over again. Figuring there was nothing to lose, I decided to use Maven as a test. After downloading Maven and checking out its web site, I started to write my project object descriptor, project.xml, and after 15 minutes I could compile and test my project. Since then, I've used Maven to build my project and never looked back. So far, I can realize any requirements that the project might require, no matter how advanced they are -- some of which I could have hardly done with Ant.
Many developers responded to my success by saying that they would like to use Maven, but do not like to change from a well-known tool and enter uncharted territory. I have to admit that using Maven needs some mind bending, and handing over to Maven some control that was previously held by the developer, but in my honest opinion, it is worthwhile. Still, I recommend playing around with Maven before migrating a project, because you need some understanding of how Maven works and how to go along with Maven to get the most out of it.
This article will show some of the tips and tricks I figured out with JDoppio. Hopefully, I can give you a hint how to proceed and where to get help. In the end, Maven is all about helping you, fellow developers and users, to save time and money building projects, project web sites, and distributions. But this requires that you know how to use Maven in a way that fits your project best.
Project Object Model: project.xml
The project object model, or POM, is the heart of the Maven build tool, even though it is the smallest part of the tool. Here I do not want to give a full description of the POM; instead, I want to focus on what will be important later on. The POM file begins with general project information, which is mostly used to generate the project web site. After that, the POM allows you to specify different versions of a project based on CVS tags. This allows you to create release branches and keep them separate from the main development. After listing the developers and the distribution license, the POM defines the dependencies of the project on other archives and tools. This section looks like this:
<dependencies>
<dependency>
<groupId>ant</groupId>
<artifactId>ant</artifactId>
<version>1.6.1</version>
<properties>
<server.lib>true</server.lib>
</properties>
</dependency>
...
</dependencies>
Maven creates a local repository under ~/.maven/repository that is used to store archives from a remote site, as well as archives generated and installed locally. Its layout looks like this:
repository
+-- <group id>
+-- <type>s
+-- <artifact id>-<version>.<type>
...
In the POM outlined above it would try to find the archive
~/.maven/repository/ant/jars/ant-1.6.1.jar, because the
type is set to .jar by default. If this archive could not be found
locally, it would try in the current remote repository with a relative URL of /ant/jars/ant-1.6.1.jar. You can also browse the remote repository if you need to figure out the group
and artifact IDs and the support version of an archive. Note that a
group can contain more than one archive with different names and
versions. In addition, you can add properties to a particular
archive in the dependency list which can be used later to identify
archives you need for a particular purpose, and we will use this later.
In the example above, the Ant 1.6.1 dependency has a property
named server.lib with the value true.
After that, the build element describes what is built and how it is tested:
<build>
<sourceDirectory>
src/java
</sourceDirectory>
<unitTestSourceDirectory>
test/src/java
</unitTestSourceDirectory>
<unitTest>
<includes>
<include>**/*Test.java</include>
</includes>
</unitTest>
<resources>
<resource>
<directory>
${basedir}/src/resource/logging</directory>
<includes>
<include>log4j.xml</include>
</includes>
</resource>
</resources>
</build>
First, this specifies the source directory -- Maven will try to compile all of the classes in this directory and its subdirectories. Then the unit test source directory specifies where the jUnit tests can be found. The unit test element then lets you specify which classes are test cases and should be executed when Maven tests the project. Finally, you can specify resources needed to build distributions, etc. Note: a complete documentation of the POM can be found on the Maven site.
If everything is set up, the source and jUnit test cases are in place, then you can compile and test your project by just entering:
maven jar:jar
on the command line. If you are offline, you can try to run Maven with
the -o option so that it will not try to download archives from the
remove repository; however, in this case, a build could fail for want
of a needed archive. Typical Maven output appears as follows:
$ maven
__ __
| \/ |__ _Apache__ ___
| |\/| / _` \ V / -_) ' \ ~ intelligent projects ~
|_| |_\__,_|\_/\___|_||_| v. 1.0-rc3
BUILD SUCCESSFUL
Total time: 1 seconds
Finished at: Tue Jun 22 13:44:36 PDT 2004
Having finished the first step of setting up a Maven project, we are ready to look at some of the advanced features of Maven.
Goals are What Drives Maven
When Maven is executed, it will start with the given goal. First, it
will execute what is specified to be run before the goal is executed,
then the goal itself, and finally, what is specified to be executed after
the goal. Maven comes with a ton of goals ready to be used; these can be
viewed with maven -g, which produces output that looks like this:
$ maven -g
__ __
| \/ |__ _Apache__ ___
| |\/| / _` \ V / -_) ' \ ~ intelligent projects
|_| |_\__,_|\_/\___|_||_| v. 1.0-rc3
Available [Plugins] / Goals
===========================
genapp .........................
Generate Application based on a template
html2xdoc ......................
Generates XDoc documentation from normal
HTML files
jdiff ..........................
generate an api difference report between
versions
junitdoclet ....................
Generate unit tests
[announcement]
Generate release announcement
generate .......................
Generate release announcement
...
[java]
( NO DEFAULT GOAL )
compile ........................
Compile the project
jar ............................
Create the deliverable jar file.
jar-resources ..................
Copy any resources that must be present in
the deployed JAR file
prepare-filesystem .............
Create the directory structure needed to
This listing indicates that in order to compile the project, you have to execute
Maven with maven java:compile, and to create a .jar file, you execute
maven java:jar. (Note: this goal is deprecated and
you should now use jar:jar instead.) Of course, most of these goals
also have other goals to be execute before or after the goal
itself. For example, the goal java:compile will have to execute the goal
java:prepare-filesystem beforehand to make sure the
necessary directory structure is in place.
You may wonder where these goals and their dependencies are coming from. You can easily figure it out by looking in the local plugin directory of Maven, in the ~/.maven/cache directory. There you will find a directory such as maven-java-plugin-1.4 containing a POM as well as the project.jelly file:
<project
xmlns:j="jelly:core"
xmlns:ant="jelly:ant"
xmlns:define="jelly:define"
xmlns:maven="jelly:maven">
<goal name="java:prepare-filesystem"
description="Create the directory
structure needed to compile">
<ant:mkdir dir="${maven.build.dest}"/>
</goal>
<goal name="java:compile"
description="Compile the project"
prereqs="java:prepare-filesystem">
<ant:echo>
Compiling to ${maven.build.dest}
</ant:echo>
<j:choose>
<j:when test="${sourcesPresent == 'true'}">
<ant:javac
...
This file is a Jelly Script file, with the project element specifying all additional Jelly tag libraries it includes, along with the namespace of these libraries. Even though you could omit the constant specification of the namespace -- <mkdir
...> instead of <ant:mkdir ...> -- I think it is
good practice to include namespaces, for documentation as well as
the clarity of the script.
Further on in the code, we find the goal
java:compile, with the prerequisite
java:prepare-filesystem. The body of this goal specifies how
the project is compiled. In this body, Ant and Jelly tags are
used to compile the Java code. As you probably guessed, Jelly tags
check to see if Java source is present and otherwise omits the
compilation. This scripting ability gives Maven a big advantage over
Ant, even though Maven is meant not to compete with Ant, but rather utilize it
when possible.
But what do you do when there is no goal available to do what you need? In this case, you can write your own extension, which will be called maven.xml and located in the same directory as the POM. This file looks the same as the one above and more or less does the same thing, except that it is private to the project. In this file, you can do one of the following:
- Add your own script either before or after goal execution (pre- or post- goal).
- Create your own goals.
- Call other goals within your scripts.
- Use the Jelly tag libraries to use Ant, Maven, etc. to do what you need.
Warning: you may be tempted to create a ton of scripts to do whatever you need. I recommend searching the Maven plugins first to see if you can find a plugin to do what you need. Not only will this make your file smaller and easier to maintain, but the plugin will also be updated over time, freeing you to focus on your own development. In addition, looking at these plugins can teach you a lot about using Maven.
A simple Maven extension looks like this:
`<project default="jdoppio:build"
xmlns:j="jelly:core"
xmlns:maven="jelly:maven"
xmlns:deploy="deploy"
>
<goal name="jdoppio:build"
description="Build all JDoppio
components">
<maven:reactor basedir="${basedir}"
includes="**/project.xml"
goals="jar:install"
banner="Installing:"
ignoreFailures="false" />
</goal>
...
This script defines the project's own build goal: jdoppio:build, better expressing what it does than a
simple java:compile. Inside, it is using Maven's reactor to execute
several sub-projects using the jar:install goal of each, which
compiles and tests the projects. jdoppio:build then
builds a distribution and copies into the local repository. The last step is important because other
sub-projects depend on it, and will use the POM's dependencies to use
these archives from the local repository.
Pages: 1, 2 |


