State of Kotlin Static Analysis

Less than a year ago I started using Kotlin to write micro-services. I was a Java developer, and mostly happy with that, but I took a liking to Kotlin immediately. In short order, I was writing cleaner, more robust code and seeing performance gains too!

The one area I wasn’t satisfied with in Kotlin was static code analysis. With Java grinding your code against static analysis tools was easy, and informative, it helped improve my code, and was vital for bringing junior folks, or outsiders, into a code base’s style and quality expectations.

Less Than A Year Ago

  • Jacoco: Crashed often on Kotlin, didn’t offer many insights, and didn’t understand Kotlin’s code generation leading to frustration code coverage numbers.
  • Ktlint and Detekt: Were young and it showed. They were hard to configure, really hard to employ as build gates, and rudimentary.
  • SonarQube, Codecov.io, Etc: With Jacoco feeding them, and broken, you can guess how well they worked…
  • IntelliJ’s Builtin Analysis: Meh. Worked but also kind of rudimentary.

Now, All That’s Changed

  • Jacoco: Now is reliable, more informative and fully understands Kotlin’s code generation.
  • Ktlint and Detekt: Have matured, have reliable plugins, are very informative, and can easily act as build gates.
  • SonarQube, Codecov.io, Etc: Well, ok, so SonarQube is still difficult, but Codecov.io rocks!
  • IntelliJ’s Builtin Analysis: Gotten better. I still, except for active hints, mostly prefer the others.

What You Need

  • Gradle: Get a new one, 5.5+, everything is easier with the newer builds.
  • Jacoco: Use the stock gradle plugin, and set the tool version to 0.8.3+.
  • Ktlint: Use the org.jlleitschuh.gradle.ktlint plugin, version 8.2.0+.
  • Detekt: Use the io.gitlab.arturbosch.detekt plugin, version 1.0.0-RC16+.
  • Editor Config: Take a look at this project for configuration convergence between IntelliJ and Ktlint.

What You’ll Get

You’ll get static analysis that offers real insights, idiomatic guidelines, accurate test coverage, one click auto-correction, and can be used to gate your build. I’ve even been able to cat herd Scala bigots with these tools.

Example

You can take a look at my ksvg project on github, it uses all these and appears on Codecov.io with 99% coverage.

Gradle Kotlin DSL: First Impressions

As an learning exercise, now that Gradle has a Kotlin DSL, I decided to migrate a Gradle build from the standard Groovy DSL over to the Kotlin one. My Kotlin SVG DSL project was the perfect candidate, being a pure Kotlin project already.   This article won’t attempt to cover all the details, but I’ll discuss the basics and my impressions.

The Migration Path

The basics of moving the Gradle build from Groovy to Kotlin are simple. Gradle itself now supports either, so none of the tools themselves change. What changes is your build.gradle script.  If Gradle sees build.gradle is expects Groovy, if it sees build.gradle.kts it expects Kotlin.  So the obvious change is to simply rename your build script and deal with the resultant issues. That said, there are script elements common to both, so before I took the leap and renamed the file, I worked in the Groovy version to insure that the common bits like the plugin management were up to current standards.

So my steps were:

  • Update Gradle to the latest and greatest (4.10.2 at that point).
  • Migrate the Groovy plugin management to the newest method
  • Update all the plugins
  • Review all the strings, Groovy allows several flavors of strings including single quoted, Kotlin mostly wants double quoted strings. The double quoted work in either, so I moved all the single quoted over to double quoted before the migration.
  • Rename build.gradle to build.gradle.kts

And KABOOM!

The Kaboom

Yes there was an earth shattering kaboom.  The renamed file, even with the common bit prepared was not a compliant Kotlin script.  The Kotlin DSL is familiar in spirit, but the code must be kotlin and so there are many syntactic and a few semantic differences around configuring and managing tasks.

All of my plugins still worked, but configuring them, and any ad hoc code I’d used to enhance them had to be reworked to varying degrees.

What I Observed

  • Many changes we sane and mechanical once you knew what to do.
  • Finding out what to do was the challenge:
    • The Kotlin DSL isn’t yet covered in a lot of documentation. Much of the documentation out there pre-dates the Kotlin DSL.
    • The Kotlin DSL is young and about 30% of the documentation out there is based on prior working/transitional versions and not right.
    • Some tried and true Groovy tricks for things like collecting dependency list etc. were hard to reimagine in Kotlin.
    • Kotlin’s type awareness meant that things you’d done in Groovy had just worked without knowing the types, so no one ever bothered documenting them. I ended up digging through the source code to some plugins to find type information needed by Kotlin.

Impressions

I mostly like the resultant Kotlin version better and have switch the master branch over to it.  But I’m not going to switch over all my projects, at least not yet.  In particular, plugins that employed too much magic, like Spring Boot with it’s automagic versioning resolution, or code generation tools, I don’t think the reward is worth the work to figure out the migration.

Creating an OS X App Bundle With Gradle

I’ve been tinkering with Kotlin and JavaFX and the result was a Java jar file that spins up a user interface.  OS X will double click open a jar, but I wanted a normal app. I looked around and found App Maker, which did a decent job, but processing the jar manually each build got annoying fast.  Looking at the info out there, and what App Maker did, it seemed like I ought to be able to get gradle to do the same without too much pain. I did.

The Simplest App Possible

So, from documentation, and investigating App Maker’s output, the simplest app is:

AppName.app/
   Contents/
      Info.plist
      MacOs/
         AppName-x.y.z.jar
         launcher
      Resources/
         application.icns

That’s the basics.  So if you’ve a working jar, all you’re lacking it the directory structure, and:

  • Info.plist: An Apple plist file, you can use a simple one unaltered.
  • launcher: A shell script that launches the jar. This can be 98% templated, with only things like the app name, jar name, and java version needing to be set.
  • application.icns: An icon formatted to Apple’s approval.

The Gradle Solution

To generate the app bundle from gradle, I did two things:

  1. Put the three needed files into src/main/app. The Info.plist and the application.icns are the actual final files.  The launcher I modified into a template, using Ant’s templating notation “@VARIABLE_NAME@”.
  2. Then I added one gradle task to build out the directory structure, and copy the files into place, applying the templating.

The Task

Here is the gradle task.  I employed some of gradle’s ant support to perform copies and templating.

task osxApp {
dependsOn jar
doLast {
def dist = "${buildDir}/app/${project.name}.app"
mkdir("$dist/Contents/MacOS")
mkdir("$dist/Contents/Resources")
ant.copy(file: "src/main/app/Info.plist", todir: "$dist/Contents")
ant.copy(file: "src/main/app/launcher", todir: "$dist/Contents/MacOS") {
filterset {
filter(token: 'APP_JAR', value: "${project.name}-${version}.jar")
filter(token: 'APP_NAME', value: project.name)
filter(token: 'JAVA_VERSION', value: jvm_version)
}
}
ant.copy(file: "src/main/app/application.icns", todir: "$dist/Contents/Resources")
ant.copy(file: "build/libs/${project.name}-${version}.jar", todir: "$dist/Contents/MacOS")
project.exec {
commandLine('chmod', '+x', "$dist/Contents/MacOS/launcher")
}
}
}
view raw osxApp.groovy hosted with ❤ by GitHub

With the files and task in place all you need to do is run gradle osxApp and you’ll hopefully find build/app/ProjectName.app – a working OS X app.

Conclusion

I could roll this into a Gradle PlugIn but that’s more work then I want to do for this.  As it stands, if you want to use this approach, just grab my project that uses it, copy the files in src/main/app and add the task to your build.gradle.

 

Sloppy Fat Jar in Gradle

In Java, one deployment approach is to bundle all you dependencies into a single fat jar and then use “java -jar foo.jar” to run things.  I use this often, and there are several Gradle plugins that facilitate doing it. The plugins tend to offer nice features like name space collision resolution, what if the same class or resource appears in two dependencies?  But recently I stumbled across this little Gradle gem for making what I’ll call a sloppy fat jar:

jar {
manifest {
attributes(
'Main-Class': "${project.group}.foo.Main"
)
}
from configurations.compile.collect { entry -> zipTree(entry) }
}
view raw build.gradle hosted with ❤ by GitHub

All it does is, in a single line (7), unpack all your dependencies into your jar. Totally brute force and crude, but then it doesn’t require a plugin or any configuration. Also it doesn’t tie into anything else, so for example it worked in a Kotlin build where other plugins got totally confused.

Gradle, JUnit5, Jacoco Working

The way information is dispersed on the Internet is pretty cool sometimes.  I’ve visited this same topic a couple times and never managed to get JUnit5 and Jacoco working with Gradle.  Fresh searches never turned up a working solution.  The other day, looking at my blog stats I saw an intriguing referrer to one of my past posts. The reference was from a blog written in Japanese. Google translate yielded an awkward version that seemed to offer a solution.  Since the Gradle code needed no translation I tried out some of the changes shown and – viola!

So, Here’s the Working Solution

The goal was to employ JUnit5, generate Jacoco reports, and feed those into Codecov.com. Here’s the snippet of working solution.

buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'org.junit.platform:junit-platform-gradle-plugin:1.1.0-RC1'
}
}
apply plugin: "jacoco"
apply plugin: 'org.junit.platform.gradle.plugin'
repositories {
jcenter()
}
dependencies {
compile 'org.junit.jupiter:junit-jupiter-api:5.1.0-RC1'
testRuntime 'org.junit.jupiter:junit-jupiter-engine:5.1.0-RC1'
}
junitPlatform.enableStandardTestTask true
jacoco {
toolVersion = '0.7.9'
applyTo junitPlatformTest
}
jacocoTestReport {
reports {
xml.enabled = true
html.enabled = true
}
}
view raw build.gradle hosted with ❤ by GitHub

I believe the secret sauce I missed was in lines 23 and 27.  To see the project that I tested this with, the repo is here.