Tag: kts

Execute Kotlin Scripts with Gradle

Execute Kotlin Scripts with Gradle

Organize Kotlin Scripts as Gradle tasks

In this article, you will learn how you can organize multiple Kotlin scripts as Gradle tasks and make them easily executable this way. I’ve found a discussion about this here. Somebody wanted to execute Kotlin scripts with Gradle build scripts which is, of course, possible by using kotlinc as shown in this (Groovy) build script. This doesn’t look very pretty though, and, as described in the corresponding thread, isn’t very performant and manageable. Another solution would be to use Gradle scripts written with the Kotlin DSL and define custom tasks within a build.gradle.kts file, which obviously can hold and run Kotlin code naturally:

// build.gradle.kts
//
// Execute Kotlin task with:  gradle  -q foo

task("foo") {
  group = "com.kotlinexpertise"
  description = "my foo task"
  doLast {
    println("Hello from foo task")
  }
}

The problem with this approach is that, in my case, I had multiple large Kotlin scripts I wanted to make executable this way. If I had put all of them into tasks, the script would have been too bloated and hard to maintain. Note that in my case, these tasks would not contribute to the build logic directly but rather provide business-relevant tasks, which I wanted to make executable via Gradle.

Gradle buildSrc to the rescue

As described in the Gradle documentation, build logic and especially custom tasks shouldn’t live within the build script directly. Gradle, therefore, offers the possibility to use a so-called buildSrc directory. This directory is treated as an included build, i.e. Gradle automatically compiles and tests this code and makes it available on the build script classpath. The following shows a typical project structure using this special buildSrc directory:

├── build.gradle //main build
├── buildSrc
│   ├── build.gradle //build for buildSrc
│   └── src //custom plugins, taks etc.
│       ├── main
│       │   └── java
│       │       └── com
│       │           └── enterprise
│       │               ├── Deploy.java
│       │               └── DeploymentPlugin.java
│       └── test
│           └── java
│               └── com
│                   └── enterprise
│                       └── DeploymentPluginTest.java
└── settings.gradle

As you can see here, the buildSrc has its own build.gradle, in which we define dependencies and plugins for buildSrc itself. As mentioned, the compiled code will be available for the surrounding build so that you can for instance define custom tasks in the buildSrc and use them in the main build.gradle.

A Kotlin example: Execute Kotlin Scripts with Gradle

Let’s say we have two bigger tasks we want to make available as Gradle tasks, which we call task1 and task2. Both tasks are good fits to be implemented with Kotlin.

The original project build looks like this:

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    kotlin("jvm") version "1.2.51"
}

java.sourceSets {
    getByName("main").java.srcDirs("...")
}

repositories {
    mavenCentral()
}

dependencies {
    compile(kotlin("stdlib-jdk8"))
}

tasks.withType<KotlinCompile> {
    kotlinOptions.jvmTarget = "1.8"
}

This is very basic and there’s nothing special in it. Now, the goal is to define two custom tasks taks1 and task2 within this script. Both tasks are supposed to be executable via gradle -q task1|task2. To make this possible, we create the buildSrc directory structure as shown above on the same level the build script already exists. Within the buildSrc, we now create the custom tasks as Kotlin classes:

package com.kotlinexpertise.tasks

import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction
import java.sql.DriverManager

open class Task1 : DefaultTask() {

    init {
        group = "com.kotlinexpertise"
        description = "task1"
    }

    @TaskAction
    fun run() {
        Class.forName("com.mysql.jdbc.Driver")
        //heavy task implementation
    }
}

Just like we would define a task within a Gradle build script directly, we define group, description and the task itself in a method annotated with TaskAction, which we call run. Learn more about custom tasks here. To make this a bit more interesting, we want to load a MySQL driver in this task, which requires us to make the corresponding dependency available for the buildSrc build. Let’s take a look at its build script (existing directly in buildSrc):

plugins {
    `kotlin-dsl`
}

repositories {
    mavenCentral()
}

dependencies {
    compile("mysql:mysql-connector-java:5.1.24")
}

As mentioned, we add the dependencies we want to use within the buildSrc. Another thing to note is that the org.gradle.kotlin.kotlin-dsl plugin is applied to this build in order to set up the Kotlin compiler features and dependencies to match the ones shipped with Gradle. We used the nicer alias kotlin-dsl in this example.

Now, after defining both Task1 and Task2 as subclasses of DefaultTask, they can be used in the main build.gradle.kts script like this:

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import com.kotlinexpertise.tasks.*

//...

task<Task1>("task1")
task<Task1>("task2")

//...

Note that both probably need to be imported as shown here. In the console, gradle tasks will list them, and you can execute them by running gradle -q task1 and gradle -q task2 respectively. You can also see the tasks listed in IntelliJ IDEA:

gradle_idea

The source code can be found here: https://github.com/s1monw1/gradle_buildSrc_kotlin

You may want to read another article of mine about the Gradle Kotlin DSL to get more information about the topic. Enjoy.

Please follow and like this Blog 🙂

Simon is a software engineer based in Germany with 7 years of experience writing code for the JVM and also with JavaScript. He’s very passionate about learning new things as often as possible and a self-appointed Kotlin enthusiast.

Run Kotlin Scripts (kts) from regular Kotlin Programs

Run Kotlin Scripts (kts) from regular Kotlin Programs

Run Kotlin Scripts from Kotlin Programs

This article presents a way to run Kotlin scripts from Kotlin programs in order to leverage the power of DSLs.

Kotlin can be used as a scripting language. Simply write top-level executable code inside a file with .kts extension and run it with the kotlinc as described in the documentation. That’s also the format of Gradle build files that are used in combination with the Gradle Kotlin DSL like this gradle.build.kts. Gradle shows a fantastic example of a domain specific language that can be written standalone in .kts files to be read by the gradle tool later on. When we try to find a way to do the same with custom DSLs (Tutorial can be found here), we first need to know how to run Kotlin scripts from Kotlin programs. The article reveals how to do so.

The Java Scripting API (JSR-223)

The Java Scripting API is a tool for using scripting engines (such as Nashorn) from Java code. It enables users to write customizable scripting code that can be picked up by the Java application at runtime. In a way, the API is a neat way of writing extensible applications.

As of Kotlin 1.1, the corresponding JSR-223 is supported for Kotlin Scripts, too. That means that it’s possible to run Kotlin scripts from regular Kotlin programs in order to make applications customizable through these scripts.

Using the Kotlin Script Engine

In order to use the mentioned Kotlin script engine, a file called javax.script.ScriptEngineFactory has to be placed inside META-INF/services of your application. It should contain the following entry: org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory.
After that, the javax.script.ScriptEngineManager will be able to find the corresponding engine when looked up via ScriptEngineManager().getEngineByExtension("kts"). This code now finds the Kotlin ScriptEngine implementation, an instance that can be used to evaluate String-based scripts such as "5 + 2", or directly read scripts from the file system. Here’s a short example:

with(ScriptEngineManager().getEngineByExtension("kts")) {
    eval("val x = 3")
    val res2 = eval("x + 2")
    assertEquals(5, res2)
}

You could also compile scripts and evaluate them later:

val script = compile("""listOf(1,2,3).joinToString(":")""")
assertEquals(listOf(1, 2, 3).joinToString(":"), script.eval())

Wrapping the glue code in a library

As shown, executing Kotlin scripts from Kotlin programs is pretty easy due to the Java scripting API implementation for Kotlin. Nevertheless, since it’s a bit cumbersome to integrate the support into an application, I wrote a tiny library that encapsulates the Scripting API glue code. It is called KtsRunner and can be found on GitHub.

The KtsRunner is a lightweight tool for executing Kotlin scripts from your custom applications. The API, as of the very first version, provides a slim KtsObjectLoader class whose usage is shown in the following example:

data class ClassFromScript(val x: String)
import de.swirtz.ktsobjectloader.ClassFromScript

ClassFromScript("I was created in kts")

The previous snippets show the definition of some arbitrary data class and the code that instantiates an object of it. The object instantiation is basically what we write into a .kts file.

val scriptReader = Files.newBufferedReader(Paths.get("path/classDeclaration.kts"))
val loadedObj: ClassFromScript = KtsObjectLoader().load<ClassFromScript>(scriptReader)
assertEquals("I was created in kts", loadedObj.x)

Using the KtsObjectLoader makes it simple to load the correspoding object of ClassFromScript from the script file. Alternatively, the script could also be provided as a String:

val scriptContent = "5 + 10"
val result: Int = KtsObjectLoader().load<Int>(scriptContent))
assertEquals(15, result)

Adequate Usage Scenario

As mentioned in the beginning, it can make sense to make your application customizable through external scripts, similar to how Gradle can be extended with any custom build script. Imagine an application that provides a test suite runtime. The actual test cases are provided by technical testers who write their test scripts using a domain specific language that is provided by the main application. Since you don’t want testers to add source files (defining new test cases) to your application all the time, the test case creation is made in independent .kts files in which the DSL is utilized by the testing team. The test suite main application can use the presented KtsRunner library for loading the test cases provided in .kts files and process them further afterward.

An example

A pretty popular DSL for Kotlin is kotlinx.html, a language for describing type-safe HTML. You let the client of your application provide some arbitrary HTML that you want to render at a later time. The HTML DSL code is provided as .kts script files and might look like this:

import kotlinx.html.*
import kotlinx.html.dom.create
import org.w3c.dom.Element
import java.io.OutputStream
import java.io.OutputStreamWriter
import javax.xml.parsers.DocumentBuilderFactory

val document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument()
document.create.html {
    head {
        title("Hello world")
    }
    body {
        h1("h1Class") {
            style = "background-color:red"
            +"My header1"
        }
        p("pClass") {
            +"paragraph1"
        }
    }
}

When executed, an instance of org.w3c.dom.Element is created that contains the described HTML code in an XML document:

<?xml version="1.0" encoding="UTF-8"?><html>
    <head>
        <title>Hello world</title>
    </head>
    <body>
        <h1 class="h1Class" style="background-color:red">My header1</h1>
        <p class="pClass">paragraph1</p>
    </body>
</html>

That’s straightforward but the interesting part is that the script should actually be executed from the main program. For this purpose, we add the KtsRunner to the application by adding a repository and the dependency itself to the Gradle build file:

maven { 
    setUrl("https://dl.bintray.com/s1m0nw1/KtsRunner")
}
dependencies {
    //...
    compile("de.swirtz:ktsRunner:0.0.x")
}  

The final code for loading the Element from the external script looks as follows:

KtsObjectLoader().load<Element>(script)

Simple, isn’t it? Unfortunately, the shown Scripting API implementation for Kotlin is rather slow and you’ll definitely notice some performance constraints. Altogether, the KtsRunner is a very tiny tool that only encapsulates the glue code for enabling Kotlin Scripting support in random applications. The library is published on bintray and can therefore easily be used from your own application.

Please follow and like this Blog 🙂

Simon is a software engineer based in Germany with 7 years of experience writing code for the JVM and also with JavaScript. He’s very passionate about learning new things as often as possible and a self-appointed Kotlin enthusiast.