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.

First thing you'd notice is that there's no build.sbt. In project/, there are build.scala and plugins.sbt; and 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.

plugins.sbt

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

addSbtPlugin("org.scalaxb" % "sbt-scalaxb" % "0.6.8")
 
resolvers += ("Sonatype Public" at "https://oss.sonatype.org/content/repositories/public/")

build.scala

Next, we are going to look at the build.scala. A plain .scala build defintion would look like this:

import sbt._
 
object Build extends sbt.Build {
  import Keys._
 
  lazy val app = Project("app", file("."), settings = appSettings)
 
  lazy val buildSettings = Defaults.defaultSettings ++ Seq(
    version := "0.1",
    organization := "com.example"
  )
 
  lazy val appSettings = buildSettings ++ Seq(
    name := "scalaxb-multipleconfigs-sample"
  )
}

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

  import sbtscalaxb.Plugin._
  import ScalaxbKeys._
  import Keys._

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:

  val Ipo = config("ipo") extend(Compile)
  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[Project.Setting[_]] = Seq(
    sources <<= xsdSource map { xsd => Seq(xsd / (base + ".xsd")) },
    packageName := base,
    protocolFileName := base + "_xmlprotocol.scala"
  )

Next, build the settings using the method:

  def codeGenSettings: Seq[Project.Setting[_]] =
    inConfig(Ipo)(baseScalaxbSettings ++ inTask(scalaxb)(customScalaxbSettings("ipo"))) ++
    inConfig(W)(baseScalaxbSettings ++ inTask(scalaxb)(customScalaxbSettings("w"))) ++ Seq(
      sourceGenerators in Compile <+= scalaxb in Ipo,
      sourceGenerators in Compile <+= scalaxb in W
    )

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, append codeGenSettings to appSettings:

  lazy val appSettings = buildSettings ++ Seq(
    name := "scalaxb-multipleconfigs-sample"
  ) ++ codeGenSettings

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

import sbt._
 
object Build extends sbt.Build {
  import sbtscalaxb.Plugin._
  import ScalaxbKeys._
  import Keys._
 
  val Ipo = config("ipo") extend(Compile)
  val W = config("w") extend(Compile)
 
  lazy val app = Project("app", file("."), settings = appSettings)
 
  lazy val buildSettings = Defaults.defaultSettings ++ Seq(
    version := "0.1",
    organization := "com.example"
  )
 
  lazy val appSettings = buildSettings ++ Seq(
    name := "scalaxb-multipleconfigs-sample"
  ) ++ codeGenSettings
 
  def customScalaxbSettings(base: String): Seq[Project.Setting[_]] = Seq(
    sources <<= xsdSource map { xsd => Seq(xsd / (base + ".xsd")) },
    packageName := base,
    protocolFileName := base + "_xmlprotocol.scala"
  )
 
  def codeGenSettings: Seq[Project.Setting[_]] =
    inConfig(Ipo)(baseScalaxbSettings ++ inTask(scalaxb)(customScalaxbSettings("ipo"))) ++
    inConfig(W)(baseScalaxbSettings ++ inTask(scalaxb)(customScalaxbSettings("w"))) ++ Seq(
      sourceGenerators in Compile <+= scalaxb in Ipo,
      sourceGenerators in Compile <+= scalaxb in W
    )
}

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)

Let me know how it worked for you.