multiple configs

A few people have asked me on how to configure a sbt build so scalaxb can be used multiple times, so let's take a look.
Here's the code:

$ git clone -b multipleconfigs git://github.com/eed3si9n/scalaxb-sample.git
$ cd scalaxb-sample/multipleconfigs/

Now open the directory using your favorite editor.

In src/ we have main/scala/main.scala, main/xsd/ipo.xsd, and main/xsd/w.xsd.

when to go multiple configs?

With the single config approach, the xmlprotocol.scala contains the typeclass instances for all schemas. It's currently necessary to compile all referenced schemas together, so normally that's the route you should take.

So when should one use multi configs? By using multiple configuration, you can separately compile the schemas and generate xmlprotocol.scala. This may be desirable if you wan to keep them apart for smaller code size or so you can generate them under different package.

schemas

Suppose we have two schemas ipo.xsd and w.xsd both under src/main/xsd/ as the example. One is for international purchase order and the other is for weather.

project/plugins.sbt

First, we need to add sbt-scalaxb to the build by adding the following to project/plugins.sbt:

addSbtPlugin("org.scalaxb" % "sbt-scalaxb" % "X.X.X")
 
resolvers += Resolver.sonatypeRepo("public")

build.sbt

Next, we are going to look at the build.sbt. A multi-project build.sbt build defintion would look like this:

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)
  )

First thing is do is add an import clauses to use sbt-scalaxb:

import ScalaxbKeys._

sbt-scalaxb normally uses sources key scoped in scalaxb task and Compile configuration to determine which schemas to compile. By creating custom configurations, we can make multiple sets of sources. I'm naming them after the package name I'd like to use:

lazy val Ipo = config("ipo") extend(Compile)
lazy val W = config("w") extend(Compile)

Next, define a method to generate custom scalaxb settings if you can generalize the settings among the configs. I've named my schema files ipo.xsd and w.xsd respectively so I can use base to figure out the schema file name, package name, and procotol file name:

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"
)

Next, build the settings using the method:

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
  )

As you can see, the custom settings are scoped in both scalaxb task and the custom configuration. The latter part registers scalaxb in Ipos and scalaxb in W as source generators. Finally, add to codeGenSettings to the project:

lazy val app = (project in file(".")).
  settings(commonSettings: _*).
  settings(codeGenSettings: _*).
  settings(
    name := "scalaxb-multipleconfigs-sample",
    libraryDependencies ++= Seq(scalaXml, scalaParser)
  )

If you put this all together here's what we get:

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"
)

By calling compile from sbt shell, it generates data bindings for ipo.xsd and w.xsd separately.

using them

The generated typeclass instances are automatically put into the package objects, so we can use them without import clause:

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))
}

This can be executed from sbt shell as follows:

> run
....
USAddress(foo,1537 Paper Street,Wilmington,DE,19808)
Weather(19808,Sunny)