複数のコンフィギュレーション
ここ最近 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)