nがひとつ多い。

えぬなおの技術的なことを書いていくとこ。

【kotlin】kotlinで書いたスクリプトを叩きたい人生だった

はじめに

本記事は Kotlin Advent Calendar 2019の9日目の記事です。

この記事で書くこと

kotlinのプロジェクトを管理していると、 いわゆる本体のアプリケーション以外にも、 DBのマイグレーションやちょっとしたデプロイスクリプトみたいな処理を書いて、 その1枚ペラの.ktファイルだけを実行したい時があったりします。

それらをコンパイルして javaで実行してもいいですが、いちいちそんなことで jarファイルを作りたくないですし、 javaclassPath指定とかめっちゃ面倒です。

そんな時どうすればいいのか、代表的な三つの方法を書いていきます。

  • ktsファイルを書いて実行する。
  • kotlinで書いてgradleで叩く。
  • kscriptを使う。

①ktsファイルを書いて実行する。

王道です。

println("Hello World!")

と適当な Main.kts を書いて、以下のように実行します。

kotlinc -script Main.kts 

うーん簡単に叩けるねえ。 しかしこれは本当に簡単なスクリプトの場合はで、ライブラリを使うとちと面倒で、

https://blog.jetbrains.com/kotlin/2018/09/kotlin-1-3-rc-is-here-migrate-your-coroutines/#scripting

kotlinの13.0RCから入った次のようなannotationで依存関係を記載することなどで実行可能です。
以下のような sample.main.kts を作り、

@file:Repository("https://jcenter.bintray.com")
@file:DependsOn("org.jetbrains.kotlinx:kotlinx-html-jvm:0.6.11")

import kotlinx.html.*
import kotlinx.html.stream.*

print(createHTML().html {
    body {
        h1 { +"Hello, World!" }
    }
})

以下のように実行します。

kotlinc -cp <path/to/kotlin-main-kts.jar> -script sample.main.kts

②kotlinで書いてgradleで叩く。

「え?.ktsファイルで書かないの?」って感じですが、たまにはpureなkotlinを実行したい時もあります。 一番簡単なのが、gradleのなかで JavaExecなtask として定義してしまうことです。

package nnao4.task

import kotlinx.html.*
import kotlinx.html.stream.*

fun main() {
    print(createHTML().html {
        body {
            h1 { +"Hello, World!" }
        }
    })
}
sourceSets {
    main.kotlin.srcDirs = main.java.srcDirs = ['src/main/kotlin']
}

task runTask(type: JavaExec) {
    main ='nnao4.task.MainKt'
    classpath = sourceSets.main.runtimeClasspath
}

これだけでもなんと、依存モジュールも併せて通常通りコンパイルするので実行できてしまいます。 .ktsでは書きにくいスクリプトもこれで簡単に実行できるようにできますね。

gradle runTask

③kscriptを使う。

出ました外部ライブラリ。

github.com

どうやら kotlinc のラッパーのようで、 以下が特徴的な機能だそうです。

In particular this wrapper around kotlinc adds

  • Compiled script caching (using md5 checksums)
  • Dependency declarations using gradle-style resource locators and automatic dependency resolution with jcabi-aether
  • More options to provide scripts including interpreter mode, reading from stdin, local files or URLs
  • Embedded configuration for Kotlin runtime options
  • Support library to ease the writing of Kotlin scriptlets
  • Deploy scripts as stand-alone binaries

高性能でテンション上がりますね😊使っていきましょう。

sdkmanを使えばinstallは一撃

sdk install kscript

さて肝心の使いっぷりですが、割と衝撃を受けます。

#!/usr/bin/env kscript

println("Hello from Kotlin!")
for (arg in args) {
    println("arg: $arg")
}

え・・・そういうこと・・・!? 実行ファイルから実行できるの!?( ´ ▽ ` ;)

ということは標準出力からも原理的に可能ですね、

kscript 'println("hello world")'

まじか・・・ちゅご・・・。 しかも衝撃的なことに、依存モジュールも対応しちゃってます!

#!/usr/bin/env kscript
//DEPS com.offbytwo:docopt:0.6.0.20150202,log4j:log4j:1.2.14

import org.docopt.Docopt
import java.util.*


val usage = """
Use this cool tool to do cool stuff
Usage: cooltool.kts [options] <igenome> <fastq_files>...

Options:
 --gtf <gtfFile>     Custom gtf file instead of igenome bundled copy
 --pc-only           Use protein coding genes only for mapping and quantification
"""

val doArgs = Docopt(usage).parse(args.toList())

println("Hello from Kotlin!")
println("Parsed script arguments are: \n" + doArgs)

当然、↑の方でも書いたようなアノテーションで依存性解決をすることもできます。 便利ですね(^▽^)ktsとしてjavaモジュール使ってなんか適当な関数発火するならこっちでいいのかもしれません。

おわりに

サーバサイドkotlinは使い勝手がよく、AndroidJavaの資産を両方に使える器用なやつです。 サーバサイドとしてはバッチ処理なども任せることも今後も多くなってくるでしょう。 そんな時に上記の方法が頭に残っていると便利かもしれません。