如何创建一个sbt任务来生成代码,然后在根项目中包含这些生成的托管源代码?



我想有一个任务,我可以运行生成一些代码。我不想在每次运行时都生成这个,只是偶尔手动运行这个任务。我创建了一个骨架项目来解释(https://github.com/jinyk/sbtmanagedsrc)。

build.sbt:

lazy val root = (project in file("."))
    .settings(scalaVersion := "2.11.8")
    .settings(gensomecode := genSomeCodeTask.value)
    /////////////////////////////////////////////////////////////
    // fugly way to get managed sources compiled along with main
    .settings(unmanagedSourceDirectories in Compile += baseDirectory.value / "target/scala-2.11/src_managed/")
    /////////////////////////////////////////////////////////////
lazy val gensomecode = taskKey[Seq[File]]("gen-code")
lazy val genSomeCodeTask = Def.task {
  val file = (sourceManaged in Compile).value / "SomeGenCode.scala"
  println("file: " + file)
  IO.write(file, """object SomeGenCode {
                   |  def doSomething() {
                   |    println("Hi!")
                   |  }
                   |}""".stripMargin)
  Seq(file)
}

所以构建。上面我可以运行sbt gensomecode,它创建target/scala-2.11/src_managed/main/SomeGenCode.scala: sbt放置"托管源"的默认位置。

我想让这个SomeGenCode对根项目可用。

src/main/scala/Main.scala:

object Main extends App {
  SomeGenCode.doSomething()
}

我唯一能想到的是在根项目的unmanagedSourceDirectories中包含默认的sourceManaged目录(参见build.sbt:line 4,即fugly way...注释下面的行)。这是非常丑陋的,看起来不像是托管源应该被如何处理。

我可能不太了解sbt的托管源概念,或者如何处理创建sbt任务来生成源的情况。

我错过了什么?

我熟悉三个选项:

  1. 生成到非托管源目录。

  2. 每次运行时生成,添加sourceGenerators in Compile <+= gensomecode

  3. 类似于(2),但使用缓存,所以它不会在每次编译时生成文件。完整示例如下:

在本例中,缓存基于build.sbt的内容,因此无论何时该文件被更改,它都会重新生成该文件。

lazy val root = (project in file("."))
    .settings(scalaVersion := "2.11.8")
    .settings(gensomecode <<= genSomeCodeTask)
sourceGenerators in Compile <+= genSomeCodeTask
lazy val gensomecode = taskKey[Seq[File]]("gen-code")
def generateFile(sourceManaged: java.io.File) = {
  val file = sourceManaged / "main" / "SomeGenCode.scala"
  println("file: " + file)
  IO.write(file, """object SomeGenCode {
                   |  def doSomething() {
                   |    println("Hi!")
                   |  }
                   |}""".stripMargin)
  Set(file)
}
def genSomeCodeTask = (sourceManaged in Compile, streams).map {
  (sourceManaged, streams) =>
  val cachedCompile = FileFunction.cached(
      streams.cacheDirectory / "mything", 
      inStyle = FilesInfo.lastModified,
      outStyle = FilesInfo.exists) { 
        (in: Set[java.io.File]) =>
          generateFile(sourceManaged)
      }
  cachedCompile(Set(file("build.sbt"))).toSeq
}

我希望我的回答还来得及,但是让我们来看看这一节关于非托管文件和托管文件

类路径、源和资源被分为两大类:非托管和托管。非托管文件是在构建控制之外手动创建的文件。它们是构建的输入。托管文件处于构建的控制之下。这些包括生成的源和资源,以及解析和检索的依赖项和编译的类。

似乎"非托管与托管"之间的关键区别是"手动与自动"。现在,如果我们看一下"生成文件"的文档。我们会立即注意到它的意思是"自动生成文件",因为生成文件将发生在sbt compile

Compile / sourceGenerators += <task of type Seq[File]>.taskValue

有意义。因为在sbt compile期间发生的任何事情都应该在sbt clean期间删除。

现在,从下面的代码来看,似乎您正在尝试生成一个非托管源文件(您没有使用sourceGenerators,不是吗?)到托管源文件目录。这样做最明显的问题是,每次调用sbt clean时,源文件都会被删除,因此必须再次运行此任务以获取此文件(更糟糕的是,您必须手动运行此任务,而不是让sbt compile为您执行此任务),从而违背了偶尔手动执行此任务的目的。

val file = (sourceManaged in Compile).value / "SomeGenCode.scala"

要解决这个问题,你必须手动生成文件到非托管源,这基本上是你的源代码目录(这取决于-我的是"/app")。然而,您必须以某种方式注释这些文件是通过某种方式生成的。我的解决方案是:

val file = (scalaSource in Compile).value / "generated" / "SomeGenCode.scala"

希望这有帮助!

最新更新