Today I've made some improvements with my Mercurial + Maven + Hudson setup - and reached a new level of karma, being able to do automated releases.

Let's go in order. First let me recap what happens with the Maven release plugin (mvn release:prepare release:perform) and Mercurial:
  1. A check is performed that there are no uncommitted changes and a build is performed as a validity proof.
  2. All the version labels in the current project are updated (e.g. 1.3.7-SNAPSHOT to 1.3.7) and changes are committed
  3. A tag in the SCM repository is made for the new release (e.g. release-4.5)
  4. All the version labels are updated again (e.g. 1.3.7 to 1.3.8-SNAPSHOT) and changes are committed again
  5. Another copy of Maven is spawned on a temporary directory where sources are checked out from the SCM, with the previously created tag; another build is performed and artifacts deployed.

It's a robust sequence, as it makes sure that there are no inconsistencies in version sequences and the release is built exactly from the tagged files.

But there are two specific problems with Mercurial:
  1. The fact that Mercurial is a distributed SCM is not fully exploited. You recall that commits are only performed locally and not shared until a push is performed. It would be a nice thing if the push was performed at the very end of the sequence, so in case something went wrong, you can rollback everything. Unfortunately, Maven performs the push at the end of step #4, and something can still fail during step #5. I suppose that since step #1 performs a full build, Maven developers thought that we have a proof of the validity of the build, but unfortunately in the real world everything can fail at every time (for instance, an OutOfMemoryError).
  2. The check out performed at step #5 is indeed a Mercurial clone, that is executed from the remote repository. This is a very stupid thing, since Mercurial repositories can be huge, so this operation can be time consuming and expensive; the local repository could be cloned in a more efficient way, but it sounds as a hard fix for Maven.

I'd say that the former is a real problem, while the latter is a serious annoyance. Here how I fixed both problems.

First, I've used a property in the SCM section of the pom for specifying the URL:

<scm>
    <connection>scm:hg:http://kenai.com/hg/forceten~src</connection>
    <developerConnection>scm:hg:${staging.hg.repo.url}</developerConnection>
    <url>http://kenai.com/projects/forceten/sources/src/show</url>
</scm>

<properties>
    <staging.hg.repo.url>https://kenai.com/hg/forceten~src</staging.hg.repo.url>
</properties>

In this way the URL can be overridden with a property such as -Dstaging.hg.repo.url=something: the idea is to prepare a temporary, local repository where Maven pushes all the changes made for the release; furthermore, the clone at step #5 is performed from that local repository, thus saving time and bandwidth.

Second, I used the altDeploymentRepository property of the deploy plugin (which is called by the release plugin) that allows to override the definition of the target Maven repository and make it point to a local folder. At the end of the process, I have that all the changes are stored locally; in other words, I've used staging repositories both for the sources and the build artifacts. If there are no errors, I can later push the local Mercurial repository to the public Mercurial repository and copy the artifacts in the Maven repository to my public Maven repository.

This is the required configuraton for the maven release plugin:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-release-plugin</artifactId>
    <configuration>
        <arguments>-Dstaging.hg.repo.url=${staging.hg.repo.url} -DaltDeploymentRepository=release-repo-hudson::default::${staging.mvn.repo.url} -Dstaging.mvn.repo.url=${staging.mvn.repo.url}</arguments>
    </configuration>
</plugin>

Basically we are propagating the relevant properties to the spawned instance of Maven; there's a little verbosity as we need the Maven staging repository expressed in form of a URL (see below) while altDeploymentRepository needs it prefixed with some specific Maven repository notation.

Maven should be called with these options:

-Dstaging.hg.repo.url=/my/hg-staging-repo -Dstaging.mvn.repo.url=file:///my/mvn-staging-repo

For copying the Maven artifacts from the staging repository to a public one the wagon-maven-plugin can be used, as it has an option to merge two repositories (goal wagon:merge-maven-repos); the required configuration is:

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>wagon-maven-plugin</artifactId>
    <configuration>
        <source>${staging.mvn.repo.url}</source>
        <target>https://services.tidalwave.it/nexus/content/repositories/releases/</target>
        <targetId>maven2-release-repository.tidalwave.it</targetId>
    </configuration>
</plugin>

It is possible to call it by a separate Maven invocation after the main build, or it can be specified in the goals of the release plugin:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-release-plugin</artifactId>
    <configuration>
        <goals>deploywagon:merge-maven-repos</goals>
        <arguments>-Dstaging.hg.repo.url=${staging.hg.repo.url} -DaltDeploymentRepository=release-repo-hudson::default::${staging.mvn.repo.url} -Dstaging.mvn.repo.url=${staging.mvn.repo.url}</arguments>
    </configuration>
</plugin>


In this way, mvn release:prepare release:perform does all but Mercurial synchronization; the latter task just needs a regular hg push and can easily be performed by a post-build script.

If I get an error, nothing gets out of my disk, so I can delete the staging repos, fix the problem and retry.

It is also possible to skip the synchronization between the staging repositories and the real ones, in case one only wants to test the process. For instance, I'm trying to have the artifacts generated by the assembly plugin deployed and I'm not able to do that yet; my trials and errors can be done locally without polluting the public repositories.