6

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?

4

1 回答 1

6

SBT 不会将当前项目的资源添加到构建类路径中。可能是因为它很少需要。

您只需将一个(资源)通过管道传输到另一个(类路径):

def commonSettings = Defaults.defaultSettings ++ Seq(
  scalaVersion := "2.10.2",
  libraryDependencies <+= scalaVersion("org.scala-lang" % "scala-compiler" % _),
  // add resources of the current project into the build classpath
  unmanagedClasspath in Compile <++= unmanagedResources in Compile
)

在那个项目中,你只需要barand baz

更新:从 sbt 0.13 开始,由于新的基于宏的语法,<+=因此是不必要的(并且未记录):<++=

libraryDependencies += "org.scala-lang" % "scala-compiler" % scalaVersion.value
unmanagedClasspath in Compile ++= (unmanagedResources in Compile).value
于 2013-07-28T22:32:14.063 回答