2013-06-16 3 views
6

Предположим, что у меня есть проект 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

@ 0__: Спасибо, я должен отметить, что да, они есть (и доступны из непредставленных макрокоманды как в консоли, так и в источнике проекта). –

+0

Не эксперт по макроэкономике, но это должен быть вопрос загрузчика класса IMO. В каких именно точках выполняется 'this.getClass.getResourceAsStream'? Возможно, вам нужно использовать контекст или что-то еще в качестве ссылки на загрузчик классов? Нет подсказки ... –

+0

@ 0__: Я предполагаю, что это что-то в этом роде, но ни одно из моих перехватов не попало в решение, и тот факт, что он работает так, как ожидалось в случае с Maven, заставляет меня думать, что это должно быть что-то простое. –

ответ

5

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 
) 

В этом проекте вам нужно только бы, что для bar и baz.

ОБНОВЛЕНИЕ: С sbt 0.13, <+= и <++= ненужные (и нелегальный) благодаря новому макросъемке на основе синтаксиса:

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