uncategorized

Gradle Build Process with Kotlin DSL

In this post we will be setting up our backend build tool. The chosen one for us is Gradle and its new Gradle Kotlin DSL variant, which allows you to write your Gradle scripts type safely with Kotlin. Lovely!

We will start by creating an empty Gradle project. I usually do this via IntelliJ to get my IDE give me assistance on creating these. Let’s take a look how it would work:

Creating Gradle Project

With IntelliJ this is fairly straight forward. Notice how we didn’t select for our source root to be created automatically. This is due to good practices, it is better to wrap Gradle modules into a parent for better movability and also in general keep modules small. We’ll touch reasons behind that a bit more in our third post of the series. The folder structure of your project should look somewhat similar to this:

Gradle folder structure

To get Gradle Kotlin Script plugin to work properly we can use a Gradle wrapper. This way we can easily inject new Kotlin DSL and keep our Gradle version up to date. We will also match the versions of our Gradle wrapper to the correct one so we can use Kotlin Gradle DSL. The latest version can be found from Gradle’s artifactory. We will add this line to our Gradle wrapper properties to get the correct Gradle version.

Kotlin DSL repository setup

Next we’ll introduce the Kotlin DSL for our Gradle scripts. This can be done by modifying the settings.gradle file and adding a notion of buildFileName into it. Note that this isn’t necessarily needed on the new versions Gradle Kotlin Script DSL but gives a good helper for users unfamiliar with the structure (it also helps since on older versions Gradle Kotlin Script doesn’t work without it). The name of the file needs to end in gradle.kts for IntelliJ and Gradle Kotlin Script plugin to pick it up correctly. For top level scripts this doesn’t make much of a difference, it is more important to remember this convention deeper in the belly of the application.

We will also create a new file called gradle.properties with a one line property definition to help us a little bit with Kotlin accessors in our Gradle scripts. The line is:

1
org.gradle.script.lang.kotlin.accessors.auto=true

After these additions we are ready to generate our build script. We will create a file called build.gradle.kts with the following contents:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
allprojects {
group = "co.ilities.kotlin-gradle"
version = "1.0"
repositories {
jcenter()
}
}
plugins {
base
}
dependencies {
subprojects.forEach {
archives(it)
}
}

There is not much to tell your offspring about that file, so we will skip the explanations completely. Remember that this is just a wrapper module in our multimodule Gradle project.

That is all fine and dandy, we have bootstrapped our Gradle wrapper module and added our first Kotlin script into the project. Next we’ll add in some code as well. We will create a new Gradle submodule for the backend code. This will host the lot of our JVM code that we will eventually add to the project. The creation itself follows familiar paths, again we will seek assistance from IntelliJ and ask it to add a new module to the project.

Kotlin backend submodule

little bit of modifications again to get our Gradle to be typesafe with Kotlin instead of dynamic Groovy and we are good to go. We will create a folder structure like Gradle expects and will also create a build.gradle.kts Kotlin Script file. IntelliJ generated a build.gradle already but for now we know what we are doing and will simply just delete that file.

Kotlin backend submodule folder structure

Our replacement, build.gradle.kts, looks like the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import org.gradle.kotlin.dsl.*
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm")
application
}
dependencies {
compile(kotlin("stdlib"))
}
val project = mapOf(
name to "fulstak-backend"
)
tasks.withType<KotlinCompile> {
kotlinOptions {
jvmTarget = "1.8"
}
}
application {
mainClassName = "co.ilities.HelloWorldKt"
}

Few things to note on this. We add 2 plugins to our application, kotlin("jvm") and application. The first one comes as a courtesy of of Gradle-Kotlin-DSL, the second one is a normal Gradle plugin. We also create a holder for our project values in a form of a map and give a name to our module. For our Kotlin compilation we pass in a jvmTarget, so that Kotlin compiler knows we are targeting Java 8. The last line of our build file defines our usage of application plugin and points to our main class, that we have created within our sources. The main class is simple and contains just a main method and hello world print statement.

After we have completed these, we are now ready to run our minimal Gradle set up application. Again we will use IntelliJ and its brilliant features to help us. This time a Gradle plugin.
This IntelliJ Gradle plugin just runs our commands through the IDE, we could as well use our command line and write out the commands. Though where’s the fun in that? Definitely not in the guessing game of correct commands.

Running code via IntelliJ via Gradle

Nicely done, we can finally run a Hello world in our project.

For funsies, we will also add a JAR file creation possibility to our Gradle Kotlin script, this way we can create a so called fat jar that we can eventually run only with a JRE without the need for a full JDK installation. This also enables us to run our application from the command line by calling the created JAR file. The addition is in reality just a way for us to define MANIFEST.MF for our JAR archive. To be fair, this process is somewhat more complicated than in standard Groovy version of Gradle build files. Without it our Java wouldn’t be able to find the main class of the application.

Creating a fat jar and running via command line

Here is the final build.gradle.kts file for our inner module.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import org.gradle.api.file.CopySpec
import org.gradle.jvm.tasks.Jar
import org.gradle.kotlin.dsl.*
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import kotlin.collections.set
plugins {
kotlin("jvm")
application
}
dependencies {
compile(kotlin("stdlib"))
}
val project = mapOf(
name to "fulstak-backend"
)
tasks.withType<KotlinCompile> {
kotlinOptions {
jvmTarget = "1.8"
}
}
application {
mainClassName = "co.ilities.HelloWorldKt"
}
val fatJar = task("fatJar", type = Jar::class) {
baseName = "${project.name}-fat"
manifest {
attributes["Main-Class"] = "co.ilities.HelloWorldKt"
}
from(
configurations.runtime.map {
if (it.isDirectory) it else zipTree(it)
}
)
with(tasks["jar"] as CopySpec)
}
tasks {
"build" {
dependsOn(fatJar)
}
}

The new fatJar task is executed on command build. It is bound to a Jar class from from Gradle JVM tasks and within the block we just define attributes to it like name, main class location and configuration handling.

Share