Saturday, June 26, 2010

maven release management

Introduction

Following up on my previous article about Maven, this one will dive into the deep and unleash the wonders of performing release management using Maven.

For release management you need some kind of source repository; this article will assume you will be using Subversion.


What is release management?

There are plenty of definitions out there, mine is as follows:

"to regulate, document and safely store source code, versions and releases".

Broken down into sections:

- source code is version managed (subversion)
- software is 'released', creating for example a milestone tag in subversion
- binary artifacts (ears usually) are also version managed
- version releases end up in a maven repository so their artifacts can be referenced from mavenized projects
- software projects are checked for compile problems and unit test failures through an automated build server

We won't be discussing any build server in this article as it is not really maven specific, but I'm going to make sure everything else is covered!


Hooking in the Nexus

A good place to store our own maven artifacts upon releasing them is in a Nexus repository. I'll leave it to the Nexus documentation to guide you how to set one up, but the least I can tell you is how to configure one in your maven POM. You'll want to put the following into your parent pom:

<distributionManagement>
    <repository>
      <id>releases</id>
      <name>Local release directory</name>
      <url>http://yournexushost/nexus/content/repositories/releases</url>
    </repository>
    <snapshotRepository>
      <id>snapshots</id>
      <name>Local snapshot directory</name>
      <url>http://yournexushost/nexus/content/repositories/snapshots</url>
    </snapshotRepository>
  </distributionManagement>

This piece of configuration assumes that you will be creating a 'releases' and a 'snapshots' repository in your Nexus server. This makes sense because in Maven we will be dealing with exactly those two types of artifacts. I'll leave it up to you if you really want snapshot versions of your projects to actually end up in Nexus.


If you followed the Nexus instruction guide (IE. you did some setup in your settings.xml also!), then you should be up and running to have Maven use Nexus!


Subversion on the commandline


To let maven do subversion stuff, you'll need to have a commandline subversion client installed. There are a few; I suggest to use SlikSVN.

Maven will do "non-interactive" commits, meaning it will not ask you for a username and password when it tries to commit to SVN. To tell Slik which username and password you want to use always, do an SVN commit from the commandline once; Slik will then ask you for your credentials. When you have them set, Maven will automatically adopt them and you won't have to pass them along to Maven when you perform releases.

Note: be sure that the SVN command can be invoked without a path; add the Slik binary directory to your PATH environmental variable!


Subversion and maven: friends for life

The first thing we will want to take care of is to tell Maven where our project is stored in your subversion environment. We'll want to tell it where it can get the source code, and where to store tags. You can do this with some simple configuration in your parent pom.

<scm>
    <connection>scm:svn:http://svn.path.to.your.project/myproject/trunk</connection>
    <developerConnection>scm:svn:http://svn.path.to.your.project/myproject/trunk</developerConnection>
  </scm>

This tiny piece of information tells Maven where you will be committing source files to - in this case to the trunk of our imaginary project. You could also specify a branch if you would want that. Maven SCM is a neat plugin that allows you to connect to far more than Subversion by the way - I'd explore the maven documentation on this topic.


The actual magic is performed by the maven release plugin - that is the one we'll be configuring.

<plugin>
        <artifactId>maven-release-plugin</artifactId>
        <configuration>
          <tagBase>http://svn.path.to.your.project/myproject/tags</tagBase>
          <preparationGoals>clean install</preparationGoals>
        </configuration>
      </plugin>

A vital piece of information is the tagbase; this is where we will be storing our release versions in subversion. Also, the preparationGoals tells Maven what it should do before we do an actual release; we will want it to do a clean install to be 100% sure that we are working with a clean up to date project.

And we're there actually, Maven is now setup to do some wicked release management steps for us.

Maven version management

Not subversion, but the actual versions of your project and its modules. In order for release management to work, you MUST adhere to the rules maven lays down for you.

Luckily, there is only one rule. During development, the version of your application will be 'version-SNAPSHOT'. The important part here is the -SNAPSHOT. For example, you can have version 1.0-SNAPSHOT. This literally translates to 'version 1.0 that is still in development', thus not released yet. If you do not work with snapshot version during development, you are not going to be able to use maven release, as one of its steps is to promote the snapshot versions to release versions!


Preparing a Maven release

To do the actual release, we need to go to the command line. Before we continue, you should know what Maven is going to do for us.

The first step of a release is to prepare it. You do this by invoking the following command under the parent pom:

mvn release:prepare

  • First of all, it will check if you still have external snapshot references in your dependencies; you should resolve those to release versions before you perform the actual release.

    Also, Maven will check your project against what is in Subversion to see if there are no differences. You'll find you cannot do a release if there is but one difference somewhere; make sure to update and commit your project!
  • The first thing that maven will do (after the clean install) is ask you which versions of the module you will be committing. Maven will turn all snapshot versions of your project's parent and modules into release versions automatically. Usually you can just <enter> through these to keep the default values.
  • The second thing maven is going to ask you is the tag name you will give your release; this is the exact name of the project as it will end up in your tags folder of the subversion repository. Choose it well; usually companies have a naming scheme for this. Be sure to put the current version number of the project into it at least.
  • The third thing maven will ask you are the next snapshot versions of your parent and modules if you have a logical version number (such as 1.X), maven will automatically generate a correct next minor version number, thus you can <enter> through the suggestions to keep them.

Phew. Once all that is done, maven will perform some magic which will generate a lot of output. If everything is alright, you'll get a build succesful and you'll have a fresh new tag in your subversion tree, plus the poms in your subversion repository will have their versions incremented to the next snapshot version. Hurray! But we're not done yet!


Performing a Maven release

We have to invoke one additional command, and that is:

mvn release:perform

This command will check out the tag we just created from the subversion repository, and it will try to compile/unit test/package it (depending on what you have setup). Afterwards if that succeeds, the release is uploaded to the nexus repository under the right group, artifact and version.

And that is all! You may want to take the packaged binaries now and store them away safely - for example I opt to put all release binaries in a releases subversion repository!


Resume and rollback

If for some reason something blows up during the two release phases, you have two options available to you.

a) fix the problem.
If you can, you can use the -Dresume=true switch to make Maven pick up where it left off. For example:

mvn release:prepare -Dresume=true


b) rollback
The action of rollback is to undo the damage that Maven has already done. After the rollback your project will be back in the state as if you didn't invoke the release command.

mvn release:rollback


Adding unit testing to the mix

No release management cycle is complete without automated unit testing. Maven, again, jumps to the rescue. Maven has a plugin called Surefire that can automatically invoke JUnit or TestNG unit tests that are part of our project. All we need to do, is tell maven that they exist.

First of all, we need to add the surefire plugin definition to our parent pom:

<plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.5</version>
        <childDelegation>true</childDelegation>
        <useSystemClassLoader>true</useSystemClassLoader>
        <argLine>-Dsun.lang.ClassLoader.allowArraySyntax=true</argLine>
      </plugin>


If you want to use TestNG, you have to add the TestNG dependency to your project.

<dependency>
      <groupId>org.testng</groupId>
      <artifactId>testng</artifactId>
      <version>5.8</version>
      <scope>test</scope>
      <classifier>jdk15</classifier>
    </dependency>

Also don't forget to add the dependency to any module (EJB, WAR) that you have unit tests in.


Now that surefire is setup, it will invoke test cases that are part of your project. By default, Maven will add resources in the following directories to the test classpath:

module/src/test/java
module/src/test/resources

This is the maven default and you should follow it; however if you want to override the convention, you can have control over it by adding the following configuration to your module pom:

<build>
  ...
  <testSourceDirectory>src/test/java</testSourceDirectory>
  <testResources>
    <testResource>
      <filtering>true</filtering>
      <directory>src/test/resources</directory>
    </testResource>
  </testResources>
  </build>

Through the testResources you can also include and exclude certain file patterns - for example if you only want to add XML files from the test resources, do the following:

<testResource>
      <filtering>true</filtering>
      <directory>src/test/resources</directory>
      <includes>
       <include>**/*.xml</include>
      </includes>
    </testResource>

Try it out: upon a mvn clean install you should now see any unit tests in your project being invoked. Not only that, but if a unit test fails your build will fail (this is something you actually want)! Also, you can run tests by invoking the following command:

mvn clean test

(I always add the clean step to be sure you won't be using out of date classes - leave it out at your own discretion).

Unit test not running?
Surefire has a naming convention. Check this documentation page for more information:

Surefire documentation


Reporting

The final thing you may want to add is report and documentation generation. For example the surefire reporting is especially useful when trying to figure out why a testcase blew up - the report will contain the generated error.

Add the following configuration to your parent pom:

<build>
   <plugins>
      ...
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-javadoc-plugin</artifactId>
        <version>2.7</version>
      </plugin>
   </plugins>
  </build>

  <reporting>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-report-plugin</artifactId>
        <configuration>
          <showSuccess>true</showSuccess>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-javadoc-plugin</artifactId>
        <configuration>
          <aggregate>true</aggregate>
        </configuration>
      </plugin>
    </plugins>
  </reporting>

Adding the javadoc plugin allows you to build documentation for your project using a simple maven command:

mvn javadoc:javadoc


Conclusion


I hope I have given you the tools you need to setup release management in your own environment. Yes it requires some work and you need to wire some tools (usually with lengthy manuals) together to get a benefit out of using Maven, but in the end it is very much worth it - I can guarantee that.

As a side note, I would like to point out the existence of Continuum, which is a build server you can use to automatically test Mavenized project builds. Continuous integration is really that final icing on the cake that can make your release management cycle complete. But I have to warn you: setting up Continuum is no picnic. You may want to explore some of the alternatives that are available, such as Hudson.

Good luck!

No comments:

Post a Comment