我有一个sbt项目,其中包括代码生成。
build.sbt的一部分是
lazy val generator = (project in file("generator")).
settings(mainClass := Some("com.example.Generator"))
lazy val generate = (project in file("generate")).
dependsOn(generator).
settings(runGeneration)
def runGeneration: SettingsDefinition = sourceGenerators in Compile += Def.taskDyn {
val cachedFun = FileFunction.cached(
streams.value.cacheDirectory / "generation"
) { (in: Set[File]) =>
val dir = (sourceManaged in Compile).value
(generator / run in Compile).toTask(" " + dir.getAbsolutePath).value
collectFiles(dir)
}
val dependentFiles = ((generator / fullClasspath in Compile) map { cp => cp.files }).taskValue.value
val genFiles = cachedFun(dependenctFiles).toSeq
Def.task {
genFiles
}
}.taskValue
这似乎有效,并且仅在依赖项已更改时才生成文件。但是,我希望有多个生成器。我没有复制代码,而是尝试将generator
项目传递给它:
lazy val generate = (project in file("generate")).
dependsOn(generator).
settings(runGeneration(generator))
def runGeneration(p: project): SettingsDefinition =
<same as before but with p instead of generator>
这会导致在解析构建文件时出错:
build.sbt:155: error: Illegal dynamic reference: File
val dependentFiles = ((p / fullClasspath in Compile) map { cp => cp.files }).taskValue.value
^
[error] sbt.compiler.EvalException: Type error in expression
[error] sbt.compiler.EvalException: Type error in expression
我猜测问题在于,如果存在依赖项循环,它无法在编译时确定,因此它会收敛地产生错误。
是否有办法使它正常工作?是否有完全不同的结构让我知道运行generator
是否会产生不同的结果?
答案 0 :(得分:0)
潜在的问题是sbt
中的任务定义具有两个组成部分,看起来它们可以混合在一起,但不能混合。如果您编写类似
Def.task {
val doIt = checkIfShouldDoIt()
if (doIt) {
someTask.value
} else {
()
}
}
如果someTask
为true,那么它看起来只会运行doIt
。实际发生的情况是,someTask.value
在someTask
上声明了此任务的依赖性,并且someTask
在对该任务执行任何操作之前就已运行。要以一种更直接地映射到实际发生的方式编写上述代码,可以编写
Def.task {
val someTaskValue = someTask.value
val doIt = checkIfShouldDoIt()
if (doIt) {
someTaskValue
} else {
()
}
}
仅当依赖项已更改时才尝试运行任务的尝试无法在单个任务中进行。
我的工作解决方案执行以下操作。我修改了生成器以接受其他参数,如果该参数为false,则不执行任何操作。这两个任务是
// Task to check if we need to generate
def checkGeneration(p: Project) = Def.taskDyn {
var needToGenerate = false
val cachedFunction = FileFunction.cached(someDir) {
(in: Set[File]) =>
needToGenerate = ! in.isEmpty
Set()
}
val dependentFiles = ((p / fullClasspath in Compile) map { cp => cp.files }).taskValue
Def.task {
cachedFun(dependentFiles.value.toSet)
needToGenerate
}
}
// Task to run generation
def runGeneration(p: Project): SettingsDefinition = sourceGenerators in Compile += Def.taskDyn {
val needToGenerate = checkGeneration(p).value
Def.task {
// Run generator as before but pass needToGenerate as additional argument
...
// Used FileFunction.cached as before to find the generated files (but not run the generator)
...
}
}
我可能有比我需要的更多动态任务,但这可行。