【问题标题】:Reading resources from a macro in an SBT project从 SBT 项目中的宏读取资源
【发布时间】: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 可以看到foobar 的资源:

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只能看到foobar

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


【解决方案1】:

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
)

在该项目中,您只需要 barbaz

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

libraryDependencies += "org.scala-lang" % "scala-compiler" % scalaVersion.value
unmanagedClasspath in Compile ++= (unmanagedResources in Compile).value

【讨论】:

猜你喜欢
  • 2015-10-03
  • 1970-01-01
  • 2013-01-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-19
  • 2011-04-21
相关资源
最近更新 更多