こんにちは、キャスレーコンサルティングID(システム・インテグレーション)部のfkmtです。
Java10では、「var」が使えるようになったそうですね。
Java8でLambdaが出てきた時にも思いましたが、もうScala使えばいいのに。
というワケで、今回はScala、、、というかSBTについて、書こうと思います。
テーマは、SBTプラグインです。
SBTとは、Scala製のビルドツールで「Simple Build Tool」の略です(でした?)。
> バージョン0.13までは、SBTのソースコードのヘッダに「sbt — Simple Build Tool」と書いてありました。
> 1.xからは、「Simple Build Tool」の記載はなくなっています。
SBTについては、(いささか古いですが)このブログ↓でもご紹介しています。
【scala初心者向け】sbtを触ってみよう!
Hello World
まずは、定番「Hello World」を。
カスタムタスク
さっそくプラグインを作って、、、といきたいところですが、
まずは、build.sbtに直接カスタムタスクを書いて、挙動を確認します。
SBTではKeyとTaskを使ってカスタマイズします。
KeyにはSettingKey、TaskKey、InputKeyという種類があって、、、
と、詳しい説明をしていると眠くなるので、サクっと書いてしまいましょう。
build.sbt
// プロジェクトの基本設定 lazy val root = (project in file(".")) .settings( scalaVersion := "2.12.5", name := "hello-world" ) // Keyの定義 // SBTから`hello`で呼び出せるようになる。 val hello = taskKey[Unit]("hello world") // helloの実装 hello := { println("hello world") }
これだけで `sbt hello`
と叩くと、以下のように表示されます。
sbt hello #> hello world
引数を使ってみる
先の例では、「hello world」と表示するだけでしたが、
次は「world」の部分を、引数から取得するようにしてみます。
build.sbt
// Keyの定義 // SBTから`hello`で呼び出せるようになる。 val hello = inputKey[Unit]("hello world") // helloの実装 hello := { val args: Seq[String] = complete.DefaultParsers.spaceDelimited("<arg>").parsed println("args.size=" + args.size) println(s"hello ${args.head}!!") }
> プロジェクトの基本設定の部分は変わらないので省略
「hello」の定義を、「taskKey」から「inputKey」に変更しました。
引数を取って処理する場合は、「inputKey」を使います。
また、タスクの実装では引数を処理するために、パーサを使います。
デフォルトで用意されているパーサの中から、引数を空白で区切ってくれるパーサを選びました。
実行してみると、以下のようになります。
sbt "hello hoge" #> args.size=1 #> hello hoge!! sbt "hello hoge fuga" #> args.size=2 #> hello hoge!!
プラグイン化してみる
基本的な挙動が分かったところで、これをプラグイン化してみます。
プラグインの基本的なことは、だいたいココ↓に書いてあります。
https://www.scala-sbt.org/1.x/docs/Plugins.html
モノスゴク要約すると、
- 1. 普通にSBTプロジェクトを作る
- 2. 「build.sbt」に `sbtPlugin := true` と書く
- 3. sbt.AutoPluginをextendsした、Objectを作る
以上です。
考えるな、感じろ
ドキュメントを読んだだけでは、理解したつもりになるだけなので、実際に手を動かして感じましょう。
build.sbt
lazy val root = (project in file(".")) .settings( organization := "jp.co.casleyconsulting", version := "0.1.0-SNAPSHOT", name := "hello-plugin", sbtPlugin := true )
注目点は、 `sbtPlugin := true`
です。
これだけでプラグインになります。
HelloPlugin.scala
package jp.co.casleyconsulting.helloplugin import sbt._ object HelloPlugin extends AutoPlugin { // これを書いておくと、プラグイン利用者が面倒なimportを書かなくてすむ object autoImport { val hello = inputKey[Unit]("hello") } import autoImport._ // このプラグインが有効化されるトリガ // `allRequirements`はこのプラグインの依存性が全て解決されたら有効化される override def trigger = allRequirements // プロジェクトのビルドセッティングにこのプラグインを設定する override lazy val buildSettings = Seq( hello := helloTask.evaluated // 引数を取らないTaskのときは `helloTask.value` になる ) // helloタスクの実装 lazy val helloTask = Def.inputTask { val args: Seq[String] = complete.DefaultParsers.spaceDelimited("<arg>").parsed println(s"hello ${args.mkString(",")} !!") } }
build.sbtに、直接カスタムタスクを書いたときよりも、少し記述量が増えます。
各コードが何をしているのかは、コード中のコメントをご参照ください。
カスタムタスクでは、定義したKeyに直接実装を書いていましたが
プラグイン化した今回は、行儀のよい(?)書き方にしてみました。
タスクの実装は、「Def.inputTask」を使って別途定義し、
定義したタスクの実装「evaluated」を呼んで、Keyにセットしています。
注意していただきたい点は、 `hello := helloTask.evaluated`
で、
この「evaluated」はInputTaskの場合に使い、引数を取らないTaskの場合は「value」を使います。
また、ついでに今回は引数が複数ある場合にも、対応してみました。
使ってみる
さて、プラグインはできたので使ってみます。
まずは、作ったプラグインをローカルインストールします。
`sbt publishLoadl`
と叩くと、ivyのローカルリポジトリ(~/.ivy2/local/
)にプラグインが配置されます。
ローカルインストールが済んだら、適当なSBTプロジェクトを作ってプラグインをロードします。
sbt new sbt/scala-seed.g8 --name="sample" cd sample cat <<'EOF'> project/plugins.sbt addSbtPlugin("jp.co.casleyconsulting" % "hello-plugin" % "0.1.0-SNAPSHOT") EOF sbt "hello hoge fuga piyo" #> hello hoge,fuga,piyo !!
できましたー!
まとめ
今回は、SBTプラグインの入門的な内容をご紹介しました。
SBTの公式ドキュメントをなぞった程度ですが、文章を読むよりコードを見て触った方が理解が進むと思い
できるだけ説明は省いて、コード主体で書いてみました。
今回は触れていませんが、プラグインのテストする仕組み(scripted test framework)もあるようです。
自分も試したことがないので、機会があればまたご紹介できればと思います。