Building a Kotlin library with Gradle
Tags: kotlin, gradle, java, maven
Published: 2020-05-25 14:21:13

# 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:

```groovy
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](https://repo.maven.apache.org/maven2/), 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](https://ktlint.github.io) in our project.

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

```groovy
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](https://docs.gradle.org/current/userguide/jacoco_plugin.html) plugin for checking code coverage. Start by adding Jacoco to the list of plugins.

```groovy
plugins {
  ...
  id 'jacoco'
}
```

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

```groovy
test {
  finalizedBy jacocoTestReport
}
```

Enable or disable Jacoco XML and HTML reports as follows:

```groovy
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](https://docs.gradle.org/current/userguide/publishing_maven.html).

Add the plugin to `build.gradle`:

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

Configure the plugin:

```groovy
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`:

```bash
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`](https://github.com/researchgate/gradle-release) plugin by ResearchGate. As usual, start by adding the plugin.

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

Configure the plugin with your Github username and token.

```groovy
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.

```groovy
tasks {
  afterReleaseBuild {
    dependsOn publish
  }
}
```

# Conclusion

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

```groovy
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!