Using Travis CI to deploy to Maven repositories and GitHub Releases

This post outlines the steps needed to simultaneously deploy to Maven repositories and to GitHub Releases. Every time a tagged commit is pushed, a Travis CI build will be triggered automatically and start the release process. This blog post uses Sonatype Nexus as an example for a Maven repository manager.

Preparing GitHub Releases

Sergey Mashkov has written a Maven plugin that allows us to create a new release on our project’s releases page and upload our build artifacts to a release. The following sections describe how we need to configure our pom.xml in order to use this plugin.

The plugin uses the scm settings to find out for which project the new release should be created. Right now, there’s still a bug in the plugin which restricts the format for our git URIs. The only working format is scm:git:git@github.com:.... Neither scm:git:https://github.com/...nor scm:git:ssh://git@github.com/...work, but a pull request has been created that adds this functionality.

So add an scmsection to your pom that looks like this:

<scm>
    <url>https://github.com/example/project</url>
    <connection>scm:git:git@github.com:example/project.git</connection>
    <developerConnection>scm:git:git@github.com:example/project.git</developerConnection>
</scm>

The second step is to include the plugin in our pom. Right now the plugin is only available from bintray.com so we need to add it as a plugin repository. We only want to create a new release on GitHub when we are building a new release. Hence we configure the plugin in an extra release profile section. This leads to the plugin being executed only if Maven is started with -Preleaseand only if the deploy goal is invoked. For more information on how to configure the plugin options please refer to its documentation and the pitfalls below.

<profiles>
    ...
    <profile>
        <id>release</id>
        <pluginRepositories>
            <pluginRepository>
                <id>bintray-cy6ergn0m-maven</id>
                <name>bintray-plugins</name>
                <url>http://dl.bintray.com/cy6ergn0m/maven</url>
            </pluginRepository>
        </pluginRepositories>
        <build>
            <plugins>
                <plugin>
                    <groupId>cy.github</groupId>
                    <artifactId>github-release-plugin</artifactId>
                    <version>0.5.1</version>
                    <configuration>
                        <tagName>${project.version}</tagName>
                        <releaseTitle>${project.artifactId}-${project.version}</releaseTitle>
                        <serverId>github</serverId>
                    </configuration>
                    <executions>
                        <execution>
                            <goals>
                                <goal>gh-upload</goal>
                            </goals>
                            <phase>deploy</phase>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>

Preparing for Maven releases

If you don’t already have a repository where you want to deploy to, you need to create a release and a snapshot repository and add them to distributionManagement.You might also want to create a separate user that has access only to your target repositories. This user will be used to upload the releases.

<distributionManagement>
    <repository>
        <id>oss</id>
        <url>https://nexus.example.com/content/repositories/oss-releases</url>
    </repository>
    <snapshotRepository>
        <id>oss</id>
        <url>https://nexus.example.com/content/repositories/oss-snapshots</url>
    </snapshotRepository>
</distributionManagement>

Putting it all together

So far we have configured the GitHub release plugin to deploy our artifacts to the GitHub Releases page and setup Maven releases. Now it’s time to glue the parts together. In order to do this we have to create a settings.xmlfor use with Maven, a .travis.yml that manages our Travis CI builds and we have to configure some environment variables in Travis CI itself. Furthermore we need a small shell script that orchestrates our release.

Maven settings

Create a new settings.xmlfile in your repository, e.g in a .travis/directory. The content of this file should look like the following snippet. The <server>ids have to match the ids in <distributionManagement>and the <serverId>of the GitHub release plugin exactly. Do not use static credentials here! You don’t want everyone who stumbles upon your repository on GitHub to have write access to your Nexus/Artifactory and GitHub. We will use Travis CI’s capability to inject environment variables into builds; the environment variables will be configured soon.

<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>oss</id>
            <username>${env.NEXUS_USERNAME}</username>
            <password>${env.NEXUS_PASSWORD}</password>
        </server>
        <server>
            <id>github</id>
            <username>${env.GITHUB_USERNAME}</username>
            <password>${env.GITHUB_TOKEN}</password>
        </server>
    </servers>

</settings>

Release script

We need a small shell script that orchestrates our releases. This script sets the correct version, creates a release and uploads it to our Maven repository and to GitHub. To release the correct version theTRAVIS_TAG environment variable will be used. Travis CI uses this variable to inject the value of the git tag into the build.

#!/usr/bin/env bash

set -e

echo "Ensuring that pom  matches $TRAVIS_TAG"
./mvnw org.codehaus.mojo:versions-maven-plugin:2.5:set -DnewVersion=$TRAVIS_TAG

echo "Uploading to oss repo and GitHub"
./mvnw deploy --settings .travis/settings.xml -DskipTests=true --batch-mode --update-snapshots -Prelease

The script first sets the <version>in the pom exactly to our git tag’s value. So your tag always matches the version you want to release, e.g. 1.0or 1.5.1. The second part creates the release. We need to reference the Maven settings in our repository here so that Travis CI has access rights to the Maven repositories and GitHub Releases. The important part here is to activate thereleaseprofile. This tells Maven to not only create and upload a Maven release but also to create a new GitHub Release.

Name this script release.sh, put it inside the .travis/directory and make it executable (chmod +x).

Build configuration

Travis CI uses a file named .travis.ymlat the root of a GitHub repository. The snipped contains the necessary steps.

In a normal build we just want to execute a simple clean verify. The verifygoal will execute unit and integration tests. To make subsequent builds faster, we want to cache the m2 repositories during builds.

The most important part is the deploy section. Here we configure Travis CI to run the release.shscript if and only if a tag has been pushed (tags: true) on the repo example/project.

sudo: false
language: java
jdk:
  - oraclejdk8
script: ./mvnw clean verify
cache:
  directories:
    - $HOME/.m2
deploy:
   provider: script
   script: .travis/release.sh
   skip_cleanup: true
   on:
    repo: example/project
    tags: true
    jdk: oraclejdk8

Configuring Travis CI itself

Now we need to teach Travis CI the values of the environment variables used in our Maven settings. To do this navigate to the Travis CI settings for your project:

travis environment

Add NEXUS_USERand NEXUS_PASSWORDfor your newly created user. You also need to configure GITHUB_USERNAMEand GITHUB_TOKEN. But even though the field is called password, what your really want to configure here is your GitHub API token. Otherwise, the GitHub release plugin will not be able to upload artifacts. You can obtain your personal access token here. The token needs access to the repo scope.

And that’s it. Now you can simply create a new tag in your repository (git tag -a 1.1 -m "Release 1.1"), push it to GitHub and Travis CI will trigger the release process. Happy releasing!

Pitfalls

Some advice so that you do not encounter the same problems we did:

  • Do not forget to make release.shexecutable otherwise the Travis CI build will fail with a rather unhelpful message: Script failed with status 127.
  • The serverIdin the GitHub release plugin’s configuration refers to a <server>entry in Maven’s settings.xml.
  • Do not be tempted to use your GitHub account password to configure the server settings for the GitHub release plugin. Even though the field is called password it’s really an API token that is needed here.
  • Do not change the value of <tagName>when configuring the GitHub release plugin. Using anything different from the project’s version (i.e. the git tag’s value), will lead to recursive release builds. This happens because the release plugin will create and push a new tag if it does not already exist. Pushing a tag invokes another Travis CI build which will create a new tag and so on.