Coder Thoughts Piotr Mionskowski's thoughts on software, technology, architecture, design patterns and programming in general.

Piotr Mionskowski

How to publish a library to a Maven repository with the maven-publish plugin

06 February 2018 maven, jcenter, jvm, gradle, and kotlin

A seasoned developer now and then creates a piece of code that he or she would like to reuse in a different project. When such time comes it is useful to know how to publish a library so that it can easily be incorporated into a different project. In this post I will describe how to publish a Kotlin library to JCenter with maven-publish and com.jfrog.bintray Gradle plugins.

publish

Gradle Maven plugins

The first step is to apply Maven plugin. The plugin adds support for deploying artifacts to Maven repositories. Note that in case of multi-project build e.g. ShouldKO the Maven plugin should be applied to every project that defines some artifact to be published. You can use allprojects to get rid of duplication e.g.:

allprojects {
    repositories {
        jcenter()
    }

    apply plugin: 'kotlin'
    apply plugin: 'maven'

    group "pl.miensol.shouldko"
}

For the com.jfrog.bintray plugin, used later on, to work nicely with Maven artifacts we need to apply additional Gradle plugin. This additional piece is the maven-publish plugin which provides ability to publish artifacts in Maven format. All we need to do is to apply plugin: 'maven-publish' in the main project.

Define Maven publishing

The com.jfrog.bintray plugin relies on properly defined Maven Publications. The Gradle DSL allows us to define them easily basing on project properties e.g.

publishing {
    publications {
        hamcrest(MavenPublication) {
            def project = project(':hamcrest')
            from project.components.java
            artifact project.sourcesJar { // not required, includes sourcesJar with correct classifer
                classifier "sources"
            }
            groupId group
            artifactId project.name
            version project.version
        }

        core(MavenPublication) {
            def project = project(':core')
            from project.components.java
            artifact project.sourcesJar {
                classifier "sources"
            }
            groupId group
            artifactId project.name
            version project.version
        }
    }
}

The above Maven Publications include sources artifact. Publishing additional classifiers for artifacts is important since it allows for IDE to show a documentation popup or debug through the library source code. However, one needs to define it first as it is not included by default when applying java or kotlin plugins to a Gradle project. This is easily done as follows:

allprojects {
    task sourcesJar(type: Jar, dependsOn: classes) {
        from sourceSets.main.allSource
    }
}

Project versioning

As you saw above, we have used project.version to indicate a version to MavenPublication. There are multiple strategies to version software but the Semantic Versioning scheme is widely accepted as a standard when it comes to libraries. If you wish to use it then there are plugins available for Gradle to simplify the mundane tasks of maintaining pre-release and patch versions. I like the set of plugin from ajoberstar that provide an opinionated way to version your project based on git tags. Applying them is easy:

plugins {
    id "org.ajoberstar.grgit" version "1.7.2"
    id "org.ajoberstar.release-opinion" version "1.7.2"
}

Now when you issue e.g. gradle build the plugin will infer a next version based on your git repository state:

> Configure project : 
Inferred project: shouldko, version: 0.1.5-dev.0.uncommitted+4f71d34

Bintray upload

Finally, when we are ready to upload our library and make it available for everyone we need to set up a Bintray account. Once we have it, on the profile page we can access API key required to configure the Bintray Gradle plugin.

bintray {
    user = project.hasProperty('bintrayUser') ? project.property('bintrayUser') : System.getenv('BINTRAY_USER')
    key = project.hasProperty('bintrayApiKey') ? project.property('bintrayApiKey') : System.getenv('BINTRAY_API_KEY')
    publications = ['core', 'hamcrest']
    pkg {
        repo = 'maven'
        name = 'shouldko'
        desc = 'Adds source line to tests assertion messages'
        userOrg = 'brightinventions'
        licenses = ['MIT']
        vcsUrl = 'https://github.com/bright/shouldko.git'
        labels = ['tests', 'hamcrest', 'junit']
    }
}

The Bintray API key should be kept private and by no means included in the source code repository. We can configure user and key by looking at project properties and if not available using environment variables. This way there is no need to expose them publicly.

gradle build bintrayUpload -PbintrayUser=<apiUser> -PbintrayApiKey=<apikKey>

The repo is the name of the Bintray repository. You can use the same Bintray repository to host multiple projects.

The Bintray plugin is very taciturn thus I like to add some log message to see when the bintrayUpload completes:

afterEvaluate {
    tasks.bintrayUpload.doLast {
        logger.lifecycle("Uploaded artifacts to bintray at version $version")
    }
}

Travis build

Every project should have at least some form of continuous integration. For open source software there are at least couple of free build servers available. Travis is probably the most popular one. For gradle project Travis will by default call build. If you would like to upload the build artifacts to Bintray whenever successful build completes you need to add a line to script section of the .travis.yml like so:

script:
  - ./gradlew build
  - ./gradlew bintrayUpload

Obviously the Bintray credentials need to be configured as well which can be done through a project configuration page:

TravisCI environment configuration

Now, the Gradle git plugin will create a development version and publish it to Bintray on every Travis build.

Tag to release

Whenever you want to release a new version of the library you now can simply tag a particular version e.g.

git tag 0.1.4
git push origin 0.1.4

After a local or continuos integration build completes you should see a new version in the Bintray web application. From there you need to publish the version.

Use the new library

Once a version is published, you can consume it from a maven or gradle project easily. Until you link your package to JCenter, you need to inform your build system about a new maven repository location e.g.:

repositories {
    jcenter()
    maven { url 'https://dl.bintray.com/brightinventions/maven' }
}

Note that brightinventions is the organization user name and maven is the repository name mentioned above. You are now, finally able to consume your library 🎉:

compile 'pl.miensol.shouldko:hamcrest:0.1.4'

Enjoy!

This article is crossposted with my company's blog.