如何避免重新编译生成的源代码

时间:2014-07-03 22:20:04

标签: scala sbt

使用sbt的

Generating boilerplate源代码可以正常工作:

sourceGenerators in Compile <+= sourceManaged in Compile map { srcDir =>
  DslBoilerplate.generate(srcDir, Seq(
    "path/to/a/definition/file"
  ))
}

当我运行sbt compile时,这也会编译生成的源代码文件,从而产生一些类文件。我只是不希望每次在开发过程中重新编译项目时都重新编译生成的源代码。

所以,从类文件中我创建了一个jar文件并使用它来代替生成的源/类文件(我删除了那些文件)。这工作正常,现在可以通过jar文件访问生成的代码。有没有办法让sbt在初始项目构建中执行4个步骤(如果需要?)?:

  1. 生成源代码文件
  2. 编译这些文件
  3. 从生成的类文件
  4. 创建一个jar
  5. 删除源文件和类文件
  6. (在this question中他们使用sbt.IO.jar方法创建一个jar但是他们已经有了现有的文件......)

    还是有另一种比制作jar更好的方法来避免重新编译生成的源代码吗?


    更新1(见下面的更新2)

    谢谢,塞思,谢谢你的回答!由于缓存现在记住它们已经创建,因此避免在每个项目编译时生成源文件都很有效。我肯定会使用这个功能,谢谢!

    但这实际上不是我原来的问题。很抱歉不够清楚。如果我们认为这发生了2次转换,那可能会更清楚:

    输入文件 --- 1 ---&gt; 源文件(* .scala) --- 2- - &gt; 目标文件(* .class)

    转换的位置

    1. 生成源代码(来自输入文件中的某些信息)和
    2. 编译生成的源代码
    3. 当我使用sbt compile编译项目时,这一切都正常。

      但是如果我“重建项目”(在IntelliJ中),生成的源代码(来自sbt编译)将再次编译,这就是我想要避免的 - 但同时可以访问该代码。有没有其他方法可以避免编译,而不是将此代码放入jar中,然后删除源文件和目标文件?

      所以我试着继续沿着与sbt搏斗的思路来使它创建一个源和目标jar - 仍然无法制作目标jar。这是我到目前为止提出的(在this的帮助下):

      sourceGenerators in Compile += Def.task[Seq[File]] {
        val srcDir = (sourceManaged in Compile).value
        val targetDir = (classDirectory in Compile).value
      
        // Picking up inputs for source generation
        val inputDirs = Seq("examples/src/main/scala/molecule/examples/seattle")
      
        // generate source files
        val srcFiles = DslBoilerplate.generate(srcDir, inputDirs)
      
        // prepare data to create jars
        val srcFilesData = files2TupleRec("", srcDir)
        val targetFilesData = files2TupleRec("", targetDir)
      
        // debug
        println("### srcDir: " + srcDir)
        println("### srcFilesData: \n" + srcFilesData.mkString("\n"))
        println("### targetDir: " + targetDir)
        println("### targetFilesData: \n" + targetFilesData.mkString("\n"))
      
        // Create jar from generated source files - works fine
        val srcJar = new File("lib/srcFiles.jar/")
        println("### sourceJar: " + srcJar)
        sbt.IO.jar(srcFilesData, srcJar, new java.util.jar.Manifest)
      
        // Create jar from target files compiled from generated source files
        // Oops - those haven't been created yet, so this jar becomes empty... :-(
        // Could we use dependsOn to have the source files compiled first?
        val targetJar = new File("lib/targetFiles.jar/")
        println("### targetJar: " + targetJar)
        sbt.IO.jar(targetFilesData, targetJar, new java.util.jar.Manifest)
      
        val cache = FileFunction.cached(
          streams.value.cacheDirectory / "filesCache",
          inStyle = FilesInfo.hash,
          outStyle = FilesInfo.hash
        ) {
          in: Set[File] => srcFiles.toSet
        }
        cache(srcFiles.toSet).toSeq
      }.taskValue
      
      def files2TupleRec(pathPrefix: String, dir: File): Seq[Tuple2[File, String]] = {
        sbt.IO.listFiles(dir) flatMap {
          f => {
            if (f.isFile && f.name.endsWith(".scala")) Seq((f, s"${pathPrefix}${f.getName}"))
            else files2TupleRec(s"${pathPrefix}${f.getName}/", f)
          }
        }
      }
      

      也许我还不需要创建罐子?也许它们不应该在源生成任务中创建?我需要帮助......


      更新2

      傻我!难怪如果我使用f.name.endsWith(".scala"),dohh

      过滤它们,我就无法制作带有类文件的jar

      由于我最初的问题不是那么清楚,而塞思的答案正在解决一个明显的解释,我会接受他的回答(经过更多调查后,我看到我应该提出另一个问题)。

1 个答案:

答案 0 :(得分:2)

您希望使用FileFunction.cached,以便不会每次都重新生成源文件。

这是我自己构建的一个例子:

sourceGenerators in Compile += Def.task[Seq[File]] {
  val src = (sourceManaged in Compile).value
  val base = baseDirectory.value
  val s = streams.value
  val cache =
    FileFunction.cached(s.cacheDirectory / "lexers", inStyle = FilesInfo.hash, outStyle = FilesInfo.hash) {
      in: Set[File] =>
        Set(flex(s.log.info(_), base, src, "ImportLexer"),
            flex(s.log.info(_), base, src, "TokenLexer"))
    }
  cache(Set(base / "project" / "flex" / "warning.txt",
            base / "project" / "flex" / "ImportLexer.flex",
            base / "project" / "flex" / "TokenLexer.flex")).toSeq
}.taskValue

此处.txt.flex文件是生成器的输入文件。生成源文件的实际工作是通过我的flex方法进行的,该方法返回java.io.File

def flex(log: String => Unit, base: File, dir: File, kind: String): File =
  ...

您应该能够将此技术应用于您的构建。

FileFunction.cached在API文档和sbt常见问题解答中描述了“如果输入文件未更改,任务如何避免重做工作?” (http://www.scala-sbt.org/0.13/docs/Faq.html)。 (如果缓存上的材料也来自http://www.scala-sbt.org/0.13/docs/Howto-Generating-Files.html,那将会很好;目前它不是。)