2012-07-16 17 views
14

Me gustaría compilar un proyecto que contiene un generador de fuente Java y luego compilar el código generado dentro de un único proyecto. Es decir: compilar Generator.scala, ejecutar Generator.generate (outputDir), compilar outputDir, paquete en un contenedor. Estoy tratando esto:SBT genera código usando el generador definido por el proyecto

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

pero SBT se queja

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

Básicamente, SBT no ve Generador define en el proyecto se compila. ¿Es posible hacerlo a mi manera con sbt?

+0

Yo también he estado luchando con este escenario exacto. No tengo una respuesta para ti, sigue siendo un novato sbt. Pero también estará esperando una respuesta. –

Respuesta

13

Entonces, después de investigar un poco, he encontrado una solución. En primer lugar, debe dividir su proyecto en dos subproyectos. gen tiene toda la fuente que incluye su código de generador. use depende de gen y utiliza el generador.

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

En este caso, yo estaba generando archivos .java así que pasé en javaSource al generador.

No es importante que al usar generadores de origen como estamos aquí, la tarea ejecutada debe devolver un Seq[File] de todos los archivos que se generaron para que sbt pueda administrarlos. En esta implementación, nuestro generador genera los nombres de los archivos de ruta completa a la salida estándar y los guardamos en un archivo temporal.

Al igual que con todas las cosas Scala y seguramente SBT, puede hacer cualquier cosa, solo necesita profundizar en ella.

+0

Gran publicación, esto funcionó para mí, aunque prefiero usar 'sourceManaged in Compile' como directorio de salida (como se recomienda en los documentos sbt). –

+0

Además, creo que no debería usar (y necesita) '.dependsOn (gen)', porque cuando publique su proyecto tendrá una dependencia innecesaria de la librería de 'use' a' gen'. –

+0

¿Cómo se hace Fork.ForkScala en sbt 1.0 y posterior? – ChoppyTheLumberjack

1

La descripción del proyecto se compila al cargarlo. No hay forma de llamar directamente al nuevo código generado en tiempo de ejecución. A menos que supongo que utilice algún tipo de reflexión para asegurarse de que no hay bifurcación de la JVM y de alguna manera tener esas clases cargadas en el cargador de clases.

La única forma en que puedo pensar en hacerlo es haciendo un proyecto dentro de la definición de tu proyecto.

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

En la definición más interna del proyecto, es posible que pueda definir el src externo como la carpeta src. Eso te dará una versión compilada del Generador disponible para el proyecto real. Luego, en la definición normal del proyecto, agregue una importación al generador y úselo como estaba haciendo.

Estoy bastante seguro de que el proyecto más interno solo se cargará y compilará una vez. Deberá tener sbt para volver a cargar la definición del proyecto si realiza cambios en el generador. Salir y volver a abrir es la manera más simple/más tonta de hacerlo, pero puede ayudar a las pruebas. Busque formas más inteligentes de volver a cargar si funciona.

+0

Necesita crear dos proyectos separados, uno para la fuente del generador y el otro para la fuente 'generated'. –

Cuestiones relacionadas