倭マン's BLOG

くだらない日々の日記書いてます。 たまにプログラミング関連の記事書いてます。 書いてます。

sbt でのコード生成を伴うプロジェクト with IntelliJ IDEA

機械的なコードがたくさんありすぎてコピペで対処するのが面倒になってきたので、sbt でコンパイル時に自動でコード生成を行わせる設定を試してみた。 まぁ、大抵のビルドツールでサポートされてる機能なので、普通にドキュメントの通りにすればいいだけと言えばそれまでですが、IDE が絡んでくるとちょっと余計な設定が必要になることもあります。 ここでは IntelliJ IDEA を使用する場合を書いていきます。(sbt 0.13)

この記事の目次

参考


sbt のみを使う場合

まずは通常の sbt プロジェクトでコンパイル時にコード生成を行う方法を見ていきましょう。 簡単なものなら build.sbt にちょっとコードを追加すればいいだけです。 ソースコード(メインコード、製品コード)のコンパイル時にコードを自動生成したい場合は build.sbt に以下のコードを追加するだけです(sbt の対話モードが起動している場合は、build.sbt の設定後に reload コマンドでプロジェクトの再読込をしましょう):

// build.sbt
sourceGenerators in Compile += Def.task {
  《生成されたファイルを Seq[File] として返す処理》
}.taskValue

生成されたコードは

  • 「target/src_managed/main」ディレクトリ

に配置されます。 処理コードを書く際に注意が必要なのは、処理の返り値としてコンパイル時にクラスパスに含めたい全てのファイルを返す必要があることです。 既に自動生成されたファイルがあって新たに生成処理をしない場合でも、そのファイルを返り値に含める必要があります*1

テストコードのコンパイル時にテストコードもしくはテストに使用するコードを自動生成したい場合は sourceGenerators のスコープを「Compile」の代わりに「Test」とします:

sourceGenerators in Test += Def.task { ...

コード生成に関する sbt コマンド
上記のように sourceGenerators を設定すれば、sbt の通常の compile コマンドでコンパイルする際にコード生成が自動で行われます。 また、コードの自動生成だけをしてコンパイルはしたくないという場合には compile:managedSources コマンドを使います。

> sbt
Java HotSpot(TM) Client VM warning: ignoring option MaxPermSize=256m; support was removed in 8.0
> compile:managedSources

(もちろん sbt コマンドとまとめて実行してもいいですが。) コード生成時に例外が投げられて失敗した場合、last comile:managedSources コマンドで投げられた例外のスタックトレースを見ることができます(失敗時にメッセージが表示されるので覚える必要なし)。

自動生成されたコードが target ディレクトリ下に生成されるので、生成コードを削除したい場合には sbt の通常の clean コマンドを実行するだけで OK です。

別の Scala ファイルに処理を書く
生成処理コードが長くなる場合、build.sbt ファイル内に書くとゴチャゴチャしてメンテナンスが面倒になるので、別ファイルに処理を切り出したくなります。 この場合、例えば build.sbt の sourceGenerators の設定を以下のようにして

sourceGenerators in Compile += Def.task {
  val destDir = (sourceManaged in Compile).value
    // ソースコードの生成先。 デフォルトでは target/src_managed/main
  MySourceGenerator.generate(destDir)
}.taskValue

処理を MySourceGenerator コンパニオンオブジェクトの generate() メソッドに書きます(もちろん名前は適当に決めてかまいません):

// MySourceGenerator.scala
object MySourceGenerator{
  def generate(destDir: File): Seq[File] = 《処理内容》
}

このコードを書く Scala ファイルは、プロジェクトのルートディレクトリ下の project ディレクトリ下に配置します*2。 必ずしもコンパニオンオブジェクトのメソッドに処理を実装する必要はありませんが、処理を記述する Scala ファイルは project ディレクトリ下に配置します。

ビルド時にのみ使用するライブラリへの依存性を追加する
プロジェクトのビルド時のみに使用したいライブラリがある場合、sbt.build に通常の依存性として書き込むことができません(たぶん)。 sbt プロジェクトでは常に project サブプロジェクトが生成されて「プロジェクトをビルドするプロジェクト」として使われるので、ビルド時にのみ使用するライブラリはこの project プロジェクトに対しての依存性として設定します。

  • プロジェクトルート/
    • project/
      • build.sbt ・・・ プロジェクトをビルドするプロジェクトの build.sbt
    • src/
      • main/
        • scala/
      • test/
        • scala/
    • target/
    • build.sbt ・・・ 通常のプロジェクトの build.sbt

この build.sbt の書き方は通常の build.sbt の書き方と同じです。 例えば Google が開発している JSON を扱うライブラリ Gson を依存性に追加したい場合

libraryDependencies += "com.google.code.gson" % "gson" % "2.6.2"

のようにします。

sbt でコードを自動生成するには大体これくらいで用が足りるかと思います。

sbt × IntelliJ IDEA

さて、sbt を使ってプロジェクトをビルドする分には上記の設定で事足りますが、IntelliJ IDEA で sbt プロジェクトを読み込むとどうも上手くビルドしてくれません(IDEA 15)。 これが IDEA のせいなのか Scala プラグインのせいなのかわかりませんが(バグなのか仕様なのかもわかりませんが)、ちょっと build.sbt に設定を書き換えればいいだけのようなのでご紹介。

うまくビルドしてくれない原因は、target ディレクトリ下のソースコードがコンパイル時に認識されないことのようで*3、自動生成されるコードの生成先を変更すれば問題が解決します。 この設定は build.sbt で sourceManaged を変更すれば OK です。 変更先は src/main/src_managed あたりが無難かと:

sourceManaged in Compile := file((sourceDirectory in Compile).value.getAbsolutePath + "/src_managed")

ファイルは絶対パスから生成しないとビルド時に例外が投げられるようなので getAbsolutePath を呼び出してます。 テストコードの生成をする場合には、2箇所の Compile スコープを Test に変えます。

あと、必須の設定ではありませんが、clean コマンドによって自動生成されたコードも削除できるようにするために以下の設定も付けしておくと便利かと思います:

cleanFiles += (sourceManaged in Compile).value

この設定は上記の sourceManaged の設定の後に書きましょう。

build.sbt を書き換えるとエディタ上部にプロジェクトをリフレッシュするかどうかを問うメッセージが表示されるので、「Reflesh project」を選択してプロジェクトをリフレッシュしましょう。 このメッセージがなかなか表示されない場合は以下のようにして表示させることができます(もっと簡単な方法あるかも):

f:id:waman:20160320122626p:plain

これで IDEA でもコードを自動生成する sbt プロジェクトを扱うことができます。 ただし、IDEA でのビルドではコード生成を自動で行ってくれないので、コード生成は Terminal などから sbt を使って行いましょう。 おしまい。

Scalaスケーラブルプログラミング第2版

Scalaスケーラブルプログラミング第2版

  • 作者: Martin Odersky,Lex Spoon,Bill Venners,羽生田栄一,水島宏太,長尾高弘
  • 出版社/メーカー: インプレスジャパン
  • 発売日: 2011/09/27
  • メディア: 単行本(ソフトカバー)
  • 購入: 12人 クリック: 235回
  • この商品を含むブログ (46件) を見る
プログラミングScala

プログラミングScala

  • 作者: Dean Wampler,Alex Payne,株式会社オージス総研オブジェクトの広場編集部
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2011/01/20
  • メディア: 大型本
  • 購入: 3人 クリック: 320回
  • この商品を含むブログ (38件) を見る

*1:代わりに target/src_managed/main ディレクトリをソースディレクトリに含めるように設定してもいいのかもしれません。 試してませんが。

*2:プロジェクトのルートディレクトリに置いても良かった気がしますが、ちょっと試したら例外出たので深入りしないことにします。

*3:IDEA でソースフォルダを指定する .idea/modules 下にある iml ファイルには設定が書かれてるので、IDEA 側のバグ?