本页将引导您开始创建自己的设置和任务。
要理解本页内容,请确保您已阅读入门指南中的前面几页,尤其是 build.sbt 和 任务图。
Keys 充满了说明如何定义键的示例。大多数键都在 Defaults 中实现。
键具有以下三种类型之一。SettingKey
和 TaskKey
在 .sbt 构建定义 中有描述。在 输入任务 页面上阅读有关 InputKey
的内容。
来自 Keys 的一些示例
val scalaVersion = settingKey[String]("The version of Scala used for building.")
val clean = taskKey[Unit]("Deletes files produced by the build, such as generated sources, compiled classes, and task caches.")
键构造函数有两个字符串参数:键的名称("scalaVersion"
)和文档字符串("The version of scala used for building."
)。
请记住,从 .sbt 构建定义 中,SettingKey[T]
中的类型参数 T
表示设置的值的类型。TaskKey[T]
中的 T
表示任务结果的类型。还请记住,从 .sbt 构建定义 中,设置在项目重新加载之前具有固定值,而任务会在每次“任务执行”(每次有人在 sbt 交互式提示符或批处理模式下键入命令)时重新计算。
键可以在 .sbt 文件、.scala 文件 或 自动插件 中定义。在启用的自动插件的 autoImport
对象下找到的任何 val
都将自动导入到您的 .sbt
文件中。
定义完任务的键后,您需要使用任务定义来完成它。您可能正在定义自己的任务,或者您可能计划重新定义现有任务。无论哪种方式看起来都一样;使用 :=
将一些代码与任务键关联起来
val sampleStringTask = taskKey[String]("A sample string task.")
val sampleIntTask = taskKey[Int]("A sample int task.")
ThisBuild / organization := "com.example"
ThisBuild / version := "0.1.0-SNAPSHOT"
ThisBuild / scalaVersion := "2.12.18"
lazy val library = (project in file("library"))
.settings(
sampleStringTask := System.getProperty("user.home"),
sampleIntTask := {
val sum = 1 + 2
println("sum: " + sum)
sum
}
)
如果任务有依赖项,您将使用 value
来引用它们的值,如 任务图 中所述。
实现任务最困难的部分通常不是 sbt 特定的;任务只是 Scala 代码。困难的部分可能是编写任务的“主体”,它执行您想要执行的任何操作。例如,您可能正在尝试格式化 HTML,在这种情况下,您可能想要使用 HTML 库(您将 将库依赖项添加到您的构建定义中 并编写基于 HTML 库的代码,也许)。
sbt 有一些实用程序库和便捷函数,特别是在 IO 中,您可以经常使用便捷的 API 来操作文件和目录。
当使用 value
从自定义任务依赖其他任务时,需要注意一个重要的细节是任务的执行语义。通过执行语义,我们的意思是这些任务到底 *何时* 评估。
例如,如果我们以 sampleIntTask
为例,任务主体中的每一行都应严格地按顺序评估。即顺序语义
sampleIntTask := {
val sum = 1 + 2 // first
println("sum: " + sum) // second
sum // third
}
实际上,JVM 可能会将 sum
内联到 3
,但任务的可观察 *效果* 将保持与按顺序执行每一行相同。
现在假设我们定义了另外两个自定义任务 startServer
和 stopServer
,并修改 sampleIntTask
如下
val startServer = taskKey[Unit]("start server")
val stopServer = taskKey[Unit]("stop server")
val sampleIntTask = taskKey[Int]("A sample int task.")
val sampleStringTask = taskKey[String]("A sample string task.")
ThisBuild / organization := "com.example"
ThisBuild / version := "0.1.0-SNAPSHOT"
ThisBuild / scalaVersion := "2.12.18"
lazy val library = (project in file("library"))
.settings(
startServer := {
println("starting...")
Thread.sleep(500)
},
stopServer := {
println("stopping...")
Thread.sleep(500)
},
sampleIntTask := {
startServer.value
val sum = 1 + 2
println("sum: " + sum)
stopServer.value // THIS WON'T WORK
sum
},
sampleStringTask := {
startServer.value
val s = sampleIntTask.value.toString
println("s: " + s)
s
}
)
从 sbt 交互式提示符运行 sampleIntTask
会导致以下结果
> sampleIntTask
stopping...
starting...
sum: 3
[success] Total time: 1 s, completed Dec 22, 2014 5:00:00 PM
为了回顾发生了什么,让我们看一下 sampleIntTask
的图形表示
与普通的 Scala 方法调用不同,在任务上调用 value
方法不会被严格评估。相反,它们只是充当占位符,表示 sampleIntTask
依赖于 startServer
和 stopServer
任务。当您调用 sampleIntTask
时,sbt 的任务引擎将
sampleIntTask
之前评估任务依赖项(部分排序)为了演示最后一点,我们可以从 sbt 交互式提示符运行 sampleStringTask
。
> sampleStringTask
stopping...
starting...
sum: 3
s: 3
[success] Total time: 1 s, completed Dec 22, 2014 5:30:00 PM
因为 sampleStringTask
依赖于 startServer
和 sampleIntTask
任务,而 sampleIntTask
也依赖于 startServer
任务,所以它出现了两次作为任务依赖项。如果这是一个普通的 Scala 方法调用,它将被评估两次,但由于 value
只是表示任务依赖项,因此它将被评估一次。以下是 sampleStringTask
评估的图形表示
如果我们没有对任务依赖项进行重复数据删除,那么当调用 test
任务时,我们将最终多次编译测试源代码,因为 Test / compile
多次出现在 Test / test
的任务依赖项中。
应该如何实现 stopServer
任务?清理任务的概念不适合任务的执行模型,因为任务是关于跟踪依赖项的。最后一次操作应该成为依赖于其他中间任务的任务。例如,stopServer
应该依赖于 sampleStringTask
,此时 stopServer
应该成为 sampleStringTask
。
lazy val library = (project in file("library"))
.settings(
startServer := {
println("starting...")
Thread.sleep(500)
},
sampleIntTask := {
startServer.value
val sum = 1 + 2
println("sum: " + sum)
sum
},
sampleStringTask := {
startServer.value
val s = sampleIntTask.value.toString
println("s: " + s)
s
},
sampleStringTask := {
val old = sampleStringTask.value
println("stopping...")
Thread.sleep(500)
old
}
)
为了证明它有效,请从交互式提示符运行 sampleStringTask
> sampleStringTask
starting...
sum: 3
s: 3
stopping...
[success] Total time: 1 s, completed Dec 22, 2014 6:00:00 PM
另一种确保某件事在另一件事之后发生的方法是使用 Scala。例如,在 project/ServerUtil.scala
中实现一个简单的函数,您可以编写
sampleIntTask := {
ServerUtil.startServer
try {
val sum = 1 + 2
println("sum: " + sum)
} finally {
ServerUtil.stopServer
}
sum
}
由于普通的函数调用遵循顺序语义,因此所有操作按顺序发生。没有重复数据删除,因此您必须注意这一点。
如果您发现有许多自定义代码,请考虑将其移至插件,以便在多个构建中重复使用。
本页只是一个小小的尝试;在 任务 页面上还有更多关于自定义任务的内容。