A Fully Automated Open Source Release Chain with Gradle and Travis CI

  • December 29, 2017
Table Of Contents

“Release early, release often”. This philosophy should be a goal for every software project. Users can only give quality feedback when they have early access to a software release. And they can only give feedback to new features and fixes if they have access to the latest version. Releasing often is a major pain when the release process is not automated. This article is a guide to a fully automated release chain that is able to publish snapshots and releases from a Github repository using Gradle, Bintray and Travis CI.

Example Code

This article is accompanied by a working code example on GitHub.

The Release Chain

The following image shows the release chain we’re going to build.

Releasing snapshots and releases from different branches

In a simplified Git Flow fashion, we have two branches in our Git repository:

The master branch contains the current state of work. Here, all features and bugfixes currently being developed come together.

The release branch contains only those versions of the codebase that are to be released.

Additionally, there may be optional feature branches in which some features are developed in isolation.

Here’s what we’re going to automate:

Each time someone pushes a commit to the master branch (or merges a feature branch into master), a snapshot will be published by our CI pipeline so that users can test the current state of work at any time.

Each time someone pushes a commit to the release branch, a stable release will be published by our CI pipeline so that users can work with the stable version.

Naturally, a snapshot or release will only be published if all tests have run successfully.

Prerequisites

To create an automated release chain as described in this article, we need to create a Bintray account and set up a Gradle build as described in my previous articles:

Once the build.gradle file is set up as described in those articles, we’re ready to configure Travis CI to do the publishing work for us automatically.

Configure Travis CI

To enable Travis CI, we need to create an account on https://about.travis-ci.com and link it to our Github account.

Activate

Once logged into the Travis account, we activate Travis CI for the repository we want to publish snapshots and releases for:

Activate in Travis CI

Set Environment Variables

In the settings of the repository on Travis CI, we now set the environment variables BINTRAY_KEY and BINTRAY_USER to our Bintray credentials:

Environment Variables

The .travis.yml File

Next, we need to put a file called .travis.yml into the codebase and push it to Github. This file contains all configuration for the CI build.

Let’s look at the contents of this file.

Basic Setup

language: java
install: true

sudo: false
addons:
  apt:
    packages:
      - oracle-java8-installer

before_install:
  - chmod +x gradlew

With the language property, we tell Travis that it’s a Java project.

install: true tells Travis that we want to take care of running the Gradle build ourselves (otherwise Travis runs gradlew assemble before each build stage).

We tell Travis to install the oracle-java8-installer that in turn takes care of installing the most current Java 8 JDK.

The last line makes the gradlew file executable so that Travis can run it.

Declare Build Stages

In the next section of .travis.yml, we’re making use of Travis CI’s build stages feature to divide our build into several steps.

stages:
  - name: build
  - name: snapshot
    if: branch = master
  - name: release
    if: branch = release

The build stage is going to run the gradle build and check if everything compiles and all tests are running.

The snapshot stage is responsible for publishing a snapshot release and thus should only run on the master branch.

The release stage is responsible for publishing a stable release and thus should only run on the release branch.

Define Build Jobs

The last thing left to do now is to configure the actual jobs that should run within the build stages we declared above:

jobs:
  include:
    - stage: build
      script: ./gradlew build
    - stage: snapshot
      script: ./gradlew artifactoryPublish -x test -Dsnapshot=true -Dbintray.user=$BINTRAY_USER -Dbintray.key=$BINTRAY_KEY -Dbuild.number=$TRAVIS_BUILD_NUMBER
    - stage: release
      script: ./gradlew bintrayUpload -x test -Dbintray.user=$BINTRAY_USER -Dbintray.key=$BINTRAY_KEY -Dbuild.number=$TRAVIS_BUILD_NUMBER

In the build stage we’re simply running our Gradle build. If this stage fails, the other stages will not be started at all.

In the snapshot stage, we’re running the artifactoryPublish task that takes care of publishing the current build as a snapshot to oss.jfrog.org. The details of the Gradle configuration are explained here. We pass on the environment variables BINTRAY_USER, BINTRAY_KEY and TRAVIS_BUILD_NUMBER, so that the Gradle script can make use of them.

In the release stage, we’re running the bintrayUpload task that takes care of publishing a stable release to Bintray, again passing in the necessary environment variables. The details of the Gradle configuration are explained here.

What now?

And that’s it. All in all this is a pretty straightforward way to publish open source Java projects with Gradle, Bintray and Travis CI.

You can tailor the process to your project as needed. Especially in projects maintaining multiple versions at the same time you might have to move toward a more complex branching strategy more like the original Git Flow. In this case, you would have to add more branches from which snapshots and releases should be published to the Travis configuration.

Written By:

Tom Hombergs

Written By:

Tom Hombergs

As a professional software engineer, consultant, architect, general problem solver, I've been practicing the software craft for more than fifteen years and I'm still learning something new every day. I love sharing the things I learned, so you (and future me) can get a head start. That's why I founded reflectoring.io.

Recent Posts

Guide to JUnit 5 Functional Interfaces

In this article, we will get familiar with JUnit 5 functional interfaces. JUnit 5 significantly advanced from its predecessors. Features like functional interfaces can greatly simplify our work once we grasp their functionality.

Read more

Getting Started with Spring Security and JWT

Spring Security provides a comprehensive set of security features for Java applications, covering authentication, authorization, session management, and protection against common security threats such as CSRF (Cross-Site Request Forgery).

Read more

Creating and Publishing an NPM Package with Automated Versioning and Deployment

In this step-by-step guide, we’ll create, publish, and manage an NPM package using TypeScript for better code readability and scalability. We’ll write test cases with Jest and automate our NPM package versioning and publishing process using Changesets and GitHub Actions.

Read more