シンプルなScala用ビルドツール「cbt (Chris's Build Tool)」を試してみる

Slickのコミッタとして有名なJan Christopher Vogtさんが作っているcbt (Chris’s Build Tool) というScala用のビルドツールがあります。*1

github.com

昨年参加したScala Days NewYork 2017でもセッションがあり、興味本位で参加したのですが、sbtのdisなどもありなかなか楽しい発表でした。*2

www.youtube.com

cbtの特徴

Scalaにはsbtという標準的なビルドツールが存在するわけですが、非常に難解なsbtと異なり、cbtは既存のScalaの知識だけでカスタマイズができるという大きな特徴があります。

たとえばコンパイルの前後に処理を挟む場合、compileメソッドをオーバーライドして前後に独自のScalaコードを記述し、super.compileを呼び出せばよいだけです。タスクはただのメソッドなので別のタスクを呼ぶ場合もメソッドを呼び出すだけですし、以下のようにメソッドを定義するだけで独自タスクも簡単に定義することができます。

class Build(val context: Context) extends BaseBuild{
  ...
  def hello = println("Hello CBT!!")
}

cbtを使ってみる

まずはGitHubリポジトリをクローンし、リポジトリのルートディレクトリに環境変数PATHを通します。*3

$ cd ~/
$ git clone https://github.com/cvogt/cbt.git
$ export PATH=$PATH:~/cbt

続いて適当なディレクトリを掘って動作を確認してみます。cbt toolsはファイルの雛形などを生成する便利ツールです。

$ mkdir my-project
$ cd my-project
$ cbt tools createMain

以下のような内容でMain.scalaが生成されます。

object Main{
  def main( args: Array[String] ): Unit = {
    println( Console.GREEN ++ "Hello World" ++ Console.RESET )
  }
}

実行してみます。

$ cbt run
Compiling to /tmp/my-project/target/scala-2.11/classes
[warn] Pruning sources from previous analysis, due to incompatible CompileSetup.
[info] Compiling 1 Scala source to /tmp/my-project/target/scala-2.11/classes...
[info] Compile success at 2017/02/15 14:19:40 [0.457s]
Hello World

このようにコンパイルや実行だけであればビルドファイルを作らなくても実行することができます。

ビルドファイルの作成

ビルドの設定などを行いたい場合はビルドファイルを作成する必要があります。

$ cbt tools createBuild

以下のような内容でbuild/build.scalaが生成されます。コメントでライブラリやマルチプトジェクト構成の場合の設定方法などが説明されています。

import cbt._
class Build(val context: Context) extends BaseBuild{
  override def dependencies =
    super.dependencies ++ // don't forget super.dependencies here for scala-library, etc.
    Seq(
      // source dependency
      // DirectoryDependency( projectDirectory ++ "/subProject" )
    ) ++
    // pick resolvers explicitly for individual dependencies (and their transitive dependencies)
    Resolver( mavenCentral, sonatypeReleases ).bind(
      // CBT-style Scala dependencies
      // ScalaDependency( "com.lihaoyi", "ammonite-ops", "0.5.5" )
      // MavenDependency( "com.lihaoyi", "ammonite-ops_2.11", "0.5.5" )

      // SBT-style dependencies
      // "com.lihaoyi" %% "ammonite-ops" % "0.5.5"
      // "com.lihaoyi" % "ammonite-ops_2.11" % "0.5.5"
    )
}

デイレクトリレイアウトはsbtと同様で、デフォルトではsrc/main/scalasrc/test/scalaなどにソースコードを配置することができます。

jarファイルにパッケージングしたいのでベーストレイトをPackageJars変更します。namegroupIdversionという3つのメソッドを実装する必要があります。

import cbt._
class Build(val context: Context) extends PackageJars{
  override def name = "my-project"
  override def groupId = "com.github.takezoe"
  override def version = "1.0.0"
  ...
}

このようにしておくとsbt packageでjarファイルを作成することができます。

まとめ

実際にcbtを使ってみた感じ、sbtと比べるとカスタマイズしやすそうで好印象です。ただ、現実的にはやはり以下のような点がネックになってきます。

  • IDEのサポートが期待できない
  • 既存のsbtプラグインが利用できない

特に既存のsbtプラグインが利用できないというのは大きな欠点です。例えばサーブレットベースのWebアプリケーションを開発するためのxsbt-web-pluginやsbtプラグインでテンプレートをScalaコードにコンパイルするTwirlなども使えません。もちろん開発環境をがっつりsbtに依存しているPlayアプリケーションの開発を行うこともできません。

現時点ではネタ感を拭えないのが正直なところですが、開発は活発に続けられていますし*4、選択肢があることは重要だと思うので、Chrisさんには是非とも頑張っていただきたいところです。

*1:自分の名前をつけてしまうセンスはどうなのかという話はここでは置いておきます。

*2:動画だけでなくスライドも公開されています。 https://docs.google.com/presentation/d/1dZ7f6cETFecfjiqQvVsYtXdUQxZzan8gS_XJZoWbF7M/

*3:あわせてnailgunを入れておくと起動が速くなったり、gpgを入れておくとpublishSignedタスクを実行できるようになったりするようです。Macの場合はbrewで入れておくとよいでしょう。

*4:Chrisさんのモチベーションがいつまで続くのかという不安はありますが…。