sbt プロジェクトで Scala 2.11 のマクロを試そうと思って設定してたんだけど、マクロの仕様上、単純な sbt プロジェクトではうまくいかなかったので必要な設定をメモ。
sbt -0.13.0 までのドキュメントには「Macro Projects」という項目があったのですが
0.13.1 からは目次からなくなったので(記事自体はある模様)、マクロ用の設定がいらなくなったのかとちょっと喜んだんだけど、全くそんなことはなかったようで。 どこかの項目に吸収されたのかな?
この記事では使用した sbt のバージョンは 0.13.6 です。
修正
マクロを定義するサブプロジェクトの build.sbt 内での名前を「macroSub」に変更しました。 マクロコードを配置するディレクトリは変わらず「macro」です。
前のままでも構わないのですが、IntelliJ IDEA (要プラグイン)で build.sbt を開くと「macro」を予約後として誤認識してしまうためです。 その他の IDE の対応は知りませんが。
参考
- http://www.scala-sbt.org/0.13.0/docs/Detailed-Topics/Macro-Projects.html
- Quasiquotes - Use cases - Scala Documentation
設定概要
Scala ではマクロの仕様上、定義と使用を別のモジュールにしないといけないようで@waman10da マクロの定義側と使用側でモジュール分けないといけないのは、sbt関係なく現状のScalaの仕様ですよ
— Kenji Yoshida (@xuwei_k) 2014, 10月 29
sbt では別プロジェクトにしてやる必要があります。 まぁ、基本的には普通のマルチプロジェクトにしてあげればいいだけですが、sbt のマルチプロジェクト自体にあんまり慣れてないのでそのあたりの設定もしていきます。
sbt 0.13.0 までのドキュメントに載っていた「Macro Projects」では、ルートプロジェクトが通常の(src ディレクトリなどを持つ) sbt プロジェクトで、そのサブプロジェクトとしてマクロコードを書くプロジェクトを定義しているので、それに従って設定します。 build.sbt で設定するのは概ね以下の項目:
- マクロを定義するサブプロジェクト macroSub & ルートプロジェクトのプロジェクト依存性の設定
- Scala のバージョンを 2.11.4 に設定
- マクロを使用するために scalac のオプションに "-language:experimental.macros" を追加
- パッケージングに macroSub プロジェクトのソースコードとバイトコードを含める
build.sbt の設定
では、上記の設定項目を順に見ていきましょう。 プロジェクト名は mymacro としておきましょう。 ちなみにプロジェクトのディレクトリ構成は以下のようになってます:mymacro/ ・・・ プロジェクトのルート・ディレクトリ
以下で build.sbt の設定を見ていきます。
サブプロジェクト macroSub
まずはマクロを配置するサブプロジェクト macroSub の設定しましょう。 サブプロジェクト macroSub の作成は「lazy val macroSub = project in file("macro")」としますが*1、マクロを使うためにはさらに scala-reflect をライブラリとして依存性に加える必要があります:lazy val macroSub = (project in file("macro")). settings( libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value )
「scalaVersion.value」はプロジェクトの Scala バージョンを参照しています。
次はルート・プロジェクトの設定。 ルート・プロジェクトは macroSub プロジェクトに依存するように設定します:
lazy val root = (project in file(".")). aggregate(macroSub). // 別に必要なし dependsOn(macroSub)
依存性は「.dependsOn(macroSub)」によって設定します。 「.aggregate(macroSub)」は、まぁ別にいりません。 設定項目の確認とかにちょっと便利なので設定してます。
Scala のバージョン
Scala のバージョンは scalaVersion によって定義できますが、単にscalaVersion := "2.11.4"
とすると、ルート・プロジェクトの scalaVersion しか設定されません。 sbt コンソールで「show scalaVersion」(もしくは単に「scalaVersion」)を実行すると*2
> show scalaVersion
[info] macroSub/*:scalaVersion
[info] 2.10.4
[info] root/*:scalaVersion
[info] 2.11.4
となり、macroSub プロジェクトの scalaVersion は 2.10.4 であることがわかります。 これは macroSub プロジェクトでは scalaVersion が設定されていないとみなされ、sbt-0.13 のデフォルト Scala バージョンである 2.10.4 が使われているのが理由です。 これを解決するためには macroSub プロジェクトで別途 scalaVersion を設定してもいいんですが、大抵は全プロジェクトで同一の Scala バージョンを使うと思うので、以下のように Global スコープでまとめて定義しておいた方が無難:
scalaVersion in Global := "2.11.4"
リロードして scalaVersion を再度実行すると
> reload
> show scalaVersion
[info] macroSub/*:scalaVersion
[info] 2.11.4
[info] root/*:scalaVersion
[info] 2.11.4
となり、macroSub プロジェクトでも scalaVersion がきちんと設定されていることが分かります。
ちなみに、同様のことが scalaVersion 以外の設定項目でも当てはまります。 例えば、マルチプロジェクト全体で同一の version を設定したい場合は
version in Global := "1.0"
とします。
Scalac オプション
sbt 関係なく、マクロを使うためには scala-reflect を依存性に含める以外に以下のいずれかの設定が必要です:- マクロを定義・実装しているコードに明示的に「import language.experimental.macros」を追加する
- scalac のコンパイルオプションに「-language:experimental.macros」を追加する
各マクロコードに import 文を書くのは面倒なので(いずれ実験コードでなくなったときにいらなくなるかもしれないし)、ここでは後者の設定をします:
scalacOptions in Global += "-language:experimental.macros"
他のコンパイル・オプションがある場合は以下のようにします:
scalacOptions in Global ++= Seq( "-deprecation", "-encoding", "UTF-8", "-language:experimental.macros" )
もしくは、このコンパイル・オプションが必要なのは macroSub プロジェクトだけなので、macroSub プロジェクトの設定を
lazy val macroSub = project. settings( scalacOptions += "-language:experimental.macros", libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value )
のようにしても構いません。 プロジェクトに複数の設定 (settings) を行いたい場合は、単にコンマ (,) で区切って書くだけで OK。
パッケージング
パッケージングの際に macroSub プロジェクトのバイトコードやソースコードをルートプロジェクトのものとまとめてパッケージングしたい場合には、ルートプロジェクトの設定 (settings) に以下のようなコードを追加しておきます:lazy val root = (project in file(".")). aggregate(macroSub). dependsOn(macroSub). // 以下を追加 settings( mappings in (Compile, packageBin) ++= mappings.in(macro, Compile, packageBin).value, mappings in (Compile, packageSrc) ++= mappings.in(macro, Compile, packageSrc).value )
これで設定完了。
サンプル build.sbt
以上の設定をまとめると build.sbt のテンプレートは以下のような感じ。 個人的に常に設定している項目 (javaVersion, encoding) をちょっと入れたのでごちゃっとしちゃってるかな?:name := "mymacro" version in Global := "1.0" scalaVersion in Global := "2.11.4" //***** Custom settings ***** val javaVersion = settingKey[String]("javac source/target version") val encoding = settingKey[String]("source encoding") javaVersion in Global := "1.8" encoding in Global := "UTF-8" //***** Projects ***** lazy val root = (project in file(".")). aggregate(macroSub). dependsOn(macroSub). settings( mappings in (Compile, packageBin) ++= mappings.in(macroSub, Compile, packageBin).value, mappings in (Compile, packageSrc) ++= mappings.in(macroSub, Compile, packageSrc).value ) lazy val macroSub = (project in file("macro")). settings( scalacOptions += "-language:experimental.macros", libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value ) //***** Options & Dependencies ***** javacOptions in Global ++= Seq( "-source", javaVersion.value, "-target", javaVersion.value, "-encoding", encoding.value ) scalacOptions in Global ++= Seq( "-deprecation", "-encoding", encoding.value ) libraryDependencies in Global ++= Seq( "org.scala-lang" % "scala-library" % scalaVersion.value, "org.scalatest" % "scalatest_2.11" % "2.2.2" % Test ) //***** Running ***** fork in Global := true //***** Packaging ***** //mainClass in Compile := Some("org.sample.Main") //mainClass in (Compile, packageBin) := Some("org.sample.Main") //mainClass in (Compile, run) := Some("org.sample.Main") crossPaths in Global := false
- javaVersion と encoding はそれぞれ java ソースのバージョンとエンコーディングです。 sbt って独自キーの定義と値の設定が1行で書けないもんなのかな?
- fork, mainClass, crossPath は実行やパッケージングに使う設定。 スコープとかこれでいいのかあんまり考えてません。
マクロの簡単なサンプルは「参考」に示したリンク先にいくつかあるので試して見て下さい。
- 作者: Martin Odersky,Lex Spoon,Bill Venners,羽生田栄一,水島宏太,長尾高弘
- 出版社/メーカー: インプレスジャパン
- 発売日: 2011/09/27
- メディア: 単行本(ソフトカバー)
- 購入: 12人 クリック: 235回
- この商品を含むブログ (45件) を見る
Gradle徹底入門 次世代ビルドツールによる自動化基盤の構築
- 作者: 綿引琢磨,須江信洋,林政利,今井勝信
- 出版社/メーカー: 翔泳社
- 発売日: 2014/11/05
- メディア: 大型本
- この商品を含むブログを見る