2012-07-16 11 views
14

Mi piacerebbe compilare un progetto che contiene un generatore di sorgenti Java e quindi compilare il codice generato all'interno di un singolo progetto. I.e: compilare Generator.scala, eseguire Generator.generate (outputDir), compilare outputDir, pacchetto in un jar. Sto cercando questo:SBT genera codice utilizzando il generatore definito dal progetto

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

ma sbt lamenta

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

In sostanza, sbt non vede generatore definito nel progetto si compila. È possibile farlo a modo mio con sbt?

+0

Anch'io ho litigato con questo scenario esatto. Non ho una risposta per te, ancora un principiante sbt. Ma aspetterò anche una risposta. –

risposta

13

Quindi, dopo aver scavato un po 'su questo, ho trovato una soluzione. Innanzitutto, è necessario suddividere il progetto in due sottoprogetti. gen ha tutta la sorgente che include il tuo codice generatore. use dipende da gen e utilizza il generatore.

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 
     } 
    } 

In questo caso, mi stava generando .java file in modo passai in javaSource al generatore.

È importante non farlo quando si utilizzano sourceGenerators così come siamo qui, l'attività eseguita deve restituire uno Seq[File] di tutti i file che sono stati generati in modo tale che sbt possa gestirli. In questa implementazione, il nostro generatore restituisce i nomi dei file di percorso completi allo standard e li salviamo in un file temporaneo.

Come per tutte le cose di Scala e sicuramente SBT, puoi fare qualsiasi cosa, devi solo scavare dentro.

+0

Ottimo post, questo ha funzionato per me, anche se preferisco usare 'sourceManaged in Compile' come directory di output (come raccomandato nei documenti di sbt). –

+0

Inoltre, penso che non dovresti usare (e ho bisogno) '.dependsOn (gen)', perché quando pubblichi il tuo progetto avrai una dipendenza della libreria non necessaria da 'use' a' gen'. –

+0

Come si fa Fork.ForkScala in sbt 1.0 e successive? – ChoppyTheLumberjack

1

La descrizione del progetto viene compilata durante il caricamento. Non c'è modo di chiamare direttamente il nuovo codice generato in fase di esecuzione. A meno che non indovini l'uso di una sorta di riflesso, assicurandoti che non ci sia la biforcazione della JVM e che in qualche modo le classi siano caricate nel classloader.

L'unico modo in cui posso pensare di farlo è creare un progetto all'interno della definizione del progetto.

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

Nella definizione più interna del progetto si può essere in grado di definire l'esterno src come cartella src. Questo ti porterà una versione compilata del generatore disponibile per il vero progetto. Quindi nella normale definizione del progetto aggiungi un'importazione al generatore e usalo come stavi facendo.

Sono abbastanza sicuro che la maggior parte del progetto interno verrà caricata e compilata solo una volta. È necessario che sbt ricarichi la definizione del progetto se apporti modifiche al generatore. Uscire e riaprire è il modo più semplice/più stupido di farlo, ma può aiutare a testare. Cercate in seguito modi più intelligenti di ricaricare se funziona.

+0

È necessario creare due progetti separati, uno per la sorgente del generatore e l'altro per la sorgente 'generata'. –

Problemi correlati