Jim has posted 11 posts at DZone. View Full User Profile

Migrating to Maven 2 (Part 1)

04.28.2008
| 15537 views |
  • submit to reddit

Ever wanted to move your build to Maven 2, but not completely re-arrange your codebase? In this 3 part series, we’ll do just that with the Google Guice framework. In this first part, we’ll take a quick tour of Maven and “Mavenize” the core Guice module. In part two, we’ll Mavenize the other Jar deliverables and take full advantage of Maven’s Reactor and multi-module build capability. The third part in this series, we’ll look at how to take advantage of the Jetty plugin and see how to edit classes and JSPs easily while the web application is running. I'll also be giving a presentation on this article at JavaOne 2008, and I would enjoy hearing your feedback on how to make it a better presentation.

A Quick Tour of Maven

What exactly is Maven 2?

  • First and foremost, it is a build tool that provides a declarative way to express project structure and dependencies, giving new developers a quick understanding of the layout of your project’s source tree and dependency versions. In this sense, it is a project comprehension tool.
  • Second, it can give you a quick project health-o-meter. It can quickly generate a project website and reports, allowing you and other developers to get an idea of how healthy your codebase is using tools like Findbugs and Cobertura, and PMD, among others.
  • Third, it provides a transitive dependency mechanism for your project, allowing you to only focus on the jars you need for your particular project.
  • In addition, it allows you to store your dependent jars outside of your source control system, making checking out a new project much faster. If multiple projects all use the same version of a Jar, it doesn't have to be downloaded again.
All of this information is contained in one (or more) project descriptor file(s) named pom.xml - short for Project Object Model. A small project usually has only one pom.xml file, but larger projects may have multiple pom.xml files located throughout the source tree. The goals of Maven are discussed in more depth and detail on Maven's Goals page.

Know you need to use Spring in your project, but not sure what dependent jars you need? No problem - just specify what version of Spring you'd like to use, and all of the jars it depends on will be downloaded for you. Need to upgrade the version of Spring you're using? Just change the version number, and that's it! Where are the dependencies downloaded from? They're typically downloaded from http://repo1.maven.org/maven2 at Contegix, who donates the space and bandwitdh. This location is sometimes referred to as the Central Repository. They are downloaded to your computer, to the .m2 directory in your home directory on your OS, though this can be changed in your Maven settings.xml file.

Each Jar in the Maven 2 repository listed in a POM as a dependency has a groupId, artifactId and version number. These three parts of the dependency together make what's known as a coordinate - groupId:artifactId:version. An optional part of the coordinate can be a classifier, which is what you use for secondary artifacts like source jars or javadoc jars. The groupId tells Maven what project a Jar belongs to, and the artifactId and version together tells Maven what Jar you want to use. For example, if we want to use different Jars from the Spring Framework project, we specify <groupId>spring</groupId> and <version>2.0.2</version> for both of them, but <artifactId>spring-core</artifactId> for the core Spring Jar and <artifactId>spring-beans</artifactId> for the spring-beans Jar.

Maven's build processes are known as lifecycles. There are three different lifecycles: Build, Site, and Clean. The Build lifecycle has 22 phases, Site has four phases, and Clean has three phases. The Build lifecycle is the one you'll typically want to use, as it has compile and test phases in it, among many others. The Site lifecycle is the one you'll use to build the project website, and the Clean lifecycle will delete the class files and website generated by the Build and Site lifecycles. Maven also has a facility for creating goals that can be executed by users for various Maven plugins. These goals can also be bound to the various lifecycle phases in the Maven build lifecycles to make the plugins easier to use.

Projects that use Maven as their build tool typically follow Maven's conventions, Source files in Maven projects are conventionally in project/src/main/language and project/src/test/language, but can be located in other directories that are specified in the POM file. This also makes it easier for developers new to the project, since they'll know where to look for the various source files in the project.

As your project grows and you find that you want to place different chunks of your build into different Jars, you'll want to make use of Maven's Reactor and munti-module build capabilities. This feature of Maven is covered in depth in Chapter 6 and Chapter 7 of Maven: The Definitive Guide and will also be explored in Part 2 of this series.

Maven also integrates nicely with several different continuous integration systems, both open source and commercial. Cruise Control can launch your build at given periods, Continuum has been built from the ground up as part of the Maven ecosystem, and Hudson has good Maven 2 integration as well. TeamCity from JetBrains and Bamboo from Atlassian both support Maven 2 out of the box and provide helpful statistics about your build as well. You can find instructions on how to set up your Maven build here for TeamCity and here for Bamboo.

Mavenize My Build!

Now for the fun part! To get started, download Maven 2 (2.0.9 as of this writing) and follow the installation instructions. Once Maven 2 is installed, download the source for Guice 1.0 and unzip the source in its own directory. Guice is a small but non-trivial real-world project that can take full advantage of Maven's multi-module capabilities. Our goal is to produce the equivalent artifacts as Guice's current ANT build scripts while keeping codebase refactoring to a minimum. Note: I'm using Java 6 as my JDK. If you're using Java 5, you may need to install the javax.activation Jar to your local repository. Instructions for doing so can be on the Coping wth Sun JARs page. The Activation jar and others that are in this category can be found in downloads of Spring Framework in its spring-framework-x.x.x-with-dependencies.zip

Eclipse, IntelliJ, and NetBeans all have Maven 2 POM editor plugins that can help keep your POM and IDE project in sync, and can also help you find the name of the Jar you're looking for on Ibiblio. m2eclipse is the most mature plugin for Eclipse and does quite a bit for you. Maven Reloaded is helpful for IntelliJ 6 users (V7 has this functionality built in)and Maven Repo Search is very helpful for finding the dependency you're looking for with IntelliJ. On top of that, Maven 2 can also generate project descriptors for each of these IDEs with a simple command, making an IDE neutral development shop a reality. However, POM files and Maven 2 builds are almost as easy to work with using a text editor and command prompt if you're not an IDE fan. Since Guice is an IntelliJ project, it may be easiest to take this option if you don't use IntelliJ.

Specifying the source and test code directories

Once you've unzipped Guice's source directory, you'll find that a pom.xml file has been created, but doesn't have much in it. What it does have though is definitely helpful and we can use it to get started. The compiler plugin specifies the version of Java we want to use, but we'll need to specify the source and test source directories. Let's add them. Your build section should now look like

  <build>
<sourceDirectory>src</sourceDirectory>
<testSourceDirectory>test</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
</plugins>
</build>

If you have multiple source or test directories, the builder-helper plugin is absolutely invaluable.

You'll notice quite a number of Maven plugins are downloaded, and then compiler errors are generated because we haven't specified all the project's dependencies.

Specifying and installing Guice's dependencies

The to get the dependencies needed to get the Guice core compiling, your dependency section should look like

  <dependencies>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>2.2-beta-1</version>
</dependency>
<dependency>
<groupId>asm</groupId>
<artifactId>asm</artifactId>
<version>3.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>

but don't compile the source just yet. The version of cgli-nodep needed by Guice isn't in the Maven Central repository just yet, so we need to install it ourselves. Navigate to the guice-1.0-src/lib/build directory in your command prompt and type

mvn install:install-file -Dfile=cglib-nodep-2.2_beta1.jar -DgroupId=cglib -DartifactId=cglib-nodep -Dversion=2.2-beta-1 -Dpackaging=jar

This will install cglib-nodep-2.2_beta1.jar using the appropriate Maven naming conventions for beta jars. Navigate back to the root project directory in your command prompt and type

>mvn compile

and you should see BUILD SUCCESSFUL. Although we don't need JUnit to perform a compile, we'll need it in just a minute, so we'll leave it in there.

A quick note on transitive dependencies and nearness

If we had used a released version of cgib that was posted on Ibiblio, we wouldn’t have needed to list the ASM dependency because cglib already has a dependency on ASM 1.5.3. However, if a newer version of ASM comes out that we’d like to use instead (as in this case), we can list that version in our POM and it will be used during compilation instead of the version already used by cglib. Maven 2's compilation classpath uses the jars listed in the <dependency> section and all of the jars that those jars are dependent upon.

This behavior is due to an algorithm often called the Nearness Algorithm because Maven determines the Jar version to use based on the version "nearest" to the dependencies listed in your POM. If the dependency is listed in your POM, that version will be used in your build. If the dependency is not listed in your POM, the version that is the closest transitive dependency will be used. This is explained further in Section 3.6 of Better Builds With Maven.

Adding test dependencines

Now that the source is compiling, we should get the unit tests compiling and running. Add the following dependencies to the bottom of your <dependencies> section after the junit jar dependency):

    <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>2.0.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>2.0.2</version>
<scope>test</scope>
</dependency>

 

We can either compile the unit tests by running the command >mvn test-compile or we can run the unit tests by running >mvn test on the command line. The test-compile phase is helpful when you need to make sure your test sources compile, but don't want to run your unit tests. Running the unit tests, you'll see they run in a blazing 6 seconds.

Dependency scopes

You'll notice that all of the Jars needed for unit testing have the <scope>test</scope> element as part of their dependency listing. This tells Maven that these jars are only needed during unit testing, but should not be added to the deployment POM. This is covered in further detail on the Introduction to the Dependency Mechanism page.

 

Codebase Health with Maven 2 Reports

Now that all of the unit tests are passing, we can start adding reports and allow for project status comprehension. The reports we'll be adding are

  • Javadoc: Generates javadocs for the project
  • JXR: Generates a javadoc-style source listing with hyperlinked project classes
  • Taglist: Generates a list of the TODO and FIXME tags found in your codebase
  • Surefire: Generates a Unit Test pass / fail report
  • Cobertura: Generates a Unit Test Coverage report
  • JDepend: Generates metrics for the project
  • FindBugs: Generates a FindBugs report (report threshold and effort are adjustable)
  • PMD: Produces a PMD report and PMD Copy/Paste Detector report
  • Rat: Generates a Google Release Audit Tool report

JavaNCSS is another tool that generates metrics for the project, but a bug in the parser unfortunately breaks on some Java 5 features.

Copy the following xml into the POM, directly below the <dependencies> section:

  <reporting>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jxr-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>taglist-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-report-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>cobertura-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jdepend-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<configuration>
<threshold>Low</threshold>
<effort>Max</effort>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
<configuration>
<linkXref>true</linkXref>
<sourceEncoding>utf-8</sourceEncoding>
<minimumTokens>100</minimumTokens>
<targetJdk>1.5</targetJdk>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>rat-maven-plugin</artifactId>
</plugin>
</plugins>
</reporting>

Once you've added these report declarations to your POM, execute the command

>mvn site

to generate your Maven 2 project site. It may take a while since quite a number of Jars need to be downloaded in order to run the reports. Fortunately they only need to be downloaded once. Even then, generating the site can take a while and should typically only be run as part of a continuous integration build or nightly build.

To view the site you've just generated, open up /guice-1.0-src/target/site/index.html and the site you just generated will come up in your browser. To view the reports, click on the Project Reports link and the reports section will expand. Even at stringent report settings, Guice looks really good!

The SCM information, mailing lists, and other static project information can be added as well and is located in the POM for Guice in the central repository here.

 

Making Guice into a Jar

Packaging Guice

Although we've finished creating the POM for Guice, we still need to generate guice.jar. To do so, run

>mvn package

and a jar named guice-1.0.jar will be located in the /guice-1.0-src/target directory. The Jar created is usable, but will require other users to put aopalliance.jar, cglib-nodep-2.2_beta-1.jar, and asm-3.0.jar on their classpath manually, and we can't use Guice in other projects. There are a few things we can do to deal with each of these situations:

Installing Guice.jar to the Local Repository

To allow Guice to be used by other projects being built locally by Maven 2 on your machine, it needs to be installed into your local repository. This is done by running

>mvn install

and Guice will be installed into your local repository in .m2/repository/com/google/code/guice/guice/1.0 along with the POM file we've made so far.

Assembling Guice.jar for Distribution

To generate a jar that can be used by other developers without needing to download any dependencies, we'll want to make use of the Maven Assembly Plugin to bundle all of the jars needed for Guice.

Add the following XML below the maven-compiler-plugin in your POM:

      <plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>

and execute the command

>mvn assembly:assembly

The generated jar named guice-1.0-jar-with-dependencies.jar will be in your guice-1.0-src/target directory. This Jar has the classes from the ASM 3.0, cglib, and aopalliance Jars bundled in the Jar along with Guice's .class files.

 

Deploying Guice

To make Guice available to your team repository (check out Artifactory, Archiva, and Nexus), use the Maven Deploy Plugin to share guice-1.0.jar and its POM, specify your team's repository as described in the Using section and performing it with

>mvn deploy:deploy

Since cglib-node-2.2_beta-1 isn't available on Ibiblio, it will need to be added to your team's repository as well using

>mvn deploy:deploy-file

Once these two steps are done, your teammates can use Guice in their Maven 2 builds with very little effort.

To enable the most sustainable solution cglib-nodep-2.2_beta-1 should be uploaded / deployed to the Central Repository, removing some of the effort you've gone through in this how-to.

 

Using Different Versions of Dependent Jars

Using different versions of dependent jars is very easy with Maven. You only need to change the value in the <version> element of each Jar you'd like to upgrade, and Maven takes care of the rest. Want to use a newer version of ASM? Just replace 3.0 with 3.1 and do a >mvn compile Want to test Guice with newer versions of Spring? Just change the versions of the Spring Jars from 2.0.2 to 2.5.3, do a >mvn test and that's it. You don't have to locate these Jars and their dependencies and then copy them to your lib folder -- all of that is done by Maven. We'll see even more of Maven's dependency management capabilities in Part 2.

 

Generating IDE Project Descriptors

Now that we have a full blown POM that's shared with teammates, they can all generate project files for the different IDEs they're using

>mvn netbeans-freeform:generate-netbeans-project

>mvn idea:idea

>mvn eclipse:eclipse

or if you're using the m2eclipse plugin, use

>mvn m2eclipse:eclipse

And now everyone can be highly productive using the IDE they enjoy the most.

Conclusion

We've had a quick tour of Maven 2, successfully built a POM that compiles Guice, generated a helpful website for it, and installed the needed dependencies that are not available on Ibiblio. We've also generated a Jar that can be used by other developers and made Guice available in a team repository that can be used by teammates. On top of that, we've seen how easy it is to upgrade dependent Jars in the POM file, just by changing version numbers of dependencies. In the next part of the article series, we'll explore how to make use of Maven's multi-module build mechanism.

 

Resources

Text of the completed pom.xml file can be found here.

Maven's External Resources Page

Better Builds With Maven

Maven: The Definitive Guide

Find the dependency you need on http://mvnrepository.com or http://repository.sonatype.org/

AttachmentSize
GuicePartOnePOM.txt6.49 KB
Published at DZone with permission of its author, Jim Bethancourt.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Milos Kleint replied on Mon, 2008/04/28 - 2:09pm

One minor correction.

There's no netbeans:netbeans goal. The preferred way of using Maven based projects in NetBeans is to install the Maven support from the update center and just open the project In the IDE.

 Milos 

Jim Bethancourt replied on Mon, 2008/04/28 - 2:32pm

Hi Milos,

Thank you for catching that error!  I thought I had seen it somewhere, but it looks like it's

>mvn netbeans-freeform:generate-netbeans-project 

through the Netbeans FreeForm plugin -- http://mojo.codehaus.org/netbeans-freeform-maven-plugin/ 

I've updated the article to reflect this. 

Thanks,

Jim 

Brian Fox replied on Mon, 2008/04/28 - 10:22pm

If you're looking for a repository manager, be sure to also take a look at Nexus

Also, there's a new chapter 8 in the Maven: The definitive guide that follows up on chapter 7 to show some multi-module optimizations. This will be published in the book version alpha-3.

Pavel Savara replied on Tue, 2008/04/29 - 5:14am

Hi Jim,

 in next part, cloud you please explain SNAPSHOT versions concept ? How to use it and when to not use it ?

Could you review eclipse integration options, give hints ? Specialy with multimodule project.

Is there way how to prepare template/archetype for new module of my project ? With predefined rules and dependencies ?  

 Thanks Pavel

Jim Bethancourt replied on Tue, 2008/04/29 - 8:55am in response to: Brian Fox

Hi Brian,

Thanks for letting me know about Nexus -- I've added it to the list.

Thanks,

Jim 

Patrick Podenski replied on Tue, 2008/04/29 - 10:05am

FYI: There are now two good options for Eclipse/Maven support:

M2Eclipse and Q4E

Both of these Eclipse plugins are vying for official status with the Eclipse Foundation.

M2Eclipse has some nice integration for artifact searching along with Nexus indexing.

The Q4E plugin has some very nice dependency analysis tools, WTP support and soon will also have a comprehensive POM editor.

You can check it out at: <http://code.google.com/p/q4e/>

And there is an interesting, regularly updated comparison between these two plugins along with the command line eclipse plugin for Maven at: <http://docs.codehaus.org/display/MAVENUSER/Eclipse+Integration>

Ittay Dror replied on Mon, 2008/10/13 - 12:28am

Maven has already been written about many, many times. Why not write about new kids on the block like Buildr or Gradle?

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.