Large Gitlab logo sitting over screenshot of application UI

Kotlin GitLab Pipeline

Leverage Gitlab pipelines and Gradle to automate building and testing your Kotlin code

Over the past few months I’ve been learning a bit of Gradle and Kotlin for a new project at work.

At work we use an internal code repository that doesn’t have any sort of continuous integration or automated build process, so it’s up to us to manually run tests on our local machines. Unfortunately, we often forget to do this, and bugs can slip by unnoticed.

When working on personal projects I try to learn from what we do at work (both the good and the bad) and I strive to always set up test automation. In most cases, this means using Gitlab pipelines to build and test every single commit. This article describes how I set that up for Kotlin.

What are Gitlab Pipelines?

The idea behind Gitlab pipelines is straightforward: define a set of steps to build and test your code, store those steps in a file in your repository, use Docker containers to run the defined steps, and finally, report back the results. It’s a simple concept but it’s very powerful.

The following screenshot shows a section of the massive build pipeline used to build and test Gitlab’s own codebase.

Gitlab’s build pipeline showing many stages and steps

Note: The Gitlab software is open-source and hosted on gitlab.com. Gitlab is quite literally building itself using these pipelines.

By looking at the pipeline, you can get an idea of all the things it is doing. Compiling all kinds of assets, running tests, generating coverage reports, and so on. There are hundreds of steps involved, spread across multiple stages. They’re all automated and they all run for every single commit.

How can this help you?

The flexibility of pipelines gives you endless possibilities. You can define what Docker container to use, what stages you want, what steps fit into each stage, and what commands to run for each stage. There are all sorts of things you can automate. For example:

  • Test your code.
  • Run static code analysis to find anti-patterns.
  • Run style checkers to find style errors.
  • Run vulnerability scans.
  • Check for out-of-date dependencies.
  • Validate commit messages to ensure they follow a given standard.
  • Run different steps on different branches/tags (e.g. Run a special publication script only on the master branch).
  • Build and host a static site (documentation).
  • Run tests against multiple database engines to ensure compatibility.

There are many others. You can read the docs for inspiration or take a look at the examples page.

Applying it to Kotlin

Since starting to use Kotlin and Gradle at work I’ve started using it for some of my personal projects as well. Figuring out a pipeline I can use in Gitlab for my Kotlin code was high on my to-do list.

For the purposes of this article, I created a demo project to demonstrate the solution I came up with. You can find it here: Kotlin Build Pipeline

Demo project’s build pipeline showing three stages and four steps

At a high level, this is what it does.

The first stage is to compile the code. This stage has a single step that uses Gradle to compile.

The second stage is to test the code. I split this into two types:

  1. Run Ktlint to find style errors — a failure here will be interpreted as a warning and the pipeline will proceed.
  2. Run all the unit tests defined in the project — a failure here means the pipeline fails.

Finally, build a “fat jar” file with all the dependencies to be published.

To set this up, I just need to create the .gitlab-ci.yml file in the root of the repository with the right instructions. Gitlab handles the rest.

image: openjdk:11-jdk

stages:
  - compile
  - test
  - package

before_script:
  - export GRADLE_USER_HOME=`pwd`/.gradle

cache:
  paths:
    - .gradle/wrapper
    - .gradle/caches

compile:
  stage: compile
  script:
    - ./gradlew assemble

test:
  stage: test
  script:
    - ./gradlew test --stacktrace

code_style:
  stage: test
  script:
    - ./gradlew ktlint
  artifacts:
    paths:
      - build/ktlint.xml
    expire_in: 1 day
  allow_failure: true

package:
  stage: package
  script:
    - ./gradlew shadowJar
  artifacts:
    paths:
      - build/libs/*.jar
    expire_in: 1 day

So what is this file actually doing?

  • image: The first line is to define which Docker container to use. In this case, I’m using a container with Java JDK 11 installed. The demo application is being built to target Java version 11 but this can be tweaked to target a different version of Java.
  • stages: The next section defines the stages of the build process. For a simple project I define three stages: compile, test, and package.
  • before_script & cache: The before_script section defines commands that get run before each and every step. In this case I am setting the GRADLE_USER_HOME system variable to the correct path. This complements the following cache section to store the .gradle/ folder in the shared cache between steps. These steps are not necessary but they make the pipeline run a little faster.
  • compile: The compile section is the first actual build step. It belongs to the compile stage and its purpose is to compile the code and ensure there are no build errors. This step runs the gradle assemble command.
  • test: As the name suggests, the purpose of the test section is to run tests. This step runs the gradle test command to run all the tests. In this project, the tests were created using the JUnit5 framework.
  • code_style: This section defines how to check the code for code-style errors. I use Ktlint for this because it is so easy to set up and use. Ktlint is configured in the build.gradle file and I made the following two tweaks to the default configuration.
    • Generate an output file with the results at build/ktlint.xml
    • Allow wildcard imports. By default Ktlint will flag wildcard imports as an error. However, this clashes with the default behaviour of my IDE (IntelliJ), which will use wildcard imports where possible. This is a controversial topic but I chose to allow them. You can re-enable this rule by updating the build.gradle file and remove the flag for disabled rules the Ktlint settings: "--disabled_rules=no-wildcard-imports"
  • package: This is the final step of the build. It’s part of the package stage and its purpose is to compile the project into a single “fat jar” and store it as an artifact for later use. This step uses the Shadow Jar Gradle plugin to generate the jar file. If it makes sense for your project, you can update this file to only run this step on certain branches (e.g. only on master) to avoid generating build artifacts for code that’s still in development.

If you want to learn more about setting up build pipelines with this file you can read more in the documentation.

Conclusion

In this article, I focused on building and testing Kotlin code, but Gitlab pipeline functionality is not limited to Kotlin or Gradle. There are plenty of other creative ways you can use pipelines for all kinds of languages.

I hope this helped you set up build automation for your own personal or professional projects. Or at least got you thinking about it.

If for some reason you’re dead-set on using Github instead, Gitlab even offers the option to run pipelines for external projects hosted on Github. Read more about that in the documentation.

blockquote

Thank you for reading. What will you build?

Resources

Also on Medium


Hector Smith