本页介绍了任务和设置系统的动机。您应该已经了解如何使用任务和设置,这些在入门指南和任务页面中进行了描述。
任务系统的一个重要方面是将构建中的两个常见相关步骤结合起来
早期版本的 sbt 使用以下方法分别配置这些步骤
为了了解将它们结合起来为什么有利,请将这种情况与在 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)
这个例子在糟糕程度上有些夸张,但我认为它与我们两个步骤的任务定义几乎相同。这很糟糕的具体原因包括
makeFoo()
。foo
可以被其他代码更改。例如,可能存在 def makeFoo2()
。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
范围内的 packageBin
、packageSrc
和 packageDoc
。这些任务的每个结果都是它们生成的 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"