Blog 2.7.2019

Code quality metrics for Kotlin projects on SonarQube

SonarQube project overview

Good to see you here! We have no doubt this post has good information, but please keep in mind that it is over 5 years old.

Code quality in a software development project is important and a good metric to follow. Code coverage, technical debt and vulnerabilities in dependencies are a couple of things you should follow. There are some de facto tools you can use to visualize things and one of them is SonarQube. Here’s a short technical note of how to set it up on a Kotlin project and visualize metrics from different tools. We are using Detekt for static source code analysis and OWASP Dependency-Check to detect publicly disclosed vulnerabilities contained within project dependencies.

Visualizing Kotlin project metrics on SonarQube

SonarQube is a nice graphical tool to visualize different metrics of your project. Lately, it has also started to support Kotlin with the SonarKotlin and sonar-kotlin plugins. From a typical Java project, you need some extra settings to get things working. It’s also good to notice that the support for Kotlin isn’t quite yet there and the sonar-kotlin provides better information i.e. what comes to code coverage.
Steps to integrate reporting to Sonar with maven build:

  • Add configuration in project pom.xml: Surefire, Failsafe, jaCoCo, Detekt, Dependency-Check
  • Run Sonar in Docker
  • Maven build with sonar:sonar option
  • Check Sonar dashboard

SonarQube project overview
(SonarQube project overview)

Configure a Kotlin project

Configure your Kotlin project built with Maven to have test reporting and static analysis. We are using Surefire to run unit tests, Failsafe for integration tests and JaCoCo to generate reports for e.g. SonarQube. See the full pom.xml from an example project (coming soon).

Test results reporting

pom.xml

<properties>
<sonar.coverage.jacoco.xmlReportPaths>${project.build.directory}/site/jacoco/jacoco.xml</sonar.coverage.jacoco.xmlReportPaths>
</properties>
<build>
    <plugins>
        <plugin>
            <groupId>org.jacoco</groupId>
            <artifactId>jacoco-maven-plugin</artifactId>
            <executions>
                <execution>
                    <id>default-prepare-agent</id>
                    <goals>
                        <goal>prepare-agent</goal>
                    </goals>
                </execution>
                <execution>
                    <id>pre-integration-test</id>
                    <goals>
                        <goal>prepare-agent-integration</goal>
                    </goals>
                </execution>
                <execution>
                    <id>jacoco-site</id>
                    <phase>verify</phase>
                    <goals>
                        <goal>report</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
        <plugin>
            <artifactId>maven-surefire-plugin</artifactId>
            <configuration>
                <skipTests>${unit-tests.skip}</skipTests>
                <excludes>
                    <exclude>**/*IT.java</exclude>
                    <exclude>**/*IT.kt</exclude>
                    <exclude>**/*IT.class</exclude>
                </excludes>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-failsafe-plugin</artifactId>
            <executions>
                <execution>
                    <goals>
                        <goal>integration-test</goal>
                        <goal>verify</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <skipTests>${integration-tests.skip}</skipTests>
                <includes>
                    <include>**/*IT.class</include>
                </includes>
                <runOrder>alphabetical</runOrder>
            </configuration>
        </plugin>
    </plugins>
    <pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.1</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-failsafe-plugin</artifactId>
                <version>2.22.1</version>
            </plugin>
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>0.8.3</version>
            </plugin>
        </plugins>
    </pluginManagement>
...
</build>

Static code analysis with Detekt

Detekt static code analysis configuration as AntRun. There’s also an unofficial Maven plugin for Detekt. It’s good to notice that there are some “false positive” findings on Detekt and you can either customize detekt rules or suppress findings if they are intentional such as @Suppress(“MagicNumber”).
Detekt code smells
(Detekt code smells)
pom.xml

<properties>
    <sonar.kotlin.detekt.reportPaths>${project.build.directory}/detekt.xml</sonar.kotlin.detekt.reportPaths>
</properties>
<build>
...
<plugins>
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-antrun-plugin</artifactId>
    <version>1.8</version>
    <executions>
        <execution>
            <!-- This can be run separately with mvn antrun:run@detekt -->
            <id>detekt</id>
            <phase>verify</phase>
            <configuration>
                <target name="detekt">
                    <java taskname="detekt" dir="${basedir}"
                          fork="true"
                          failonerror="false"
                          classname="io.gitlab.arturbosch.detekt.cli.Main"
                          classpathref="maven.plugin.classpath">
                        <arg value="--input"/>
                        <arg value="${basedir}/src"/>
                        <arg value="--filters"/>
                        <arg value=".*/target/.*,.*/resources/.*"/>
                        <arg value="--report"/>
                        <arg value="xml:${project.build.directory}/detekt.xml"/>
                    </java>
                </target>
            </configuration>
            <goals>
                <goal>run</goal>
            </goals>
        </execution>
    </executions>
    <dependencies>
        <dependency>
            <groupId>io.gitlab.arturbosch.detekt</groupId>
            <artifactId>detekt-cli</artifactId>
            <version>1.0.0-RC14</version>
        </dependency>
    </dependencies>
</plugin>
</plugins>
...
</build>

Dependency checks

Dependency check with OWASP Dependency-Check Maven plugin
OWASP Dependency-Check
(OWASP Dependency-Check)
pom.xml

<properties>
    <dependency.check.report.dir>${project.build.directory}/dependency-check</dependency.check.report.dir>
    <sonar.host.url>http://localhost:9000/</sonar.host.url>
    <sonar.dependencyCheck.reportPath>${dependency.check.report.dir}/dependency-check-report.xml</sonar.dependencyCheck.reportPath>
    <sonar.dependencyCheck.htmlReportPath>${dependency.check.report.dir}/dependency-check-report.html</sonar.dependencyCheck.htmlReportPath>
</properties>
<build>
...
<plugins>
<plugin>
    <groupId>org.owasp</groupId>
    <artifactId>dependency-check-maven</artifactId>
    <version>4.0.2</version>
    <configuration>
        <format>ALL</format>
        <skipProvidedScope>true</skipProvidedScope>
        <skipRuntimeScope>true</skipRuntimeScope>
        <outputDirectory>${dependency.check.report.dir}</outputDirectory>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>check</goal>
            </goals>
        </execution>
    </executions>
</plugin>
</plugins>
...
</build>

Sonar scanner to run with Maven

<build>
...
    <pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.sonarsource.scanner.maven</groupId>
                <artifactId>sonar-maven-plugin</artifactId>
                <version>3.6.0.1398</version>
            </plugin>
        </plugins>
    </pluginManagement>
...
</build>

Running Sonar with a Kotlin plugin

Create a SonarQube server with Docker

$ docker run -d --name sonarqube -p 9000:9000 -p 9092:9092 sonarqube

There’s also OWASP docker image for SonarQube which adds several community plugins to enable SAST. But for our purposes, the “plain” SonarQube works nicely.
Use the Kotlin plugin which comes with SonarQube (SonarKotlin) or install the sonar-kotlin plugin which shows information differently. If you want to use sonar-kotlin and are using the official Docker image for SonarQube then you have to first remove the SonarKotlin plugin.
Using sonar-kotlin

$ git clone https://github.com/arturbosch/sonar-kotlin
$ cd sonar-kotlin
$ mvn package
$ docker exec -it sonarqube sh -c "ls /opt/sonarqube/extensions/plugins"
$ docker exec -it sonarqube sh -c "rm /opt/sonarqube/extensions/plugins/sonar-kotlin-plugin-1.5.0.315.jar"
$ docker cp target/sonar-kotlin-0.5.2.jar sonarqube:/opt/sonarqube/extensions/plugins
$ docker stop sonarqube
$ docker start sonarqube

Adding dependency-check-sonar-plugin to SonarQube

$ curl -JLO https://github.com/SonarSecurityCommunity/dependency-check-sonar-plugin/releases/download/1.2.1/sonar-dependency-check-plugin-1.2.1.jar
$ docker cp sonar-dependency-check-plugin-1.2.1.jar sonarqube:/opt/sonarqube/extensions/plugins
$ docker stop sonarqube
$ docker start sonarqube

Run test on project and scan with Sonar

The verify phase runs your tests and should generate i.a. jacoco.xml under target/site/jacoco and detekt.xml.

$ mvn clean verify sonar:sonar

Access Sonar via http://localhost:9000/

Code quality metrics? So what?

You now have metrics on Sonar to show to stakeholders but what should you do with those numbers?
One use case is to set quality gates on SonarQube to check that a set of conditions must be met before the project can be released into production. Ensuring code quality of “new” code while fixing existing ones is one good way to maintain a good codebase over time. The Quality Gate facilitates setting up rules for validating every new code added to the codebase on subsequent analysis. By default, the rules are: “coverage on new code < 80%; the percentage of duplicated lines on new code > 3; maintainability, reliability or security rating is worse than A”. The default rules provide a good starting point for your projects quality metrics.

Kotlin

Marko Wallin

Marko works as a full stack software engineer and creates better world through digitalization. He writes technology and software development related blog and developes open source applications e.g. for mobile phones. He also likes mountain biking.

Back to top