Suppose I have a Scala project with three sub-projects, with files like this:
foo/src/main/scala/Foo.scala
foo/src/main/resources/foo.txt
bar/src/main/scala/Bar.scala
bar/src/main/resources/bar.txt
baz/src/main/scala/Baz.scala
baz/src/main/resources/baz.txt
Foo.scala
contains a simple macro that reads a resource at a given path:
import scala.language.experimental.macros
import scala.reflect.macros.Context
object Foo {
def countLines(path: String): Option[Int] = macro countLines_impl
def countLines_impl(c: Context)(path: c.Expr[String]) = {
import c.universe._
path.tree match {
case Literal(Constant(s: String)) => Option(
this.getClass.getResourceAsStream(s)
).fold(reify(None: Option[Int])) { stream =>
val count = c.literal(io.Source.fromInputStream(stream).getLines.size)
reify(Some(count.splice))
}
case _ => c.abort(c.enclosingPosition, "Need a literal path!")
}
}
}
If the resource can be opened, countLines
returns the number of lines; otherwise it's empty.
The other two Scala source files just call this macro:
object Bar extends App {
println(Foo.countLines("/foo.txt"))
println(Foo.countLines("/bar.txt"))
println(Foo.countLines("/baz.txt"))
}
And:
object Baz extends App {
println(Foo.countLines("/foo.txt"))
println(Foo.countLines("/bar.txt"))
println(Foo.countLines("/baz.txt"))
}
The contents of the resources don't really matter for the purposes of this question.
If this is a Maven project, I can easily configure it so that the root project aggregates the three sub-projects and baz
depends on bar
, which depends on foo
. See this Gist for the gory details.
With Maven everything works as expected. Bar
can see the resources for foo
and bar
:
Some(1)
Some(2)
None
And Baz
can see all of them:
Some(1)
Some(2)
Some(3)
Now I try the same thing with SBT:
import sbt._
import Keys._
object MyProject extends Build {
lazy val root: Project = Project(
id = "root", base = file("."),
settings = commonSettings
).aggregate(foo, bar, baz)
lazy val foo: Project = Project(
id = "foo", base = file("foo"),
settings = commonSettings
)
lazy val bar: Project = Project(
id = "bar", base = file("bar"),
settings = commonSettings,
dependencies = Seq(foo)
)
lazy val baz: Project = Project(
id = "baz", base = file("baz"),
settings = commonSettings,
dependencies = Seq(bar)
)
def commonSettings = Defaults.defaultSettings ++ Seq(
scalaVersion := "2.10.2",
libraryDependencies <+= scalaVersion("org.scala-lang" % "scala-compiler" % _)
)
}
But now Bar
can only see the resources in foo
:
Some(1)
None
None
And Baz
can only see foo
and bar
:
Some(1)
Some(2)
None
What's going on here? This SBT build file seems to me to be a pretty literal translation of the Maven configuration. I have no problem opening a console in the bar
project and reading /bar.txt
, for example, so why can't these projects see their own resources when calling a macro?