ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Maven: Trove of Tips Maven: Trove of Tips

by Andreas Schaefer
08/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

Next Pagearrow