Tag: bintray

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.

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.

Please follow and like this Blog πŸ™‚
Publish Kotlin Library on Bintray using Gradle Kotlin DSL and Travis CI

Publish Kotlin Library on Bintray using Gradle Kotlin DSL and Travis CI

Distribute a Library on Bintray using Gradle Kotlin DSL

In my latest blog post, published a few weeks back, I informed about the usage of the Gradle Kotlin DSL and how it helps with describing build scripts. In another earlier post, I introduced a small library that can be utilized for simplifying the creation of TLS/SSL sockets using a custom Kotlin DSL: SeKurity.

In this post, we’ll investigate how such a library can be made available to others that actually want to make use of it inside other projects. Ultimately, it should be possible to list the SeKurity library as a simple dependency in a build script like Maven or Gradle. Since the library itself is already backed by Gradle, I’ll show a way of publishing the resulting artifacts at the end of that build.

Where to publish?

In order to make a library available to anybody else, it needs to be published to a publicly accessible e.g. Maven repository like the famous Maven Central. Unless you’re absolutely certain about the quality of your library, it’s recommended to share it on custom repositories rather than THE central one directly as you’ll kinda lose control of it once it’s out there. One alternative is called Bintray and it will also be used in the present article. After creating an account or simply signing in with your GitHub, Google or Twitter account, you can create your own repositories there.

Automating the Build

Before enabling our build to publish the built artifacts, the process should be automated in a CI tool like Travis CI, which interacts nicely with GitHub. To make it work, it must be enabled in the GitHub repo settings by adding it as a service (see Settings -> Integrations & services). Travis expects the repository to incorporate a .travis.yml configuration that tells it what to do. For a simple Java/Kotlin project using Gradle, the following will be sufficient:

language: java

before_install:
- chmod -R +x src
- chmod +x gradlew

script: ./gradlew clean build
deploy:
  provider: script
  script: ./gradlew bintrayUpload -PbintrayUser=$BINTRAY_USER
  -PbintrayApiKey=$BINTRAY_KEY
    on:
      branch: master

It’s normally not necessary to specify the script command explicitly as long as you’re using the default instructions. In the shown case, Gradle runs the build task first before it deploys via the gradle bintrayUpload taks, which we will learn about later. The deployment only happens for the master branch. The gradle deploy takes two arguments $BINTRAY_USER and $BINTRAY_KEY, which contain private data and are therefore securely stored in Travis directly. When the build is run, Travis takes care of replacing them.
You can finally enable the repository in Travis and it will start a build on every push immediately. For further information on Travis, the documentation is highly recommended.

The Gradle Build

As shown earlier, the bintrayUpload Gradle task gets invoked in the build. Where does it come from? Let’s have a look at the relevant parts of the gradle.build.kts file.

The Plugins


plugins {
    kotlin("jvm") version "1.2.30"
    id("com.github.johnrengelman.shadow") version "2.0.2"
    `maven-publish`
    id("com.jfrog.bintray") version "1.8.0"
}
  • The shadow plugin is used because we need the library to be bundled with all its dependencies in order to avoid classpath conflicts.
  • The maven-publish plugin helps with creating the relevant artifact and a relevant pom.xml file that will also be published.
  • The bintray plugin does all the heavy work at the end of the build. It refers to the maven-publish result and pushes the artifacts to Bintray.

Plugin Configuration

The build file also adds custom configurations for all previously shown plugins as shown next:

I Shadow


val artifactID = "sekurity"

val shadowJar: ShadowJar by tasks
shadowJar.apply {
    baseName = artifactID
    classifier = null
}

The shadow plugin uses the project’s name as the artifact’s baseName and the “all” qualifier by default, which is overridden here.

II Maven-Publish


fun MavenPom.addDependencies() = withXml {
    asNode().appendNode("dependencies").let { depNode ->
        configurations.compile.allDependencies.forEach {
            depNode.appendNode("dependency").apply {
                appendNode("groupId", it.group)
                appendNode("artifactId", it.name)
                appendNode("version", it.version)
            }
        }
    }
}

val publicationName = "tlslib"
publishing {
    publications.invoke {
        publicationName(MavenPublication::class) {
            artifactId = artifactID
            artifact(shadowJar)
            pom.addDependencies()
        }
    }
}

This one is a bit tricky. We reuse the result of shadow and add it to the publication which will be published under the artifactId that corresponds to the JAR’s baseName. By default, the generated POM does not list the library dependencies, which is done by mapping each compile dependency to a valid dependency XML node.

III Bintray


fun findProperty(s: String) = project.findProperty(s) as String?

bintray {
    user = findProperty("bintrayUser")
    key = findProperty("bintrayApiKey")
    publish = true
    setPublications(publicationName)
    pkg(delegateClosureOf {
        repo = "SeKurity"
        name = "SeKurity"
        userOrg = "s1m0nw1"
        websiteUrl = "https://kotlinexpertise.com"
        vcsUrl = "https://github.com/s1monw1/TlsLibrary"
        setLabels("kotlin")
        setLicenses("MIT")
    })
}

The bintray plugin is most important since it takes care of actually uploading the generated artifacts to Bintray itself. The user and key are obtained from the Gradle project properties, which are passed by Travis later on as already shown earlier. By referring to the publication created in maven-publish, all artifacts will be uploaded as specified. The pkg block describes the library’s attributes and where, i.e. to what repository, the publishing happens.

The Result

All these blocks merged result in a pretty nice Gradle build script, which can be observed in this Gist and also the SeKurity repository itself. The resulting artifact is made available here: https://bintray.com/s1m0nw1/SeKurity/SeKurity

Referring to the published Library

Now that the library is being uploaded to Bintray, it can be listed as a dependency in arbitrary build files. If we have another Gradle-backed project, we first need to add the relevant repository to the list of repositories:


repositories {
    mavenCentral()
    jcenter()

    maven {
        setUrl("https://dl.bintray.com/s1m0nw1/SeKurity/")
    }
}

After that, the library can be added as a simple compile dependency like this:


dependencies {
//...other dependencies
    compile("de.swirtz:sekurity:0.0.2")
}

Recap

As shown, Gradle plugins can be used for setting up a proper build that publishes a custom library to Bintray easily. In order to automate the whole process, CI tools like Travis can be used for executing the build whenever a new change has been made.

I hope the little tutorial helps with your build as well! If you like, have a look at my Twitter account and follow if you’re interested in more Kotlin stuff πŸ™‚ Thanks a lot.

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.

Please follow and like this Blog πŸ™‚