【问题标题】:SBT sourceGenerators task - execute only if a file changesSBT sourceGenerators 任务 - 仅在文件更改时执行
【发布时间】:2019-03-02 22:11:10
【问题描述】:

在我的 SBT 项目中,我有一个输入文件 src/main/greeting/Greeting.txt,其内容如下:

Hello, world!

这是我的build.sbt,它从Greeting.txt 文件生成Scala 源代码:

sourceGenerators in Compile += Def.task{
  println("GENERATING FILES")
  val inputFile = file("src/main/greeting/Greeting.txt")
  val generatedFile =
    (sourceManaged in Compile).value / "scala" / "Main.scala"
  val greeting = IO.read(inputFile).trim
  IO.write(
    generatedFile,
    s"""object Main extends App { println("${greeting}") }"""
  )
  Seq(generatedFile)
}.taskValue

这个build.sbt 工作正常,除了它运行我的任务以生成 Scala 源代码每次我编译/运行我的项目。我希望它仅在Greeting.txt-文件更改时运行这些任务。我怎样才能做到这一点?


MCVE

生成项目的 Bash 脚本:

#!/bin/bash
mkdir sourceGeneratorsExample
cd sourceGeneratorsExample
mkdir -p src/main/scala
mkdir -p src/main/greeting
echo "Hello, world!" >> src/main/greeting/Greeting.txt
cat <<HEREDOC > build.sbt
sourceGenerators in Compile += Def.task{
  println("GENERATING FILES")
  val inputFile = file("src/main/greeting/Greeting.txt")
  val generatedFile =
    (sourceManaged in Compile).value / "scala" / "Main.scala"
  val greeting = IO.read(inputFile).trim
  IO.write(
    generatedFile,
    "object Main extends App { println(\"" + greeting + "\") }"
  )
  Seq(generatedFile)
}.taskValue
HEREDOC

重复/文档

  • This 是 2012 年的答案,从那时起发生了很多变化。
  • current reference manual 建议使用 "sbt.Tracked.{ inputChanged, outputChanged } etc",但没有对此进行扩展,并且手册中的其他任何地方都没有提到 Tracked 对象。

【问题讨论】:

标签: scala sbt


【解决方案1】:

你可以使用FileFunction.cached,这是一个:

用于帮助构建/工件生成/等步骤检测它们是否需要运行的通用更改检测助手。

它使用缓存文件夹,SBT 会自动记录文件更改。使用FileFunction.cached,您的build.sbt 可能如下所示:

sourceGenerators in Compile += Def.task{

  // * Create a cached function which generates the output files
  //   only if the input files have changed.
  // * The first parameter is a file instance of the path to
  //   the cache folder
  // * The second parameter is the function to process the input 
  //   files and return the output files
  val cachedFun = FileFunction.cached(
    streams.value.cacheDirectory / "greeting"
  ) { (in: Set[File]) =>

    println("GENERATING FILES")

    val generatedFile =
      (sourceManaged in Compile).value / "scala" / "Main.scala"
    val greeting = IO.read(in.head).trim
    IO.write(
      generatedFile,
      "object Main extends App { println(\"" + greeting + "\") }"
    )
    Set(generatedFile)
  }

  // get the input file
  val inputFile = file("src/main/greeting/Greeting.txt")

  // put the input file into a `Set` (as required by `cachedFun`),
  // pass it to the `cachedFun`,
  // convert the result to `Seq` (as required by `Def.task`)
  cachedFun(Set(inputFile)).toSeq

}.taskValue

FileFunction.cached 的第一个参数是一个目录,用于存储缓存信息(例如输入文件的哈希值)。在这里,我们传递了streams.value.cacheDirectory / "greeting",它将在target-目录内的某处创建一个缓存子目录。好处是他的目录会在clean任务运行时自动清理。

cached 方法的第一个参数列表采用两个额外的可选inStyleoutStyle 参数,它们确定如何检测更改(例如,通过修改日期,或通过比较哈希)。请注意,在旧版本的 SBT 中,这两个参数是强制性的,因此您的 cachedFun 看起来有点像这样:

val cachedFun = FileFunction.cached(
  cacheBaseDirectory = streams.value.cacheDirectory / "greeting",
  inStyle = FilesInfo.lastModified,
  outStyle = FilesInfo.exists
)(cachedFunBodyImpl)

FileFunction.cached 方法的第二个参数列表采用一个函数,该函数将输入文件的Set 映射到输出文件的Set。只有在输入文件发生变化时才会调用它。

您可以找到有关旧版 SBT here (SBT 0.13.5) 的更多信息,该版本扩展了 cached 和文件跟踪样式。引用:

第一个参数列表有两个附加参数,允许显式指定文件跟踪样式。默认情况下,输入跟踪样式为 FilesInfo.lastModified,基于文件的最后修改时间,输出跟踪样式为 FilesInfo.exists,仅基于文件是否存在。另一种可用的样式是 FilesInfo.hash,它根据文件内容的哈希跟踪文件。


第一个代码 sn-p 已经使用 SBT 1.2.8 进行了测试。第二个代码 sn-p 也应该适用于早期的 0.13.x 版本。

【讨论】:

  • cachedFun,是否可以执行另一个任务?我的代码生成涉及运行完整的 Java 程序。 Sbt 已经设置了类路径,所以理论上,我只需要做(genProj / run in Compile).toTask("").value 但这会给我关于非法动态访问的错误。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-08-19
  • 2018-04-02
  • 1970-01-01
  • 1970-01-01
  • 2017-05-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多