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

时间:2016-06-30 18:24:33

标签: scala sbt

我想有一个可以运行的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)
}

所以使用上面的build.sbt我可以运行创建的sbt gensomecode target/scala-2.11/src_managed/main/SomeGenCode.scala sbt放置“托管来源”的默认位置。

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

的src /主/阶/ Main.scala:

object Main extends App {
  SomeGenCode.doSomething()
}

我唯一能做的就是在根项目的sourceManaged中包含默认的unmanagedSourceDirectories目录(参见build.sbt:line 4又名fugly way...评论下面的行)。这很丑陋,看起来不像是应该如何处理托管来源。

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

我错过了什么?

2 个答案:

答案 0 :(得分:2)

我熟悉三种选择:

  1. 生成非托管源目录。

  2. 通过添加sourceGenerators in Compile <+= gensomecode

  3. 在每次运行时生成
  4. 与(2)类似,但使用缓存,因此它不会在每次编译时生成文件。以下完整示例。

  5. 在此示例中,缓存基于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
    }
    

答案 1 :(得分:0)

希望我为时不晚,但让我们看一下有关Unmanaged vs Managed files的部分

类路径,源和资源分为两个主要类别:非托管和托管。非托管文件是在构建控制范围之外的手动创建的文件。它们是构建的输入。托管文件受构建的控制。这些包括生成的源和资源以及已解析和已检索的依赖项以及已编译的类。

“非托管与托管”之间的主要区别似乎是“手动与自动”。现在,我们来看一下"generating files"的文档。我们将立即注意到,这意味着“自动生成文件”,因为生成文件将在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"

希望有帮助!