我想有一个任务,我可以运行生成一些代码。我不想在每次运行时都生成这个,只是偶尔手动运行这个任务。我创建了一个骨架项目来解释(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任务来生成源的情况。
我错过了什么?
我熟悉三个选项:
-
生成到非托管源目录。
-
每次运行时生成,添加
sourceGenerators in Compile <+= gensomecode
-
类似于(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"
希望这有帮助!