Migrating to Maven 2 (Part 1)
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.
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
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
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
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):
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.
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:
Once you've added these report declarations to your POM, execute the command
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
Although we've finished creating the POM for Guice, we still need to generate guice.jar. To do so, run
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
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:
and execute the command
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.
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
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
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
or if you're using the m2eclipse plugin, use
And now everyone can be highly productive using the IDE they enjoy the most.
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.
Text of the completed pom.xml file can be found here.
Maven's External Resources Page
(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)