Предположим, что у меня есть проект Scala с тремя суб-проектов, с файлами, как это:Чтение ресурсов из макроса в проекте SBT
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, заставляет меня думать, что это должно быть что-то простое. –