Integrate CLI tools with Gradle by example: Detekt

“magnifying glass near gray laptop computer” by Agence Olloweb on Unsplash

Recently we wanted to introduce static code analysis in one of our Kotlin Android projects. There are two popular tools available: KtLint and Detekt. While they do basically “the same” their philosophy is quite different.

Since we wanted to have something highly configurable we have chosen Detekt.

The plan — and some problems

This seemed to be an easy and straight forward task since we wanted to have it integrated in our Gradle build and there is a Gradle plugin already available.

Sounds easy enough.

It all looked good and it spitted out issues — it even honored the reporting options but unfortunately it didn’t care about the actual rules we configured.

There are already some open issues other contributors are working on. So it would’t have made much sense to also get into this. You know: too many cooks spoil the soup

So we could have leaned back and wait for the issues to get fixed or come up with our own pragmatic way to solve the problem until the Gradle plugin works for us: just use the CLI in our build for now

And that’s what we finally did — and the good thing is that it’s an easy way to integrate any CLI tool running in the JVM into your Gradle build.

Get Detekt working without the Gradle plugin

We created a separate .gradle file containing the necessary code to run the Detekt CLI.

For us we named it detekt.gradle and placed it in the root of our multi module project.

First thing we did is to define a new configuration and add the dependency to the CLI:

If you have additional custom rulesets this is the place to reference them.

The actual integration uses the JavaExec task to execute the Detekt CLI:

group and description are optional but it’s a good idea to define it to make it visible in the tasks overview.

main is the entry point of the JVM application — that’s where the static main method is.

classpath is the classpath containing the JVM application — and the additional dependencies if necessary. Thanks to Gradle we can just use our declared configuration.

Then we just define the parameters. We decided to have one config file common to all modules living in our project’s root. (detekt-config.yml)

We could have written all the parameters just as one parameter to args() but for clarity we defined them this way.

When you (like we) also define separate tasks for the creation of the baseline / consuming the baseline you might not want to repeat the parameter values.

For Detekt you can lookup the exact parameters to pass to the CLI at https://arturbosch.github.io/detekt/cli.html

The final step is to have an “apply from: ‘../detekt.gradle” in each module’s build.gradle file you want to have Detekt active for.

Now a “gradle detekt” will run the analysis.

Inspect and adapt

Regardless how much you cared about your code — running Detekt without any customization on your code base containing hundreds or thousands of files for the first time will most likely give you a lot of warnings but it’s a good first step.

Just look at the reported issues and decide on what you (as a team) agree and on what you disagree.

Feel free to adjust thresholds and disable rules until all (or almost all) of the remaining issues is something you agree on and are what you really want to fix.

Remember you can suppress rules in the code where it makes sense while keeping the rule active.

There is no use in getting hundreds of issues reported that nobody agrees on (and consequently nobody wants to fix). In the end those tools are here to help you — not to make you sad or angry 😉

In order to actually adjust the rules you just copy the default ruleset into your project and start from there.

The configuration file is a YAML file and looks like this (this is only a small part of it)

Just use your favorite editor to modify it. It should be pretty much self-explanatory but there is also documentation for it.

No matter what you adjust — probably you still end up with a bunch of code style violations you consider worth to fix. You could instantly start to tackle them or you could use another nice feature of Detekt: Baselines

First you run Detekt with the option to generate a baseline file — that’s a file containing the “known issues”. Next time you run Detekt (with the baseline file configured) it will only report issues that are not already contained in the baseline.

This works surprisingly well — often it also understands that you only added some lines of code without touching the actual rule violating code and consequently won’t report that as a new issue — I have seen worse implementations before which just record the line number and the kind of the violation to ignore in the baseline. However also here you might get some issues which are not directly caused by your changed code sometimes — but to be honest that’s a good opportunity to fix those things in a walk by.

In the end it shouldn’t be your goal to live forever with the baseline. You should really go for fixing those remaining issues. But thanks to the baseline option you can postpone that tedious task for a moment.

Use it in your CI builds

Now that you have Detekt up and running with rules adapted to your needs you can (and should) also include it in your CI builds.

If you want to have the generated report visualized by e.g. Jenkins you can use Jenkin’s Checkstyle plugin to visualize the generated XML report (remember: that report is in the popular Checkstyle format)

TL;DR

  • if the official Gradle plugin doesn’t work in your setup just use the CLI
  • adjust the rules to match your needs