2012-07-16 90 views
14

我想编译一个包含java源代码生成器的项目,然后在单个项目中编译生成的代码。 I.e:编译Generator.scala,运行Generator.generate(outputDir),编译outputDir,打包成jar。 我想这一点:SBT使用项目定义的生成器生成代码

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

但SBT抱怨

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

基本上,SBT没有看到发生器在它编译项目定义。 有没有可能以我的方式与sbt?

+0

我也一直在摔跤这个确切的情况。我没有为你回答,仍然是一个新手。但也会等待答案。 – 2012-07-19 11:55:03

回答

13

因此,在深入了解这一点后,我想出了一个解决方案。首先,你需要将你的项目分成两个子项目。 gen包含您的生成器代码的所有源代码。 use取决于gen并使用发生器。

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

在这种情况下,让我在javaSource传递给发电机,我产生.java文件。

当我们在这里使用sourceGenerators时,执行的任务必须返回生成的所有文件的Seq[File]以便sbt可以管理它们,这一点很重要。在这个实现中,我们的生成器将完整的路径文件名输出到标准输出,并将它们保存到一个临时文件中。

就像Scala和SBT的所有事情一样,你可以做任何事情,只需要深入研究。

+0

伟大的文章,这为我工作,虽然我更喜欢使用'sourceManaged in Compile'作为输出目录(建议在sbt文档中)。 – 2013-02-07 18:00:04

+0

另外,我认为你不应该使用(并且需要)'.dependsOn(gen)',因为当你发布你的项目时,你会从'use'到'gen'有一个不必要的库依赖。 – 2013-02-07 18:58:38

+0

你如何在sbt 1.0及更高版本中做Fork.ForkScala? – ChoppyTheLumberjack 2018-02-20 18:48:05

1

项目描述在加载时编译。没有办法直接调用运行时生成的新代码。除非我猜想使用某种反射来确保JVM没有分叉,并以某种方式将这些类加载到类加载器中。

我能想到的唯一方法就是在项目定义中制作一个项目。

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

在最内部的项目定义中,您可以将外部src定义为src文件夹。这将为您提供真实项目可用的Generator的编译版本。然后在正常的项目定义中添加一个导入到Generator并像你一样使用它。

我很确定最内层的项目只会被加载和编译一次。如果对发生器进行更改,则需要重新加载项目定义。退出和重新开放是最简单/最笨的方式,但它可能有助于测试。稍后查找重新加载的更智能的方法,如果它有效的话。

+0

您需要创建两个单独的项目,一个用于生成器源,另一个用于生成的源。 – 2012-07-25 14:48:58