【发布时间】:2016-03-10 03:25:42
【问题描述】:
假设我有一个包含三个子项目的 Scala 项目,文件如下:
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 包含一个简单的宏,用于读取给定路径的资源:
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!")
}
}
}
如果资源可以打开,countLines返回行数;否则为空。
另外两个 Scala 源文件只是调用这个宏:
object Bar extends App {
println(Foo.countLines("/foo.txt"))
println(Foo.countLines("/bar.txt"))
println(Foo.countLines("/baz.txt"))
}
还有:
object Baz extends App {
println(Foo.countLines("/foo.txt"))
println(Foo.countLines("/bar.txt"))
println(Foo.countLines("/baz.txt"))
}
资源的内容对于这个问题并不重要。
如果这是一个 Maven 项目,我可以很容易地配置它,让根项目聚合三个子项目,baz 依赖于bar,它依赖于foo。有关血腥细节,请参阅this Gist。
使用 Maven,一切都按预期工作。 Bar 可以看到foo 和bar 的资源:
Some(1)
Some(2)
None
而Baz 可以看到所有这些:
Some(1)
Some(2)
Some(3)
现在我尝试用 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" % _)
)
}
但是现在Bar只能看到foo里面的资源:
Some(1)
None
None
而Baz只能看到foo和bar:
Some(1)
Some(2)
None
这里发生了什么?在我看来,这个 SBT 构建文件是 Maven 配置的相当直译。例如,我在bar 项目中打开控制台并读取/bar.txt 没有问题,那么为什么这些项目在调用宏时看不到自己的资源?
【问题讨论】:
-
@0__:谢谢,我应该提到——是的,它们就在那里(并且可以从控制台和项目源中的非宏代码中获得)。
-
不是宏专家,但它必须是类加载器问题 IMO。
this.getClass.getResourceAsStream究竟是在什么时候执行的?也许您需要使用上下文或其他东西作为类加载器引用?没有线索... -
@0__:我假设它是这样的,但我没有找到解决方案,而且它在 Maven 案例中按预期工作的事实让我认为它应该很简单。
-
只是想... 由于资源是在编译时查询的,因此您假设它在类路径上。但是我想知道,如果 sbt 从编译类路径中排除依赖资源,这不是完全合法的吗?毕竟在“正常”(非宏)条件下,无论如何都无法访问这些资源。 Maybe related problem
-
另一种方法是使用 System.getProperties 在 SBT 和宏之间进行通信。不过,这不是很漂亮。
标签: scala maven classpath sbt scala-macros