1. 任务/设置:动机

任务/设置:动机 

本页介绍了任务和设置系统的动机。您应该已经了解如何使用任务和设置,这些在入门指南任务页面中进行了描述。

任务系统的一个重要方面是将构建中的两个常见相关步骤结合起来

  1. 确保执行其他任务。
  2. 使用该任务的一些结果。

早期版本的 sbt 使用以下方法分别配置这些步骤

  1. 依赖声明
  2. 某种形式的共享状态

为了了解将它们结合起来为什么有利,请将这种情况与在 Scala 中延迟初始化变量的情况进行比较。以下 Scala 代码是公开延迟初始化值的糟糕方法

// Define a variable that will be initialized at some point
// We don't want to do it right away, because it might be expensive
var foo: Foo = _

// Define a function to initialize the variable
def makeFoo(): Unit = ... initialize foo ...

典型用法是

makeFoo()
doSomething(foo)

这个例子在糟糕程度上有些夸张,但我认为它与我们两个步骤的任务定义几乎相同。这很糟糕的具体原因包括

  1. 客户端需要知道先调用 makeFoo()
  2. foo 可以被其他代码更改。例如,可能存在 def makeFoo2()
  3. foo 的访问不是线程安全的。

第一点类似于声明任务依赖关系,第二点类似于两个任务修改相同状态(项目变量或文件),第三点是由于不同步的共享状态造成的。

在 Scala 中,我们有内置的功能可以轻松解决这个问题:lazy val

lazy val foo: Foo = ... initialize foo ...

示例用法

doSomething(foo)

在这里,lazy val 在一个 DRY 结构中为我们提供了线程安全性、访问之前的保证初始化以及不可变性。sbt 中的任务系统对任务做了与 lazy val 对我们糟糕示例所做的一样的事情(以及更多,但我们在这里不讨论)。

任务定义必须声明其输入及其输出的类型。sbt 将确保输入任务已运行,然后将其结果提供给实现该任务的函数,该函数将生成其自己的结果。其他任务可以使用此结果,并确保该任务已运行(一次),并且在此过程中是线程安全且类型安全的。

任务定义的一般形式如下

myTask := {
  val a: A = aTask.value
  val b: B = bTask.value
  ... do something with a, b and generate a result ...
}

(这仅仅是关于任务背后想法的讨论,因此有关使用详细信息,请参见sbt 任务页面。)这里,假设 aTask 生成类型为 A 的结果,而 bTask 生成类型为 B 的结果。

应用 

例如,考虑生成一个包含项目二进制 jar、源代码 jar 和文档 jar 的 zip 文件。首先,确定哪些任务生成这些 jar。在本例中,输入任务是主 Compile 范围内的 packageBinpackageSrcpackageDoc。这些任务的每个结果都是它们生成的 jar 的 File。我们的 zip 文件任务通过映射这些打包任务并将它们的输出包含在 zip 文件中来定义。作为最佳实践,我们随后返回此 zip 的 File,以便其他任务可以映射到 zip 任务上。

zip := {
    val bin: File = (Compile / packageBin).value
    val src: File = (Compile / packageSrc).value
    val doc: File = (Compile / packageDoc).value
    val out: File = zipPath.value
    val inputs: Seq[(File,String)] = Seq(bin, src, doc) x Path.flat
    IO.zip(inputs, out)
    out
}

val inputs 行定义了输入文件如何映射到 zip 中的路径。有关详细信息,请参见映射文件。显式类型不是必需的,但包含在内是为了清晰起见。

zipPath 输入将是一个自定义任务,用于定义 zip 文件的位置。例如

zipPath := target.value / "out.zip"