loading...

Building a Kotlin library with Gradle

cueo profile image Mohit Mayank ・4 min read

Introduction

The most boring part of a library is the setting up part. Choosing the build systems, publishing builds, setting up code coverage, linting, etc etc. This blog is aimed at abstracting those concerns in the form of a simple file at the end of all this.

Choosing a build system

  • Gradle
  • Maven

Both Gradle and Maven have their own pros and cons. It is not in the scope of this article to delve into those details. However, I'd mention that Gradle tends to be a little bit faster while Maven has better support (in terms of IDE or plugins).

In this blog, we will only deal with Gradle.

Initial set-up

For a Kotlin project, our initial Gradle file looks something like this:

plugins {
  id 'org.jetbrains.kotlin.jvm' version '1.3.72'
}

group 'com.cueo'
version '1.0-SNAPSHOT'

repositories {
    mavenLocal()    
  mavenCentral()
}

dependencies {
  implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
}

compileKotlin {
    kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
    kotlinOptions.jvmTarget = "1.8"
}

Few pointers about the above configuration:

  1. plugins In order to build your Kotlin project with Gradle you need to set-up the kotlin-gradle plugin. This is done by the line: id 'org.jetbrains.kotlin.jvm' version '1.3.72'. (1.3.72 is the latest version at the time of writing).
  2. repositories This is where you configure if you want to download the artifacts from Maven central, a Nexus proxy or your organisation's specific Nexus. Do NOT forget to add mavenLocal(). This speeds up loading of the dependencies even if you do not have them cached in the build folder. If some other project has a same dependency, it loads the dependency from Maven's .m2 folder.
  3. kotlinOptions.jvmTarget inside compileKotlin and compileTestKotlin ensures the target JVM.

Linting

A linter forces (and sometimes formats) the code to follow a coding style. If you work on a project with several team members, linting ensures the project follows a specific coding style, eg: use spaces instead of tabs, space before brackets and braces, space after commas, etc.

Let's enable ktlint in our project.

An anti-bikeshedding Kotlin linter with built-in formatter.

plugins {
  ...
  id 'org.jlleitschuh.gradle.ktlint' version '9.2.1'
}

That's it! Now you can run:

  • gradle ktlint to check for errors.
  • gradle ktlintFormat to auto fix some of the fixable errors.

Adding the plugin also automatically attaches itself to the build lifecycle.

Code coverage

We will use Gradle's Jacoco plugin for checking code coverage. Start by adding Jacoco to the list of plugins.

plugins {
  ...
  id 'jacoco'
}

You can also make sure each every time gradle test runs, a coverage report is generated by Jacoco.

test {
  finalizedBy jacocoTestReport
}

Enable or disable Jacoco XML and HTML reports as follows:

jacocoTestReport {
  reports {
    xml.enabled true
    html.enabled true
  }
}

Publish to Nexus

Eventually you'd want to publish your library to Nexus so that other projects can add your library as a dependency. We will use Gradle's maven-publish plugin to do that. You can find detailed description of the plugin here.

Add the plugin to build.gradle:

plugins {
  ...
  id 'maven-publish'
}

Configure the plugin:

publishing {
  publications {
    mavenJava(MavenPublication) {
      artifactId = 'awesome-kotlin-lib'
      from components.java
      pom {
        name = 'Awesome Kotlin Library'
        description = 'An awesome library written in Kotlin built using Gradle.'
      }
    }
  }
  repositories {
    maven {
      def snapshotsRepoUrl = 'https://your-nexus-url.com/snapshots'
      def releasesRepoUrl = 'https://your-nexus-url.com/releases'
      url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl

      credentials {
        username = System.getenv('NEXUS_USERNAME')
        password = System.getenv('NEXUS_PASSWORD')
      }
    }
  }
}

The above configuration makes some assumptions about the maven repository:

  • The URLs for SNAPSHOT and RELEASE builds are different. You might as well use the same endpoint for both SNAPSHOT and RELEASE builds.
  • Your system or CI is configured with the Nexus username and password. Locally you can just do the following before running gradle publish:
export NEXUS_USERNAME='username'
export NEXUS_PASSWORD='unhackable-password'

Now gradle publish will publish your jar to the Nexus given you have sufficient permissions.

Releases

maven-publish is incomplete without a release plugin. While the former publishes the jar to the Nexus, the latter is responsible for bumping up the versions in Git and creating relevant tags. We will use gradle-release plugin by ResearchGate. As usual, start by adding the plugin.

plugins {
  ...
  id 'net.researchgate.release' version '2.6.0'
}

Configure the plugin with your Github username and token.

release {
  svn {
    username = System.getenv('GITHUB_USERNAME')
    password = System.getenv('GITHUB_TOKEN')
  }
}

Make sure each gradle release is followed by publishing the new build to the Nexus.

tasks {
  afterReleaseBuild {
    dependsOn publish
  }
}

Conclusion

In the end our build.gradle file should look something like this:

plugins {
  id 'org.jlleitschuh.gradle.ktlint' version '9.2.1'
  id 'org.jetbrains.kotlin.jvm' version '1.3.72'
  id 'maven-publish'
  id 'jacoco'
  id 'net.researchgate.release' version '2.6.0'
}

java {
  withSourcesJar()
}

repositories {
  mavenLocal()
  mavenCentral()
}

dependencies {
  implementation 'org.slf4j:slf4j-api:1.7.30'

  implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.3.72'

  testImplementation 'org.jetbrains.kotlin:kotlin-test:1.3.72'
  testImplementation 'org.junit.jupiter:junit-jupiter:5.6.0'
  testImplementation 'io.mockk:mockk:1.9.3.kotlin12'
}

group = 'com.cueo'
version = project.properties['version']

publishing {
  publications {
    mavenJava(MavenPublication) {
      artifactId = 'awesome-kotlin-lib'
      from components.java
      pom {
        name = 'Awesome Kotlin Library'
        description = 'An awesome library written in Kotlin built using Gradle.'
      }
    }
  }
  repositories {
    maven {
      def snapshotsRepoUrl = 'https://your-nexus-url.com/snapshots'
      def releasesRepoUrl = 'https://your-nexus-url.com/releases'
      url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl

      credentials {
        username = System.getenv('NEXUS_USERNAME')
        password = System.getenv('NEXUS_PASSWORD')
      }
    }
  }
}

compileKotlin {
    kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
    kotlinOptions.jvmTarget = "1.8"
}

test {
  useJUnitPlatform
  finalizedBy jacocoTestReport
}

jacocoTestReport {
  reports {
    xml.enabled true
    html.enabled true
  }
}

release {
  svn {
    username = System.getenv('GITHUB_USERNAME')
    password = System.getenv('GITHUB_TOKEN')
  }
}

tasks {
  afterReleaseBuild {
    dependsOn publish
  }
}

That's all, folks! You can now go ahead and write tons of libraries helping out all the developers out there.

Cheers!

Posted on by:

Discussion

markdown guide