Maven Continuous Integration Best Practices


January 15, 2009 By Brian Fox

Continuous Integration is a development best practice that you need to be using in your process; it is an essential part of an efficient Software Development Lifecycle (SLDC).   If you aren’t using it already, then you should start, now.   The main benefit of Continuous Integration is the ability to flag errors as they are introduced into a system instead of waiting multiple days for test failures and critical errors to be identified during the QA cycle.  This post isn’t about the virtues of using CI, it’s about how to setup an optimal environment in a Maven shop. Here are seven tips for running Maven builds in a CI system such as Hudson.

#1 Automate Snapshot Deployment

In my experience, it is best to let your CI system deploy your snapshots. This is the most reliable way to guarantee that the contents of your repository are kept in sync with your source control system. In order to do this in a practical way, you need to couple CI with a repository manager like Nexus that can automatically purge snapshots. I’ve managed projects that produced >300gb of snapshots in less than a week. Using a repository manager will save your sanity.

#2 Isolate Local Repostitories

Another critical component of a good CI setup is local repository isolation. The local repository in Maven is the temporary holding spot for all artifacts downloaded and produced by Maven, and it is not currently setup to be multi-process safe.   There is a remote possibility of a conflict, but it does exist.

The main reason I like to have a local repository per project is that it’s the only way to test that your project is build-able against the artifacts in the corporate repository. If you don’t have separate local repos, then the product on one build will be seen by another build on CI, even if it’s not in the corporate repository. This is important since one function of CI should be to validate that the code is buildable by a real developer.

Tip: use -Dmaven.repo.local=xxxx to define the unique local repositories for each build.

#3 Regularly Purge Local Repositories

To further validate the contents of the repository, and to manage the disk space, I purge the local repostories every night. This way if changes in the repository or artifacts are removed, the CI system will detect this. To keep it easy to purge all the local repositories, I tend to structure them under a single common folder such as /opt/repos/*.

Obviously having many local repositories requires more disk space than a single monolitic one due to dependency duplication, but even on our large grid the repos are less than 10gb total. Local repos get giant when you don’t control the snapshots and purging them nightly keeps this under control.

Tip: use your CI system itself to schedule the local repo cleanup. This way anyone can clean the repos manually right from the UI if Maven gets confused.

Over time, I’ve also picked up a few more simple tricks:

#4 Enable Batch Mode

Tip: Enable -B (batch) mode on the build. This will make the logs shorter since it avoids the dependency download progress logging. It also ensures that the build won’t hang due to waiting for user input. (to enable globally in settings.xml:<interactiveMode>false</interactiveMode>)

#5 Enable Full Stack Traces

Tip: Enable -e to cause Maven to produce the full stack trace if there’s a build exception. This will make it easier to comprehend any problems in the resulting build failure log/email without having to build it again.

#6 Print Test Failures to Standard Output

Tip: Enable -Dsurefire.useFile=false. This is a favorite of mine since this causes surefire to print test failures to standard out, where it will get included in the build failure log and email. This saves you from having to dig back onto the machine to find the surefire report just to see a simple stack trace. (to enable globally in settings.xml:<properties><surefire.useFile>true</surefire.useFile></properties> in an active profile)

#7 Always check for Snapshots

Tip: Enable -U to cause Maven to always check for new snapshots. (to enable globally in settings.xml: <updatePolicy>always</updatePolicy>….this goes on a repository definition)

Summary

Using the above settings and process causes every build to push the artifacts to the repository. The next downstream build will have its own clean repo and check the repository manager for the latest snapshots. Then at least once a day, everything is dumped locally and all dependencies are pulled out of the repository manager.

Naturally, doing all of this updating and purging puts some network load between the CI and repository manager. This works best if they share a highspeed network. If your repository manager isn’t close to your CI system, then you should put one there, if only to proxy the artifacts and reduce the impact of the daily local repo purge.

Note: If you are going to follow these tips, it is essential that you download a copy of Nexus, purging the contents of your local repository and downloading everything from the Central Maven repository once a day (per project) is exactly the sort of behavior that causes traffic problems on the Central Maven repo.