Recently I’ve decided to try to migrate the project I’m working on to Gradle Kotlin DSL. The project contains three application modules and a few library modules. It was based on Gradle 4.10.2 and Android Gradle plugin 3.2.1 before migration, now it is Gradle 5.1 and Android Gradle plugin 3.3.0-rc3. All Gradle files were converted to Kotlin. Here are some takeaways from the process of migrating the project.

👍 The final result is deeply satisfying

Groovy-based Gradle projects were hard in maintenance, and the IDE support for them was very poor. For example, in many cases navigating between the definitions of extra properties and their usage was impossible. Now the issues are gone and the IDE support for Gradle Kotlin DSL is first-class. The only problem is that sometimes, after opening your .gradle.kts file, you need to wait a couple of seconds for the editor to refresh the data and initialize code highlighting.

👍 Managing dependencies is much easier

This was also possible to set up in Groovy-based projects, but during the migration I’ve changed the way we manage dependencies versions from extra properties to Kotlin files generated by jmfayard’s buildSrcVersions plugin.

// Groovy

implementation 'com.squareup.okhttp3:okhttp:$okhttpVersion' // Kotlin

implementation("com.squareup.okhttp3:okhttp:${extra["okhttpVersion"]}") // Kotlin, after migrating to buildSrcVersions

implementation(Libs.okhttp)

The buildSrcVersions plugin also makes updating the dependencies a lot easier: all you need to do is run ./gradlew buildSrcVersions and edit the Versions.kt file generated in your buildSrc module:

// buildSrc/src/main/kotlin/Versions.kt // Before buildSrcVersions task

object Versions {

const val com_squareup_okhttp3: String = "3.12.0"

} // After buildSrcVersions task

object Versions {

const val com_squareup_okhttp3: String = "3.12.0" // available: "3.12.1"

} // After manual update

object Versions {

const val com_squareup_okhttp3: String = "3.12.1"

}

👍 Now it’s time to drop extra properties

Extra properties in Gradle were often abused. Most of the projects use them to keep additional information (like version name or version code), but also I’ve seen examples of keeping functions in extra properties to make them available at the right moment of preparing or executing the Gradle script. Also, you can’t structure them in any convenient way.

In Gradle Kotlin DSL, using extra properties is more complicated since there is a standardized way of accessing extra properties ( extra["property"] ), there are more type-safety restrictions than in Groovy, and extra properties API returns Any? . As a result, you often need to perform an unsafe cast to make the script compile:

// Groovy

ext {

projectCompileSdk = 28

} android {

compileSdkVersion projectCompileSdk

} // Kotlin

extra["projectCompileSdk"] = 28 android {

compileSdkVersion(extra["projectCompileSdk"] as Int)

}

The better option is to consistently keep all the additional information in Kotlin code in the buildSrc module:

// buildSrc/src/main/kotlin/ProjectVersions.kt

object ProjectVersions {

const val COMPILE_SDK = 28

} // app/build.gradle

android {

compileSdkVersion(ProjectVersions.COMPILE_SDK)

}

👍 Extension methods FTW

Another discovery was how easy we could extend the Gradle APIs with extension methods. Suppose you often use some command-line custom property in your Gradle scripts. For example, your CI can pass build number to use it as a version code:

./gradlew -PciBuildNumber=%BUILD_NUMBER% assembleDebug

Now you can prepare an extension method in your buildSrc module and use it wherever you want, without a need to import anything.

// buildSrc/src/main/kotlin/Extensions.kt

fun Project.getCiBuildNumber() = if (project.hasProperty("ciBuildNumber") property("ciBuildNumber") else null // app/build.gradle

android {

defaultConfig {

versionCode = getCiBuildNumber() ?: 1

}

}

👍 Defining buildTypes and flavors: getByName() vs create()

It’s a small thing but makes your application definition more descriptive. In Gradle Kotlin DSL, you need to use getByName() or create() in buildTypes and flavors blocks (depending on if you’re altering already existing configuration or you’re creating a new one):

// Groovy

buildTypes {

debug {

...

}

release {

...

}

anotherbuildtype {

...

}

} // Kotlin

buildTypes {

getByName("debug") {

...

}

getByName("release") {

...

}

create("anotherbuildtype") {

...

}

}

People now can understand which of the build types are generic and which were manually added to the module.

👎 The migration process is painful

The good news is that you can migrate your Groovy-based files one by one (you can have both Groovy-based and Kotlin-based Gradle scripts in your project at the same time). The bad news is that migrating a single file is not so easy, especially when it’s a complex one.

I’ve started migrating my files by changing their extensions from .gradle to .gradle.kts and trying to make them compile: change string literals from single quotes to double quotes etc. The problem is that you won’t get any IDE support unless you make it compile; all you have are the errors reported by the script compiler in its output. Sometimes the better strategy for migrating the file is to comment out all of its content and rewrite it block-by-block. Both approaches require a lot of manual changes, and there is no way of auto-migration (and I guess it would be hard to prepare such a mechanism). So be prepared to spend quite a lot of time on migrating the project to Gradle Kotlin DSL. My migration process took one and a half days.

👎 The APIs are not prepared for Kotlin

This mostly applies to APIs exposed by the Android Gradle plugin and are related to Java-Kotlin interop.

You call compileSdkVersion(28) method in one place but make an assignment targetSdkVersion = 28 two lines below.

In the lintOptions block, you can set isCheckReleaseBuilds = true (and also other similar properties which don't sound good).

Sometimes you need to find out a non-standard way of accessing the APIs, as in this example of settings maxParallelForks (https://github.com/gradle/kotlin-dsl/issues/440):

// Groovy

testOptions {

unitTests {

returnDefaultValues = true

all {

maxParallelForks = Runtime.runtime.availableProcessors()

}

}

} // Kotlin

testOptions {

unitTests.apply {

isReturnDefaultValues = true

all(KotlinClosure1<Any, Test>({

(this as Test).also {

maxParallelForks = Runtime.getRuntime().availableProcessors()

}, this))

}

}

}

Some changes were introduced in the newer versions of the Android Gradle Plugin. Before version 3.3.0-alpha11 you had to call setSourceCompatibility(JavaVersion.VERSION_1_8) , now you can just make an assignment like in Groovy. But I guess that a significant revamp of the APIs needs to happen anyway.

👎 More code generation is needed

Suppose you have an additional build type and you have to specify its dependencies. One of the examples can be setting up LeakCanary:

debugImplementation(Libs.leakcanary_android)

releaseImplementation(Libs.leakcanary_android_no_op)

"anotherbuildtypeImplementation"(Libs.leakcanary_android_no_op)

As you can see, there is no method generated for defining a dependency related to the custom build type. We need to use an extension method on String, and we don’t have any check for the correctness of that call until we resync the project.

👎 Performance

Migrating to Gradle Kotlin DSL increased build times on our CI by ~50 seconds. It is a noticeable value for some of the jobs (like running static analysis which took ~1m15s and now it is ~2m5s). Most likely this is the time needed to compile Kotlin DSL scripts. There are some issues raised for this (https://github.com/gradle/kotlin-dsl/issues/902) so I expect some improvements in the upcoming versions of Gradle.

👎 Not so many examples on the web

I’ve found only one complex open-source project that is migrated to Gradle Kotlin DSL and has a clean implementation of it so I could use it to check if I’m doing well with my migration process (https://github.com/DroidKaigi/conference-app-2018). Samples provided by Gradle Kotlin DSL team (https://github.com/gradle/kotlin-dsl/tree/master/samples) are very basic but sometimes useful. In general, you’re on your own if you have a more complex project: use many plugins, or your Gradle scripts have some custom logic.

Is now the time to migrate to Gradle Kotlin DSL?

As I’m writing this article at the beginning of January 2019, the Gradle Kotlin DSL is considered to be stable and ready to be used. I found no critical bugs during the migration process (I was able to migrate all the Gradle scripts successfully), but I didn’t merge my changes due to increased build time. I can’t say if performance issues affect all the projects. The good idea is to monitor your build performance at the each of the steps of the migration process: you can quickly decide if you go further with it or wait for some more improvements. One thing is clear: Gradle’s switch from Groovy to Kotlin is a step in the right direction.