2012-07-16 5 views
14

Chciałbym skompilować projekt, który zawiera generator źródła java, a następnie skompilować wygenerowany kod w ramach jednego projektu. Na przykład: skompiluj Generator.scala, uruchom Generator.generate (outputDir), skompiluj outputDir, spakuj do słoika. Próbuję to:SBT generowanie kodu przy użyciu zdefiniowanego projektu generatora

sourceGenerators in Compile <+= sourceManaged in Compile map { out => 
    Generator.generate(out/"generated") 
} 

ale SBT narzeka

[error] Build.scala:1: object example is not a member of package org 
[error] import org.example.Generator 

Zasadniczo SBT nie widzi Generator zdefiniowane w projekcie to kompiluje. Czy można to zrobić po swojemu z sbt?

+0

Ja też zmagałem się z tym dokładnym scenariuszem. Nie mam dla ciebie odpowiedzi, wciąż nowicjusz sbt. Ale będzie też czekał na odpowiedź. –

Odpowiedz

13

Więc, po tym jak się trochę nad tym zastanowiłem, znalazłem rozwiązanie. Najpierw musisz podzielić projekt na dwa podrzędne projekty. gen zawiera całe źródło zawierające kod generatora. use zależy od gen i używa generatora.

import sbt._ 
    import Keys._ 
    import java.io.{ File ⇒ JFile, FileOutputStream } 

    object OverallBuild extends Build { 

     lazy val root = Project(id = "overall", base = file(".")).aggregate(gen, use) 

     lazy val gen = Project(id = "generate", base = file("gen")) 

     val myCodeGenerator = TaskKey[Seq[File]]("mycode-generate", "Generate My Awesome Code") 

     lazy val use = Project(id = "use", base = file("use"), 
     settings = Defaults.defaultSettings ++ Seq(

      sourceGenerators in Compile <+= (myCodeGenerator in Compile), 

      myCodeGenerator in Compile <<= 
      (javaSource in Compile, dependencyClasspath in Runtime in gen) map { 

       (javaSource, cp) ⇒ runMyCodeGenerator(javaSource, cp.files) 

      })).dependsOn(gen) 

     def runMyCodeGenerator(javaSource: File, cp: Seq[File]): Seq[File] = { 
     val mainClass = "com.yourcompany.myCodeGenerator" 
     val tmp = JFile.createTempFile("sources", ".txt") 
     val os = new FileOutputStream(tmp) 

     try { 
      val i = new Fork.ForkScala(mainClass).fork(None, Nil, cp, 
      Seq(javaSource.toString), 
      None, 
      false, 
      CustomOutput(os)).exitValue() 

      if (i != 0) { 
      error("Trouble with code generator") 
      } 
     } finally { 
      os.close() 
     } 
     scala.io.Source.fromFile(tmp).getLines.map(f ⇒ file(f)).toList 
     } 
    } 

W tym przypadku, byłem generowania plików .java więc zdałem w javaSource do generatora.

Ważne jest, aby nie było tak, że gdy używamy sourceGenerators, jak jesteśmy tutaj, wykonane zadanie musi zwrócić Seq[File] ze wszystkich plików, które zostały wygenerowane, aby sbt mógł nimi zarządzać. W tej implementacji nasz generator wyprowadza pełne nazwy plików ścieżek do standardowego wyjścia i zapisujemy je do pliku tymczasowego.

Podobnie jak w przypadku wszystkich rzeczy Scala i na pewno SBT, możesz zrobić wszystko, wystarczy się w to zagłębić.

+0

Świetny post, to działało dla mnie, chociaż wolę używać 'sourceManaged in Compile' jako katalogu wyjściowego (zgodnie z zaleceniami w dokumentach sbt). –

+0

Ponadto, myślę, że nie powinieneś używać (i potrzebujesz) '.dependsOn (gen)', ponieważ kiedy opublikujesz swój projekt, będziesz mieć niepotrzebną zależność od 'use' do' gen'. –

+0

Jak zrobić Fork.ForkScala w sbt 1.0 i nowszych? – ChoppyTheLumberjack

1

Opis projektu jest kompilowany podczas ładowania. Nie ma możliwości bezpośredniego wywołania nowego kodu wygenerowanego w czasie wykonywania. Chyba że za pomocą jakiegoś odbicia upewniam się, że nie ma rozwidlenia JVM i w jakiś sposób mam załadowane te klasy do programu ładującego klasy.

Jedyny sposób, w jaki mogę to zrobić, to umieszczenie projektu w definicji twojego projektu.

root 
- src 
- project/ 
    - Build.scala // normal project definition 
    - project/ 
    - Build.scala // inner most 

W definicji najbardziej wewnętrznej wewnętrznej może być możliwe zdefiniowanie zewnętrznego src jako folderu src. To dostarczy Ci skompilowaną wersję Generatora dostępną dla prawdziwego projektu. Następnie w normalnej definicji projektu dodaj import do Generatora i używaj go tak, jak robiłeś.

Jestem prawie pewien, że najbardziej wewnętrzny projekt zostanie załadowany i skompilowany tylko raz. Będziesz musiał przeładować definicję projektu sbt, jeśli dokonasz zmian w Generatorze. Wyjście i ponowne otwarcie jest najprostszym/najgłupszym sposobem, ale może pomóc w testowaniu. Wyszukaj później inteligentniejsze sposoby ponownego ładowania, jeśli działa.

+0

Należy utworzyć dwa oddzielne projekty, jeden dla źródła generatora, a drugi dla źródła "wygenerowanego". –