将项目传递到sbt动态任务

时间:2019-06-05 17:55:33

标签: sbt

我有一个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是否会产生不同的结果?

1 个答案:

答案 0 :(得分:0)

潜在的问题是sbt中的任务定义具有两个组成部分,看起来它们可以混合在一起,但不能混合。如果您编写类似

的代码
Def.task {
  val doIt = checkIfShouldDoIt()

  if (doIt) {
    someTask.value
  } else { 
    ()
  }
}

如果someTask为true,那么它看起来只会运行doIt。实际发生的情况是,someTask.valuesomeTask上声明了此任务的依赖性,并且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)
    ...
  }
}

我可能有比我需要的更多动态任务,但这可行。