複数のコンフィギュレーション
ここ最近 scalaxb を複数のコンフィギュレーションで使う sbt ビルドの設定の方法について何人かの方に聞かれたので、ちょっとみてみよう。
コードは以下から入手してほしい:
$ git clone -b multipleconfigs git://github.com/eed3si9n/scalaxb-sample.git
$ cd scalaxb-sample/multipleconfigs/
好きなエディタでディレクトリごと開く。src/
内には main/scala/main.scala
、main/xsd/ipo.xsd
、main/xsd/w.xsd
が入っている。
複数の config はいつ必要?
単一 config の場合は、全てのスキーマの型クラスのインスタンスが xmlprotocol.scala に含まれる。現行では、参照されているスキーマは一緒にコンパイルされないければいけないので、通常はこの方法を取ることになる。
それでは、いつ複数の config を使うべきだろうか? 複数のコンフィギュレーションを使うことで、スキーマを別々にコンパイルして xmlprotocol.scala を独立して生成することができる。分けることでコードのサイズを小さく抑えたり、別のパッケージ下に生成したい場合に有効だ。
スキーマ
サンプル同様に、ipo.xsd
と w.xsd
という二つのスキーマが src/main/xsd/
下にあることを仮定する。一つは international purchase order で、もう一つは weather だ。
project/plugins.sbt
第一に、ビルドに sbt-scalaxb を加えるために、以下を project/plugins.sbt
に書く:
addSbtPlugin("org.scalaxb" % "sbt-scalaxb" % "X.X.X") resolvers += Resolver.sonatypeRepo("public")
build.sbt
次に、build.sbt
をみていく。マルチ・プロジェクト build.sbt ビルド定義ファイルは以下のようになる:
lazy val commonSettings = Seq( version := "0.1", organization := "com.example", scalaVersion := "2.11.5" ) lazy val scalaXml = "org.scala-lang.modules" %% "scala-xml" % "1.0.2" lazy val scalaParser = "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.1" lazy val app = (project in file(".")). settings(commonSettings: _*). settings( name := "scalaxb-multipleconfigs-sample", libraryDependencies ++= Seq(scalaXml, scalaParser) )
取り敢えず import 節を加えて sbt-scalaxb を使えるようにする:
import ScalaxbKeys._
sbt-scalaxb は通常 scalaxb
タスクと Compile
コンフィギュレーションにスコープ付けされた sources
キーを用いてどのスキーマがコンパイルされるかを決定する。カスタムのコンフィギュレーションを作ることで、複数セットの sources
を作れる。使う予定のパッケージ名にあわせて config 名を決めた:
lazy val Ipo = config("ipo") extend(Compile) lazy val W = config("w") extend(Compile)
次に、config 内での scalaxb のセッティングをある程度一般化できるようならば、それを作るメソッドを定義する。ipo.xsd
、w.xsd
という名前にしてあるので、それを base
に使ってスキーマのファイル名、パッケージ名、プロトコルファイル名を決める:
def customScalaxbSettings(base: String): Seq[Def.Setting[_]] = Seq( sourceManaged := (sourceManaged in Compile).value / "sbt-scalaxb" / base, generateRuntime := false, sources := Seq(xsdSource.value / (base + ".xsd")), packageName := base, protocolFileName := base + "_xmlprotocol.scala" )
次に、セッティングを組み合わせていく:
def codeGenSettings: Seq[Def.Setting[_]] = inConfig(Ipo)(baseScalaxbSettings ++ inTask(scalaxb)(customScalaxbSettings("ipo"))) ++ inConfig(W)(baseScalaxbSettings ++ inTask(scalaxb)(customScalaxbSettings("w"))) ++ Seq( sourceGenerators in Compile += (scalaxb in Ipo).taskValue, sourceGenerators in Compile += (scalaxb in W).taskValue, // Runtime needs to be generated only once generateRuntime in (Ipo, scalaxb) := true )
上記のとおり、カスタムのセッティングは scalaxb
タスクとカスタムのコンフィギュレーションにスコープ付けされている。後半で、scalaxb in Ipos
と scalaxb in W
のタスクを source generator として登録している。最後に、codeGenSettings
をプロジェクトに追加する:
lazy val app = (project in file(".")). settings(commonSettings: _*). settings(codeGenSettings: _*). settings( name := "scalaxb-multipleconfigs-sample", libraryDependencies ++= Seq(scalaXml, scalaParser) )
まとめると、こうなる:
import ScalaxbKeys._ lazy val Ipo = config("ipo") extend(Compile) lazy val W = config("w") extend(Compile) lazy val commonSettings = Seq( version := "0.1", organization := "com.example", scalaVersion := "2.11.5" ) lazy val scalaXml = "org.scala-lang.modules" %% "scala-xml" % "1.0.2" lazy val scalaParser = "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.1" lazy val app = (project in file(".")). settings(commonSettings: _*). settings(codeGenSettings: _*). settings( name := "scalaxb-multipleconfigs-sample", libraryDependencies ++= Seq(scalaXml, scalaParser) ) def codeGenSettings: Seq[Def.Setting[_]] = inConfig(Ipo)(baseScalaxbSettings ++ inTask(scalaxb)(customScalaxbSettings("ipo"))) ++ inConfig(W)(baseScalaxbSettings ++ inTask(scalaxb)(customScalaxbSettings("w"))) ++ Seq( sourceGenerators in Compile += (scalaxb in Ipo).taskValue, sourceGenerators in Compile += (scalaxb in W).taskValue, // Runtime needs to be generated only once generateRuntime in (Ipo, scalaxb) := true ) def customScalaxbSettings(base: String): Seq[Def.Setting[_]] = Seq( sourceManaged := (sourceManaged in Compile).value / "sbt-scalaxb" / base, generateRuntime := false, sources := Seq(xsdSource.value / (base + ".xsd")), packageName := base, protocolFileName := base + "_xmlprotocol.scala" )
これで、compile
を sbt シェルから呼び出すと ipo.xsd と w.xsd に対して別々にデータバインディングが生成される。
使ってみる
生成された型クラスのインスタンスは自動的にパッケージオブジェクトに入っているので、import 節無しで使えるようになっている:
object Main extends App { val shipto = <shipto xmlns="http://www.example.com/IPO"> <name>foo</name> <street>1537 Paper Street</street> <city>Wilmington</city> <state>DE</state> <zip>19808</zip> </shipto> println(scalaxb.fromXML[ipo.USAddress](shipto)) val weather = <weather xmlns="http://www.example.com/weather"> <zip>19808</zip> <status>Sunny</status> </weather> println(scalaxb.fromXML[w.Weather](weather)) }
これを以下のように sbt シェルから実行する:
> run
....
USAddress(foo,1537 Paper Street,Wilmington,DE,19808)
Weather(19808,Sunny)