Fixing two problems with Maven + Mercurial + Hudson: second take
Posted by fabriziogiudici on November 9, 2009 at 2:46 PM EST
In the past weekend I've been able to improve the settings for
automated Maven releases that I've
blogged about about ten days ago. Peter Mount complemented
the information with some practical examples on how to use
that stuff invoking Maven with the proper parameters. I've been able to
significantly clean up and improve the Maven configuration, so now a
staged release can be performed exclusively with a sequence of Maven
invocations, specifying profiles instead of a complicated set of
parameters. And I'll show you how to fit it better with a CI
environment such as Hudson.I'm not repeating the concepts behind this stuff, that have been explained in my previous post, but just showing you the relevant sections of the POM.
First, the SCM URL is defined by means of a property, which is hg.repo.url, whose value has a default in the properties section:
<scm>
<connection>scm:hg:http://kenai.com/hg/forceten~src</connection>
<developerConnection>scm:hg:${hg.repo.url}</developerConnection>
<url>http://kenai.com/projects/forceten/sources/src/show</url>
</scm>
<properties>
<hg.repo.url>https://kenai.com/hg/forceten~src</hg.repo.url>
<staging.mvn.repo.url>file://${project.build.directory}/target-maven-repo</staging.mvn.repo.url>
</properties>
Then you define a “release” profile which is used to create the staged release, that is to have a local pair of (Maven;Mercurial) repositories populated with all the relevant artifacts. The profile overrides and defines some properties, such as hg.repo.url and altDeploymentRepository, that make Maven point to the staging repositories; furthermore it installs during the initialization phase a couple of goals that scratch and create the relative directories under the target folder (the staging Mercurial repo is also initialized). There's still a minor issue, in the fact that those directories are created for every module, not only for the main directory, in spite of the inherited=false property (which seems to be severely broken with Maven, or at least obscure).
<profile>
<id>release</id>
<properties>
<hg.repo.url>file://${project.build.directory}/dummy-hg-repo</hg.repo.url>
<altDeploymentRepository>release-repo-hudson::default::${staging.mvn.repo.url}</altDeploymentRepository>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<execution>
<id>clean-release-dirs</id>
<phase>initialize</phase>
<goals>
<goal>exec</goal>
</goals>
<inherited>false</inherited>
<configuration>
<executable>rm</executable>
<arguments>
<argument>-rf</argument>
<argument>${project.build.directory}/dummy-hg-repo</argument>
<argument>${project.build.directory}/target-maven-repo</argument>
</arguments>
</configuration>
</execution>
<execution>
<id>create-release-dirs</id>
<phase>initialize</phase>
<goals>
<goal>exec</goal>
</goals>
<inherited>false</inherited>
<configuration>
<executable>mkdir</executable>
<arguments>
<argument>-p</argument>
<argument>${project.build.directory}/dummy-hg-repo</argument>
<argument>${project.build.directory}/target-maven-repo</argument>
</arguments>
</configuration>
</execution>
<execution>
<id>init-dummy-hg-repo</id>
<phase>initialize</phase>
<goals>
<goal>exec</goal>
</goals>
<inherited>false</inherited>
<configuration>
<executable>hg</executable>
<workingDirectory>${project.build.directory}/dummy-hg-repo</workingDirectory>
<arguments>
<argument>init</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
A second profile, “release-commit”, performs both a Mercurial push from the staging repository and also invokes the wagon-maven-plugin:merge-repo goal, which synchronize the real Maven repository with the new artifacts:
<profile>
<id>release-commit</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<execution>
<id>push-hg</id>
<phase>initialize</phase>
<goals>
<goal>exec</goal>
</goals>
<inherited>false</inherited>
<configuration>
<executable>hg</executable>
<arguments>
<argument>push</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>wagon-maven-plugin</artifactId>
<configuration>
<source>${staging.mvn.repo.url}</source>
<target>${project.distributionManagement.repository.url}</target>
<targetId>${project.distributionManagement.repository.id}</targetId>
</configuration>
<executions>
<execution>
<id>merge-repo</id>
<phase>initialize</phase>
<goals>
<goal>merge-maven-repos</goal>
</goals>
<inherited>false</inherited>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
A third profile, “release-cancel”, can be used in alternative to “release-commit” to throw away everything that has been prepared for the release, perhaps because we only wanted to run a test. It basically scratches the local Mercurial repository, so you have to clone it again from the main remote repository. This could be improved, by just stripping (by means of the hg strip command) the local commits that haven't be pushed again, but I haven't found so far a simple way to detect the revision number to strip from (and unfortunately it seems there's no such a thing as a “strip all local commits” in Mercurial).
<profile>
<id>release-cancel</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<execution>
<id>strip-hg</id>
<phase>initialize</phase>
<goals>
<goal>exec</goal>
</goals>
<inherited>false</inherited>
<configuration>
<!-- FIXME: would be better to strip local changes -->
<executable>rm</executable>
<arguments>
<argument>-rf</argument>
<argument>.hg</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
At last, you need to configure the release plugin so the “release” profile is correctly propagated:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<configuration>
<preparationGoals>clean install verify</preparationGoals>
<goals>clean install javadoc:javadoc assembly:assembly deploy</goals>
<arguments>-Prelease -DaltDeploymentRepository="${altDeploymentRepository}"</arguments>
</configuration>
</plugin>
Small tip: note that we are passing altDeploymentRepository - one could wonder why, as it would be automatically re-defined by the forked Maven invocation. The reason is that its definition is relative to the current working directory (e.g. target/something) so the re-definition in the forked Maven would set it to target/checkout/target/something.
My Hudson job is now configured with a sequence of two Maven invocations:
mvn -B -Prelease clean install release:clean release:prepare release:perform
mvn -N -P${RELEASE_MODE} initialize
The Hudson job is configured with a parameter named RELEASE_MODE which is used to choose whether you want to perform a true release or a test one.

I've done also a couple more refinements which prove useful in a Hudson environment, but it's a matter of another post.
You can see the real-world Hudson configuration here (if the link is broken, try to remove the /hudson part); you can also download the related projects sources.
Related Topics >>
Blog Links >>
- Login or register to post comments
- Printer-friendly version
- fabriziogiudici's blog
- 2546 reads





