scalasbtsbt-0.13cross-build

SBT Scala cross versions, with aggregation and dependencies


I am struggling with how crossScalaVersions works with subprojects.

I have a project that compiles with 2.10 (foo) and a project that compiles with 2.11 (bar). They share a cross compiled project (common).

How can I compile projects foo and bar?


build.sbt

lazy val root = (project in file(".")).aggregate(foo, bar).settings(
  crossScalaVersions := Seq("2.10.4", "2.11.4")
)

lazy val foo = (project in file("foo")).dependsOn(common).settings(
  crossScalaVersions := Seq("2.10.4"),
  scalaVersion := "2.10.4"
)

lazy val bar = (project in file("bar")).dependsOn(common).settings(
  crossScalaVersions := Seq("2.11.4"),
  scalaVersion := "2.11.4"
)

lazy val common = (project in file("common")).settings(
  crossScalaVersions := Seq("2.10.4", "2.11.4")
)

project/build.properties

sbt.version=0.13.7

foo/src/main/scala/Foo.scala

object Foo {
  <xml>{new C}</xml>
}

bar/src/main/scala/Bar.scala

case class Bar(a: C, b: C, c: C, d: C, e: C, f: C, g: C,
  h: C, i: C, j: C, k: C, l: C, m: C, n: C, o: C, p: C,
  q: C, r: C, s: C, t: C, u: C, v: C, w: C, x: C, y: C,
  z: C)

common/src/main/scala/Common.scala

class C {}

Attempt 1

$ sbt compile
[info] Resolving jline#jline;2.12 ...
[warn]  ::::::::::::::::::::::::::::::::::::::::::::::
[warn]  ::          UNRESOLVED DEPENDENCIES         ::
[warn]  ::::::::::::::::::::::::::::::::::::::::::::::
[warn]  :: common#common_2.11;0.1-SNAPSHOT: not found
[warn]  ::::::::::::::::::::::::::::::::::::::::::::::
[warn] 
[warn]  Note: Unresolved dependencies path:
[warn]      common:common_2.11:0.1-SNAPSHOT
[warn]        +- bar:bar_2.11:0.1-SNAPSHOT
sbt.ResolveException: unresolved dependency: common#common_2.11;0.1-SNAPSHOT: not found

Attempt 2

$ sbt +compile
[error] /home/paul/test/bar/src/main/scala/Bar.scala:1: Implementation restriction: case classes cannot have more than 22 parameters.
[error] case class Bar(a: C, b: C, c: C, d: C, e: C, f: C, g: C,
[error]            ^
[error] one error found
[error] (bar/compile:compile) Compilation failed

Attempt 3

$ sbt foo/compile bar/compile
[warn]  ::::::::::::::::::::::::::::::::::::::::::::::
[warn]  ::          UNRESOLVED DEPENDENCIES         ::
[warn]  ::::::::::::::::::::::::::::::::::::::::::::::
[warn]  :: common#common_2.11;0.1-SNAPSHOT: not found
[warn]  ::::::::::::::::::::::::::::::::::::::::::::::
[warn] 
[warn]  Note: Unresolved dependencies path:
[warn]      common:common_2.11:0.1-SNAPSHOT
[warn]        +- bar:bar_2.11:0.1-SNAPSHOT
sbt.ResolveException: unresolved dependency: common#common_2.11;0.1-SNAPSHOT: not found

Attempt 4

$ sbt +foo/compile +bar/compile
[error] /home/paul/test3/foo/src/main/scala/Foo.scala:2: To compile XML syntax, the scala.xml package must be on the classpath.
[error] Please see http://docs.scala-lang.org/overviews/core/scala-2.11.html#scala-xml.
[error]   <xml>{new C}</xml>
[error]   ^
[error] one error found
[error] (foo/compile:compile) Compilation failed

Attempt 5

I even tried defining common_2_10 and common_2_11 projects with that same base directory but different scala versions. I recall reading that targets are namespaced by Scala version, but SBT says there is a conflict.

$ sbt
[error] Overlapping output directories:/home/paul/test3/common/target:
[error]     ProjectRef(file:/home/paul/test3/,common_2_10)
[error]     ProjectRef(file:/home/paul/test3/,common_2_11)

The only thing I've gotten to work is manually specifying versions:

$ sbt ++2.10.4 foo/compile ++2.11.4 bar/compile

But this is a lot of commands, can never use parallelism, and obviates the whole use of (1) project aggregation and (2) cross building.

Am I missing something fundamental about the intent of crossScalaVersions? Or is there a way to have it play well with the rest of SBT, and for me to compile my heterogeneous projects?


Solution

  • I wound up declaring common twice, once for each version.

    lazy val root = (project in file(".")).aggregate(foo, bar)
    
    lazy val foo = (project in file("foo")).dependsOn(common_2_10).settings(
      scalaVersion := "2.10.4"
    )
    
    lazy val bar = (project in file("bar")).dependsOn(common_2_11).settings(
      scalaVersion := "2.11.4"
    )
    
    def commonProject = (project in file("common")).settings(
      target := baseDirectory.value / s"target-${scalaVersion.value}"
    )
    
    lazy val common_2_10 = commonProject.settings(
      scalaVersion := "2.10.4"
    )
    
    lazy val common_2_11 = commonProject.settings(
      scalaVersion := "2.11.4"
    )
    

    Note that I had to make the target directories different, or else SBT would reject it because they overlapped.

    Also note that def makes commonProject not included the SBT's magical (reflection-based) search for project definitions.


    This isn't the prettiest, but it is robust, readable, and reasonable. All commands/tasks work as one might expect.

    In one way this is even better than crossScalaVersions, in that 2.10 and 2.11 projects can now be compiled in parallel, which does not happen with crossScalaVersions :)


    EDIT: I created an SBT plugin, sbt-cross, to help out with this.