如何避免重新编译生成的源代码 [英] How to avoid re-compiling generated source code

查看:63
本文介绍了如何避免重新编译生成的源代码的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

生成样板 使用 sbt 工作的源代码很好:

Generating boilerplate source code with sbt works fine:

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

当我运行 sbt compile 时,这也会编译生成的源代码文件,从而生成一些类文件.只是不希望每次开发时重新编译生成的源代码.

When I run sbt compile this also compiles the generated source code files, thus producing some class files. I just don't want the generated source code to be re-compiled every time I re-compile the project during development.

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

So, from the class files I made a jar file and used this instead of the generated source/class files (I deleted those). This worked fine, now having access to the generated code through the jar file. Is there a way though to let sbt do the 4 steps (if needed?) in the initial project build?:

  1. 生成源代码文件
  2. 编译这些文件
  3. 从生成的类文件创建一个 jar
  4. 删除源文件和类文件

(在 this question 他们使用 sbt.IO.jar 方法来创建一个 jar,但他们已经有了现有的文件...)

(In this question they use the sbt.IO.jar method to create a jar but there they already have existing files...)

或者有没有比制作 jar 更好的方法来避免重新编译生成的源代码?

Or is there another better approach than making a jar to avoid re-compiling generated source code?

谢谢赛斯,谢谢你的回答!避免在每个项目编译时生成源文件非常有效,因为缓存现在会记住它们已被创建.我一定会使用这个功能,谢谢!

Thanks, Seth, for your answer! It worked great to avoid generating the source files with each project compilation since the cache now remembers that they have been created. I'll certainly use this feature, thanks!

但这实际上并不是我最初提出问题时的想法.抱歉不够清楚.如果我们将其视为发生的 2 个转换可能会更清楚:

But this was actually not what I had in mind with my original question. Sorry for not being clear enough. It might be clearer if we think of this as 2 transformations happening:

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

Input file ---1---> Source file (*.scala) ---2---> Target file (*.class)

转换在哪里

  1. 生成源代码(来自输入文件中的一些信息)和
  2. 编译生成的源代码

当我使用 sbt compile 编译项目时,这一切正常.

This all works fine when I compile the project with sbt compile.

但是如果我重建项目"(在 IntelliJ 中),生成的源代码(来自 sbt 编译)将再次编译,这就是我想要避免的 - 但同时可以访问该代码.除了将这段代码放在jar中然后删除源文件和目标文件之外,还有其他避免编译的方法吗?

But then if I "rebuild the project" (in IntelliJ), the generated source code (from the sbt compilation) will compile again, and that's what I want to avoid - but at the same time have access to that code. Is there any other way to avoid compilation than placing this code in a jar and then delete the source and target files?

所以我试图继续沿着这条思路与 sbt 搏斗,让它创建一个源和目标 jar - 仍然无法创建目标 jar.这是我到目前为止想出的(在 这个):

So I tried to continue along that line of thought wrestling with sbt to make it create a source and target jar - still can't make the target jar. This is what I came up with so far (with help from 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)
    }
  }
}

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

Maybe I still don't need to create jars? Maybe they shouldn't be created in the source generation task? I need help...

骗我!!!难怪如果我用 f.name.endsWith(".scala") 过滤它们,我不能用类文件制作 jar,dohh

Silly me!!! No wonder I can't make a jar with class files if I filter them with f.name.endsWith(".scala"), dohh

因为我最初的问题不是那么清楚,而且赛斯的回答是针对一个明显的解释,所以我会接受他的回答(经过更多调查后,我认为我可能应该再问一个问题).

Since my initial question was not that clear, and Seth's answer is addressing an obvious interpretation, I'll accept his answer (after investigating more, I see that I should probably ask another question).

推荐答案

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

You want to use FileFunction.cached so that the source files aren't regenerated every time.

这是我自己构建的示例:

Here's an example from my own build:

Compile / sourceGenerators += Def.task[Seq[File]] {
  val src = (Compile / sourceManaged).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:

Here the .txt and .flex files are input files to the generator. The actual work of generating the source files is farmed out to my flex method, which returns a java.io.File:

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

您应该能够将这种技术应用于您的构建.

You should be able to adapt this technique to your build.

FileFunction.cached 在 API 文档和 sbt FAQ 中的如果输入文件不变,任务如何避免重做工作?"下有描述.(http://www.scala-sbt.org/0.13/docs/Faq.html).(如果关于缓存的材料是从 http://www.scala-sbt.org/0.13/docs/Howto-Generating-Files.html 也是;目前不是.)

FileFunction.cached is described in the API doc and in the sbt FAQ under "How can a task avoid redoing work if the input files are unchanged?" (http://www.scala-sbt.org/0.13/docs/Faq.html). (It would be nice if the material on caching was referenced from http://www.scala-sbt.org/0.13/docs/Howto-Generating-Files.html as well; currently it isn't.)

这篇关于如何避免重新编译生成的源代码的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆