Distribute a Library on Bintray using Gradle Kotlin DSL
In my latest blog post, published a few weeks back, I informed about the usage of the Gradle Kotlin DSL and how it helps with describing build scripts. In another earlier post, I introduced a small library that can be utilized for simplifying the creation of TLS/SSL sockets using a custom Kotlin DSL: SeKurity.
In this post, we'll investigate how such a library can be made available to others that actually want to make use of it inside other projects. Ultimately, it should be possible to list the SeKurity library as a simple dependency in a build script like Maven or Gradle. Since the library itself is already backed by Gradle, I'll show a way of publishing the resulting artifacts at the end of that build.
Where to publish?
In order to make a library available to anybody else, it needs to be published to a publicly accessible e.g. Maven repository like the famous Maven Central. Unless you're absolutely certain about the quality of your library, it's recommended to share it on custom repositories rather than THE central one directly as you'll kinda lose control of it once it's out there. One alternative is called Bintray and it will also be used in the present article. After creating an account or simply signing in with your GitHub, Google or Twitter account, you can create your own repositories there.
Automating the Build
Before enabling our build to publish the built artifacts, the process should be automated in a CI tool like Travis CI, which interacts nicely with GitHub. To make it work, it must be enabled in the GitHub repo settings by adding it as a service (see Settings -> Integrations & services). Travis expects the repository to incorporate a .travis.yml
configuration that tells it what to do. For a simple Java/Kotlin project using Gradle, the following will be sufficient:
language: java
before_install:
- chmod -R +x src
- chmod +x gradlew
script: ./gradlew clean build
deploy:
provider: script
script: ./gradlew bintrayUpload -PbintrayUser=$BINTRAY_USER
-PbintrayApiKey=$BINTRAY_KEY
on:
branch: master
It's normally not necessary to specify the script
command explicitly as long as you're using the default instructions. In the shown case, Gradle runs the build
task first before it deploys via the gradle bintrayUpload
taks, which we will learn about later. The deployment only happens for the master branch. The gradle deploy takes two arguments $BINTRAY_USER
and $BINTRAY_KEY
, which contain private data and are therefore securely stored in Travis directly. When the build is run, Travis takes care of replacing them.
You can finally enable the repository in Travis and it will start a build on every push immediately. For further information on Travis, the documentation is highly recommended.
The Gradle Build
As shown earlier, the bintrayUpload
Gradle task gets invoked in the build. Where does it come from? Let's have a look at the relevant parts of the gradle.build.kts
file.
The Plugins
plugins {
kotlin("jvm") version "1.2.30"
id("com.github.johnrengelman.shadow") version "2.0.2"
`maven-publish`
id("com.jfrog.bintray") version "1.8.0"
}
- The
shadow
plugin is used because we need the library to be bundled with all its dependencies in order to avoid classpath conflicts. - The
maven-publish
plugin helps with creating the relevant artifact and a relevantpom.xml
file that will also be published. - The
bintray
plugin does all the heavy work at the end of the build. It refers to themaven-publish
result and pushes the artifacts to Bintray.
Plugin Configuration
The build file also adds custom configurations for all previously shown plugins as shown next:
I Shadow
val artifactID = "sekurity"
val shadowJar: ShadowJar by tasks
shadowJar.apply {
baseName = artifactID
classifier = null
}
The shadow
plugin uses the project's name as the artifact's baseName
and the "all" qualifier by default, which is overridden here.
II Maven-Publish
fun MavenPom.addDependencies() = withXml {
asNode().appendNode("dependencies").let { depNode ->
configurations.compile.allDependencies.forEach {
depNode.appendNode("dependency").apply {
appendNode("groupId", it.group)
appendNode("artifactId", it.name)
appendNode("version", it.version)
}
}
}
}
val publicationName = "tlslib"
publishing {
publications.invoke {
publicationName(MavenPublication::class) {
artifactId = artifactID
artifact(shadowJar)
pom.addDependencies()
}
}
}
This one is a bit tricky. We reuse the result of shadow
and add it to the publication which will be published under the artifactId
that corresponds to the JAR's baseName. By default, the generated POM does not list the library dependencies, which is done by mapping each compile dependency to a valid dependency
XML node.
III Bintray
fun findProperty(s: String) = project.findProperty(s) as String?
bintray {
user = findProperty("bintrayUser")
key = findProperty("bintrayApiKey")
publish = true
setPublications(publicationName)
pkg(delegateClosureOf {
repo = "SeKurity"
name = "SeKurity"
userOrg = "s1m0nw1"
websiteUrl = "https://kotlinexpertise.com"
vcsUrl = "https://github.com/s1monw1/TlsLibrary"
setLabels("kotlin")
setLicenses("MIT")
})
}
The bintray
plugin is most important since it takes care of actually uploading the generated artifacts to Bintray itself. The user
and key
are obtained from the Gradle project properties, which are passed by Travis later on as already shown earlier. By referring to the publication created in maven-publish
, all artifacts will be uploaded as specified. The pkg
block describes the library's attributes and where, i.e. to what repository, the publishing happens.
The Result
All these blocks merged result in a pretty nice Gradle build script, which can be observed in this Gist and also the SeKurity repository itself. The resulting artifact is made available here: https://bintray.com/s1m0nw1/SeKurity/SeKurity
Referring to the published Library
Now that the library is being uploaded to Bintray, it can be listed as a dependency in arbitrary build files. If we have another Gradle-backed project, we first need to add the relevant repository to the list of repositories:
repositories {
mavenCentral()
jcenter()
maven {
setUrl("https://dl.bintray.com/s1m0nw1/SeKurity/")
}
}
After that, the library can be added as a simple compile
dependency like this:
dependencies {
//...other dependencies
compile("de.swirtz:sekurity:0.0.2")
}
Recap
As shown, Gradle plugins can be used for setting up a proper build that publishes a custom library to Bintray easily. In order to automate the whole process, CI tools like Travis can be used for executing the build whenever a new change has been made.
I hope the little tutorial helps with your build as well! If you like, have a look at my Twitter account and follow if you’re interested in more Kotlin stuff 🙂 Thanks a lot.
Simon is a software engineer with 9+ years of experience developing software on multiple platforms including the JVM and Serverless environments. He currently builds scalable distributed services for a decision automation SaaS platform. Simon is a self-appointed Kotlin enthusiast.
Would be great to have a article on publishing a Kotlin multi-platform library using the Gradle Kotlin DSL (without using a CI). The samples in the article don’t work in Gradle 4.9.
Is there a better way to inherit the dependencies. e.g. with the kotlin-dsl setting up the developers and license sections can work via
pom {
packaging = "jar"
developers {
developer {
email.set("yuri@schimke.ee")
id.set("yschimke")
name.set("Yuri Schimke")
}
}
licenses {
license {
name.set("Apache License")
url.set("http://opensource.org/licenses/apache-2.0")
distribution.set("repo")
}
}
scm {
connection.set("scm:git:https://github.com/yschimke/okurl.git")
developerConnection.set("scm:git:git@github.com:yschimke/okurl.git")
url.set("https://github.com/yschimke/okurl.git")
}
}