dracoblue.net

Uploading Snapshots and Releases to Maven Central with Travis

When I was developing on the http-response-headers open source library for spring boot, I was curious how difficult it would be to make this available at maven central. And of course: everything should work automatically with travis ;).

To make your jars available at http://search.maven.org/, you can submit your open source project to "Sonatype OSSRH".

Basically you have 3 steps (OSSRH Guide):

  1. Create a Jira Account at Sonatype
  2. Create a Project Ticket
  3. Deploy to https://oss.sonatype.org/content/repositories/snapshots or https://oss.sonatype.org/service/local/staging/deploy/maven2/. Therefor you need to:
    • Modify your pom.xml
    • Add build plugins for binaries, javadoc and sources jar files and gpg signing
    • Create a gpg key
    • Sign your builds
    • Integrate this with travis secured environment variables

The steps 1 and 2 took me some minutes and the response of the sonatype staff came a workday later. Nice.

Since I wanted to automate step 3, the following lines are more like a reminder for myself how I got this working. You can follow the following steps to set this up for your self, too.

Add general information to your pom.xml

You need to add the following parts to your pom.xml. There are detailed explanations of these configuration values at sonatype.org.

This general information needs to be available (e.g. a missing description tag will make deployment to maven central impossible):

<groupId>org.example.spring</groupId>
<artifactId>my-library</artifactId>
<packaging>jar</packaging>
<version>0.1.0-SNAPSHOT</version>
<name>my-library</name>
<url>https://example.org</url>
<description>This small java library is really nice if you want to do something.</description>

Also the developer and license information is necessary:

<developers>
  <developer>
    <id>jd</id>
    <name>Joe Doe</name>
    <email>[email protected]</email>
    <url>https://example.org</url>
  </developer>
</developers>

<licenses>
  <license>
    <name>MIT</name>
    <url>https://opensource.org/licenses/MIT</url>
    <distribution>repo</distribution>
  </license>
</licenses>

Add distributionManagement for ossrh to your pom.xml

The following two entries are given to you, as soon as you finish step 1+2 at sonatype's jira:

<distributionManagement>
  <snapshotRepository>
    <id>ossrh</id>
    <url>https://oss.sonatype.org/content/repositories/snapshots</url>
  </snapshotRepository>
  <repository>
    <id>ossrh</id>
    <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
  </repository>
</distributionManagement>

Once set up, the maven deploy task will know the target for uploads.

Add maven build plugins

For a successful upload to maven central you need your jar, a java doc jar, a java sources jar and all of those need to be signed with a gpg key. The following configuration in your pom.xml will take care of those steps:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.3</version>
        </plugin>
        <plugin>
            <groupId>org.sonatype.plugins</groupId>
            <artifactId>nexus-staging-maven-plugin</artifactId>
            <version>1.6.3</version>
            <extensions>true</extensions>
            <configuration>
                <serverId>ossrh</serverId>
                <nexusUrl>https://oss.sonatype.org/</nexusUrl>
                <autoReleaseAfterClose>true</autoReleaseAfterClose>
            </configuration>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-source-plugin</artifactId>
          <version>2.2.1</version>
          <executions>
            <execution>
              <id>attach-sources</id>
              <goals>
                <goal>jar-no-fork</goal>
              </goals>
            </execution>
          </executions>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-javadoc-plugin</artifactId>
          <version>2.9.1</version>
          <executions>
            <execution>
              <id>attach-javadocs</id>
              <goals>
                <goal>jar</goal>
              </goals>
            </execution>
          </executions>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-gpg-plugin</artifactId>
          <version>1.5</version>
          <executions>
            <execution>
              <id>sign-artifacts</id>
              <phase>verify</phase>
              <goals>
                <goal>sign</goal>
              </goals>
            </execution>
          </executions>
        </plugin>
    </plugins>
</build>

Generate gpg key

To sign your packages, you have to add a gpg key. To create a gpg key follow the steps at gpg guide at sonatype.org.

$ gpg --gen-key
Please select what kind of key you want:
   (1) RSA and RSA (default)
   (2) DSA and Elgamal
   (3) DSA (sign only)
   (4) RSA (sign only)
Your selection? 1
What keysize do you want? (2048)
Requested keysize is 2048 bits
Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0)
Key does not expire at all
Is this correct? (y/N) y

Real name: Hans
Name must be at least 5 characters long
Real name: Joe Doe
Email address: [email protected]
Comment: JD
You selected this USER-ID:
    "Joe Doe (JD) <[email protected]>"

Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O
You need a Passphrase to protect your secret key.

gpg: key 750E67A6 marked as ultimately trusted
public and secret key created and signed.

gpg: checking the trustdb
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
gpg: depth: 0  valid:   2  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 2u
pub   2048R/750E67A6 2016-01-31
      Key fingerprint = 6E70 DA17 1F4C 7DB8 586D  A100 6B3D 388D 750E 67A6
uid                  Joe Doe (JD) <[email protected]>
sub   2048R/533B368A 2016-01-31

This process will ask your for a passphrase, which is necessary to configure your settings.xml for the build.

Create a settings.xml for the travis build

The following settings.xml should be available in your git repository at .travis/settings.xml:

<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>
            <!-- Maven Central Deployment -->
            <id>ossrh</id>
            <username>${env.SONATYPE_USERNAME}</username>
            <password>${env.SONATYPE_PASSWORD}</password>
        </server>
    </servers>
    <profiles>
      <profile>
        <id>ossrh</id>
        <activation>
          <activeByDefault>true</activeByDefault>
        </activation>
        <properties>
          <gpg.executable>${env.GPG_EXECUTABLE}</gpg.executable>
          <gpg.passphrase>${env.GPG_PASSPHRASE}</gpg.passphrase>
        </properties>
      </profile>
    </profiles>
</settings>

As you can see we'll use environment-Variables to configure passphrase and sonatype password, so you don't need to commit those to your source repository (which would be a very stupid thing to do!).

Add the secrets to your Travis Settings Page

If your project is already on travis, you need to add the environment variables on the settings page. For the http-response-headers project under DracoBlue namespace, the url looks like this: https://travis-ci.org/DracoBlue/http-response-headers/settings.

Fill SONATYPE_USERNAME and SONATYPE_PASSWORD with your jira credentials and GPG_PASSPHRASE with your gpg passphrase. The GPG_EXECUTABLE should be filled with gpg.

Create base64 of public / secret key

Since we don't want to commit the secret keys for gpg signing, we want to add them as environment variables, too.

Therefor we need to generate the key secrets as base64 encoded version with:

$ gpg -a --export-secret-keys [email protected] | base64

and store it as the environment variable GPG_SECRET_KEYS in travis.

Now generate ownertrust with:

$ gpg --export-ownertrust | base64

and store it as the environment variable GPG_OWNERTRUST in travis.

Add .travis.yml

Your .travis.yml file could look like this:

language: java
jdk:
  - oraclejdk8
  - oraclejdk7
  - openjdk7
install:
  - mvn --settings .travis/settings.xml install -DskipTests=true -Dgpg.skip -Dmaven.javadoc.skip=true -B -V
before_install:
  - if [ ! -z "$GPG_SECRET_KEYS" ]; then echo $GPG_SECRET_KEYS | base64 --decode | $GPG_EXECUTABLE --import; fi
  - if [ ! -z "$GPG_OWNERTRUST" ]; then echo $GPG_OWNERTRUST | base64 --decode | $GPG_EXECUTABLE --import-ownertrust; fi
deploy:
  -
    provider: script
    script: .travis/deploy.sh
    skip_cleanup: true
    on:
      repo: ExampleOrg/my-library
      branch: master
      jdk: oraclejdk8
  -
    provider: script
    script: .travis/deploy.sh
    skip_cleanup: true
    on:
      repo: ExampleOrg/my-library
      tags: true
      jdk: oraclejdk8

It's very important to override the install instruction for mvn with --settings .travis/settings.xml, otherwise your settings.xml will be ignored and the configuration would be useless.

The idea of this setup is:

  1. Import the GPG Secret Keys and Ownertrust at the beginning
  2. Each master commit, should deploy to snapshots
  3. Each tagged commit, should deploy to releases

I usually use the repo condition, to avoid that forks of my repository accidently try to publish things to maven.

Add .travis/deploy.sh

Since it's easier to read if you have all deploy steps in a seperate file, I created a .travis/deploy.sh for this:

if [ ! -z "$TRAVIS_TAG" ]
then
    echo "on a tag -> set pom.xml <version> to $TRAVIS_TAG"
    mvn --settings .travis/settings.xml org.codehaus.mojo:versions-maven-plugin:2.1:set -DnewVersion=$TRAVIS_TAG 1>/dev/null 2>/dev/null
else
    echo "not on a tag -> keep snapshot version in pom.xml"
fi

mvn clean deploy --settings .travis/settings.xml -DskipTests=true -B -U

This snippet sets the version in the pom file to the tag version (if it's a git tag). Afterwards a deploy will be triggered.

That's why in my pom.xml there is always a -SNAPSHOT qualifier and no final MAJOR.MINOR.PATCH version, yet. This part takes care of creating a SemVer version of the pom.xml.

Make sure that the deploy.sh file is executable by running:

$ chmod +x .travis/deploy.sh

Result

If you set this up correctly, your setup will work like this:

  1. In your pom.xml you have <version>0.1.0-SNAPSHOT</version>
  2. If you push a commit to master of your repository, a new snapshot will be uploaded and is available as 0.1.0-SNAPSHOT.
  3. If you git tag 0.1.0 and git push --tags afterwards, you will have 0.1.0 of your library available at maven central.

You can see this in action at my library http-response-headers.

What do you think about this process? Is there an easier or different way to handle this?

Changelog

  • 2016/11/28: added info about chmod +x .travis/deploy.sh to ensure that the deployment script is executable
  • 2016/11/28: added skip_cleanup: true
  • 2016/11/28: added -Dgpg.skip to the mvn install command, since signing is not necessary here, yet.
  • 2016/11/28: added if [ ! -z "$GPG_SECRET_KEYS" ]; and if [ ! -z "$GPG_OWNERTRUST" ]; to before_install commands to make PRs without the GPG Environment Variables possible
In gradle, java, maven, nexus, open source, travis by
@ 31 Jan 2016, Comments at Reddit & Hackernews

Give something back

Were my blog posts useful to you? If you want to give back, support one of these charities, too!

Report hate in social media Campact e.V. With our technology and your help, we protect the oceans from plastic waste. Gesellschaft fur Freiheitsrechte e. V. The civil eye in the mediterranean

Recent Dev-Articles

Read recently

Recent Files

About