Gradle, Kotlin DSL, Closures

I’ve never been the biggest fan of Groovy, and have been working more and more with Kotlin, so it seemed natural to move my Gradle builds over to the Kotlin DSL. The more I use it, particularly in Gradle 5.+ the more I like it.

That said, Gradle grew up with a Groovy DSL, and there are still some places this is apparent. I mentioned one previously, Groovy supports dynamic typing, and the Gradle ecosystem relied on that heavily. Much of what one did in Gradle was done without any idea of the underlying types. You had no idea what type the argument coming in was, only that it had property X that you needed. When using Kotlin, the types have to be known. Since the ecosystem as a whole never cared, type information is rarely documented, so you end up hoping your IDE will give you hints or digging into the source. It’s not a deal breaker but is a speed bump.

The other bit of impedance is closures. Gradle relies on Groovy closures frequently and while Kotlin is fine with closures, when you throw the following in a blender:

  • Gradle APIs
  • Groovy DSL syntax
  • Kotlin DSL syntax
  • Dynamic typing
  • Static typing
  • Closures

Well, you end up with a bit of an icky goo.

Practical Example

Gradle’s support for running unit tests works well and efficiently. But at times it’s more efficient then I want, narrowing down the tests to run based on the impact of a code change, and reporting a succinct pass or fail. I like a bit of insight into the process and I regularly ask it to report each test it runs and categorize the results. In the Groovy DSL I’ve done that as follows:

test {

    beforeTest { descriptor ->
        logger.lifecycle("\tRunning $descriptor.className.$descriptor.name")
    }

    afterSuite { descriptor, result ->
        if (descriptor.parent == null) {
            logger.lifecycle("\tTests run: $result.testCount, Failures: $result.failedTestCount, Skipped: $result.skippedTestCount")
        }
    }
}

That adds a closure before each test being run that informs you it’s running, and a closure with a summary after the entire suite has run, telling you what happened.

The challenge was how to add those same closures using the Kotlin DSL. Needless to say it didn’t work without changes. The solution isn’t a radical change, but just finding the right way to define the closures. Here’s the Kotlin DSL version that I worked out:

tasks {    
   withType<Test> {
        beforeTest(closureOf<TestDescriptor>{
            logger.lifecycle("\tRunning ${this.className}.${this.name}")
        })
      afterSuite(KotlinClosure2<TestDescriptor,TestResult,Unit>({descriptor, result ->
            if (descriptor.parent == null) {
                logger.lifecycle("Tests run: ${result.testCount}, Failures: ${result.failedTestCount}, Skipped: ${result.skippedTestCount}")
            }
            Unit
        }))
    }
}

So you can see, you’re still just assigning the closures, and the body of the closures is basically the same, it’s just that the “{}” Groovy closure, needs to be declared more verbosely in Kotlin, with type information.

Also note that the single argument beforeTest closure, and the two argument afterSuite have unique definitions to deal with the differing argument count. Groovy, not caring about types is happy to treat all closures equally and just unpack any number of arguments in the target. Kotlin wants the types of its arguments and to support that Gradle added some crude convenience functions and types. There are types for 0, 1, 2 and 3 argument closures, and some shorthand functions for the common 1 argument scenario. It works. It’s not o horrible. It’s a rough patch that hopefully will get refined over time.

In Closure

I gave just the single example, but the solution I gave will work anywhere you need to apply a closure in Gradle with the Kotlin DSL. You’ll need to know the types, and declare the appropriate KotlinClosureX object.

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s