Publishing a Kotlin library to your Bintray repo using Gradle Kotlin DSL and Bintray Gradle Plugin

publishing-house

This post was originally posted on Medium

There are many benefits to working on Open Source Software, both for companies and individuals. Sharing your work with others just opens so much more possibilities as compared to working in a “closed box”. By sharing your code you get numerous things literally for free, things like code reviews and contributions to your own projects, a better understanding of the technology you work with by contributing to other OSS projects, to networking and getting to know your “clients”.

While working on OSS within the company, chances are, you probably have all the “infra” already set up - things like where the source code is stored, or where to publish the packages. But if you’re working on a “personal” OSS project you will most likely come to a point where you will want to publish your artifacts to a publicly accessible repository (A Central Maven repository, for example, if you’re working on a java-based project.) For someone who has never published an artifact to be available to others the process can be somewhat unclear, and scarce documentation makes things even more daunting. If on top of that you’re using Gradle Kotlin DSL build scripts then chances are documentation could be even sparser. Even today some plugin maintainers don’t always “consider” kotlin dsl support simply because they don’t use it themselves, and leave it up to users to “figure it out”. Even though I have to admit the documentation improves constantly and is much better than when Gradle Kotlin DSL was first introduced in 2017.

Until recently I have been working in software test automation, and have been coding for a living for some time now. Software Testing and Quality Assurance is one of my passions at work and naturally my “OSS pet projects” are also related to this area. Some time ago I’ve gone through the process of publishing my first OSS project, a Kotlin library that generates “fake” data for tests and data anonymization: kotlin-faker. The process wasn’t entirely clear at first, but when I understood the basics of publishing the artifacts to a publicly available repository things got quite simple.

Where to publish?

where-to-publish

At first, I was thinking to publish everything directly to Maven Central. This seems like a natural choice since this is one of the most commonly used repos out there and is directly accessible from most Maven and Gradle builds without the need to specify any additional information such as the url to your custom repo.

However, there are some caveats to doing this, and I would generally recommend using a custom repository, for example Bintray (which is what I am using myself and will be using as an example in this article) unless you’re absolutely sure what you’re doing.

Why is Bintray preferable to Maven Central?

First, with Maven Central you have to be sure about your setup. You can play around with your publishing setup and always undo things in Bintray, but not in Maven Central. Second is that you have complete control over your files and if you’re not sure about the quality of your library and want to test things — this would be a good place to do this. In Maven Central you sort of loose all control after you publish something there. Last, Maven Central has more strict requirements when it comes to publishing artifacts.

Note: while you do have the ability to modify your files in your own Bintray repo, you should be careful while doing so as this might break things for people who are already using these artifacts.

After publishing to Bintray and verifying that everything is as you want it to be you can then sync your artifacts JCenter and to Maven Central directly from the UI, through the bintray plugin configuration or even the APIs. You can read more about it in their blogpost.

The Setup

Bintray Prerequisites

These are pretty self explanatory and easy to find, so I won’t go into too many details here and will just list what you will need to do.

Gradle build file configuration

First we need to add the maven-publish and bintray plugins to the gradle.build.kts build script file:

plugins {
    `maven-publish`
    id("com.jfrog.bintray") version "1.8.4"
}

Configuring maven-publish plugin

After adding maven-publush plugin you should have a publishing extension function available, which is used to configure your publication:

publishing {
    publications {
        create<MavenPublication>(publicationName) {
            groupId = artifactGroup
            artifactId = artifactName
            version = artifactVersion
            from(components["java"])

            pom.withXml {
                asNode().apply {
                    appendNode("description", pomDesc)
                    appendNode("name", rootProject.name)
                    appendNode("url", pomUrl)
                    appendNode("licenses").appendNode("license").apply {
                        appendNode("name", pomLicenseName)
                        appendNode("url", pomLicenseUrl)
                        appendNode("distribution", pomLicenseDist)
                    }
                    appendNode("developers").appendNode("developer").apply {
                        appendNode("id", pomDeveloperId)
                        appendNode("name", pomDeveloperName)
                    }
                    appendNode("scm").apply {
                        appendNode("url", pomScmUrl)
                    }
                }
            }
        }
    }
}

Notice the pom.withXml part. We need to set certain metadata before publishing a library. Maven Central, for example, has some requirements on what metadata needs to be present in the pom.

This can be done as seen in code above. Another way is to utilize the DSL that Maven Publish Plugin provides for this purpose:

pom {
    name = 'My Library'
    description = 'A concise description of my library'
    url = 'http://www.example.com/library'
    properties = [myProp: "value", "prop.with.dots": "anotherValue"]
    licenses {
        license {
            name = 'The Apache License, Version 2.0'
            url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
        }
    }
    developers {
        developer {
            id = 'johnd'
            name = 'John Doe'
            email = 'john.doe@example.com'
        }
    }
    scm {
        connection = 'scm:git:git://example.com/my-library.git'
        developerConnection = 'scm:git:ssh://example.com/my-library.git'
        url = 'http://example.com/my-library/'
    }
}

Note: it is not necessary to explicitly specify the pom metadata - it will be generated by the plugin automatically, therefore the pom {} configuration can be omitted altogether unless you want to set/modify some things in the generated pom explicitly.

Usually you want to publish javadocs and sources as well. To create sources.jar we need to create a task that will generate a jar:

val sourcesJar by tasks.creating(Jar::class) {
    archiveClassifier.set("sources")
    from(sourceSets.getByName("main").allSource)
}

And then add it to the publishing configuration:

publishing {
    publications {
        create<MavenPublication>(publicationName) {
            artifact(sourcesJar)
        }
    }
}

To generate kdocs (javadoc in Kotlin world) you should use the Kotin/dokka plugin.

For more info on using Maven Publish Plugin also see the official userguide page.

Configuring Bintray plugin

The Bintray plugin takes care of actually uploading artifacts to your repository.

A basic setup would look something like this:

bintray {
    user = project.findProperty("bintrayUser").toString()
    key = project.findProperty("bintrayKey").toString()

    publish = true // Set to false if you don't want to automatically publish the release after uploading the artifacts

    setPublications(publicationName) // We're using the publication created by the `maven-publish` plugin

    pkg.apply {
        repo = "maven" // This is the name of the repo that you have previously created in Bintray
        name = artifactName
        userOrg = user
        githubRepo = githubRepo
        vcsUrl = pomScmUrl
        description = "Some description can go here, but is optional"
        setLabels("tag1", "tag2", "tag3")
        setLicenses("MIT")
        desc = description
        websiteUrl = pomUrl
        issueTrackerUrl = pomIssueUrl
        githubReleaseNotesFile = githubReadme

        version.apply {
            name = artifactVersion
            desc = pomDesc
            released = Date().toString()
            vcsTag = artifactVersion
        }
    }
}

A few things to keep in mind:

The rest is pretty much self explanatory, but I’ve added a few comments in the code just in case.

Publishing to your Bintray repo

When you’re ready to upload a new release just use the command: $ ./gradlew bintrayUpload -PbintrayUser="my_bintray_username" -PbintrayKey="my_bintray_api_key"

This can also be set up with CI, where bintrayUser and bintrayKey properties will be stored as “secrets”.

Downloading artifacts from your repository

In order to be able to use the published package as a dependency in a build file we first need to add our repository to the list of repositories. In gradle it would look something like this:

repositories {
    maven {
        setUrl("https://dl.bintray.com/serpro69/maven/")
    }
}

After that the library can be added as a usual dependency:

dependencies {
    implementation '$groupId:$artifactId:$version'
}

Alternatively you can also sync packages from your bintray repo to JCenter and Maven Central.

Retrospective

All in all this wasn’t actually that hard. I needed to collect some pieces of missing information here and there and understand what’s happening when you publish an artifact and what are the prerequisites for that. After that the process looks quite straightforward.

You can achieve the same results with Maven if you’re using that as a build tool, but Gradle is way superior to Maven when it comes to build configurations. This shouldn’t be a surprise to anyone nowadays though. Unfortunately we are sometimes forced to use Maven, especially on legacy projects, but if you’re starting a new project I strongly suggest considering gradle.

Having a personal repository on Bintray where you can actually play around and have control over your files helps a lot, and I’m glad I didn’t go with Maven Central straight away.