16

我想编译一个包含 java 源代码生成器的项目,然后在单个项目中编译生成的代码。即:编译Generator.scala,运行Generator.generate(outputDir),编译outputDir,打包成jar。我正在尝试这个:

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 没有在它编译的项目中看到 Generator 定义。sbt可以按照我的方式来做吗?

4

2 回答 2

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

在这种情况下,我正在生成 .java 文件,因此我将其传递javaSource给了生成器。

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

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

于 2012-07-25T14:47:37.067 回答
1

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

我能想到的唯一方法是在您的项目定义中创建一个项目。

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

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

我很确定最里面的项目只会被加载和编译一次。如果您对生成器进行更改,您将需要让 sbt 重新加载项目定义。退出并重新打开是最简单/最愚蠢的方法,但它可能有助于测试。如果可行,请稍后查找更智能的重新加载方式。

于 2012-07-24T23:47:52.170 回答