Deploying to Maven Repositories from Tavis CI

Those that follow my work must probably know I work on an open source project called Roboconf.
This project is mainly developed in Java and is hosted by GitHub.

We recently got Maven repositories hosted by Sonatype.
This is really useful, since until now, Roboconf users had to compile the project themselves before using it. And we have not yet released anything. With all the changes we are doing, it is really hard to speak of stable versions. At least, snpashot versions may help to gain some time. Besides, we have crossed-repositories dependencies. As an example, our Maven plug-in needs a Maven module from the platform. So, we need a snapshot repository to share and reuse some built artifacts.

So, in addition to use these repositories to perform clean releases, we also wanted to use the provided snapshot repository for continuous integration. Our CI system is Travis. And what we wanted to do is to deploy our artifacts to the snapshot repository when a build succeeds.

There are quite a lot of articles on the web about this.
This one helped me a lot, as well as this one. Obviously, you must have an account and a Maven repository on Sonatype.

One thing that was difficult (and not really documented anywhere), is how to deal with the build matrix. In our travis.yml file, we ask Travis to build our project against 3 different JDK (Open JDK 6, Open JDK 7 and Oracle JDK 7). But given the way Travis works, we do not want it to upload our artifacts 3 times per build. And since there is no after_build step in the Travis life cycle, I had to find a trick.

So, here is my travis.yml file.

language: java

# Test several JDK
jdk:
  - oraclejdk7
  - openjdk6
  - openjdk7

# Remove all the bla-bla...
script:
  - mvn clean install -q

# Actions to perform after the build.
after_success:
  - ./after_success.sh

What is interesting is the content of the after_success script.
Here is its content.

#!/bin/bash

# Get the Java version.
# Java 1.5 will give 15.
# Java 1.6 will give 16.
# Java 1.7 will give 17.
VER=`java -version 2>&1 | sed 's/java version "\(.*\)\.\(.*\)\..*"/\1\2/; 1q'`

# We build for several JDKs on Travis.
# Some actions, like analyzing the code (Coveralls) and uploading
# artifacts on a Maven repository, should only be made for one version.

# If the version is 1.6, then perform the following actions.
# 1. Copy Maven settings on the VM.
# 2. Notify Coveralls.
# 3. Upload artifacts to Sonatype.
# 4. Use -q option to only display Maven errors and warnings.
# 5. Use --settings to force the usage of our "settings.xml" file.
# 6. Enable the profile that generates the javadoc and the sources archives.

if [ $VER == "16" ]; then
	wget http://roboconf.net/resources/build/settings.xml
	mvn clean deploy cobertura:cobertura org.eluder.coveralls:coveralls-maven-plugin:cobertura -q --settings settings.xml -P jdoc-and-sources
else
	echo "No action to undertake (not a JDK 6)."
fi

Since we do not want to perform heavy tasks 3 times per build, I managed to filter these builds in my script. This way, complex tasks are performed only once. In this case, evaluating code coverage and Maven deployment is only done when we build on a JDK 6. For this, I get the Java version in my bash script and makes the right checks. Heavy tasks are performed if and only if the right conditions are met.

The key thing to be able to deploy on Maven is the use of a settings.xml file that contains credentials of the release server. Since I have other related repositories that need the same Maven settings, I hosted this settings.xml file on a web server. If you do not need to reuse it, you could simply put it in our source repository. Anyway, in my case and before deploying the artifacts, Travis downloads this file and uses it for the deployment.

Here is the content of this file.

<?xml version="1.0" encoding="UTF-8"?>
<settings 
		xmlns="http://maven.apache.org/SETTINGS/1.0.0"
		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
		
	<servers>
		<server>
			<id>sonatype-snapshots</id>
			<username>${env.USER_ID}</username>
			<password>${env.USER_PWD}</password>
		</server>
	</servers>
	
</settings>

This file gives the credentials (user and password) to use to connect to my Sonatype account. Since I do not want to put their values in clear, I defined them as environment variables on Travis. I simply visited the page of my project on Travis and went into the settings menu. There is an option to define environment variables per repository. env.USER_ID refers to the environment variable called USER_ID. I defined its value in Travis. Same thing for the password.

Now, how does Maven know to which repository deploy?
Well, that’s easy. In my project, the parent POM contains a distributionManagement section. Here is what it looks like.

<distributionManagement>
	<snapshotRepository>
		<id>sonatype-snapshots</id>
		<name>Sonatype Nexus snapshot repository</name>
		<url>https://oss.sonatype.org/content/repositories/snapshots</url>
	</snapshotRepository>

	<repository>
		<id>sonatype-release-staging</id>
		<name>Sonatype Nexus release repository</name>
		<url>https://oss.sonatype.org/service/local/staging/deploy/maven2</url>
	</repository>
</distributionManagement>

There is one repository for releases.
And there is another one for snapshots.

Notice that the ID of my snapshot repository matches in both the parent POM and my settings.xml. The fact that Travis must upload to the snapshot repository is defined by the project version. If it contains SNAPSHOT, Maven will automatically select it. And in continuous integration, we decided we were only building snpashot versions. Releases are performed manually, independently of Travis.

That’s it. 😉
As you can see, there is no clean solution to handle the build matrix in Travis with Maven deployment. But the solution I described in this post works and is quite simple to manage and maintain. So, maybe it will help others.


About this entry