本页描述了设置核心引擎。这对于在 sbt 之外使用它可能很有用。它可能也有助于理解 sbt 的内部工作原理。
文档包含两个部分。第一部分展示了在设置引擎之上构建的示例设置系统。第二部分评论了 sbt 的设置系统是如何在设置引擎之上构建的。这可能有助于阐明核心设置引擎提供的功能以及构建类似于 sbt 设置系统的东西所需的步骤。
要运行此示例,请先创建一个新的项目,其 build.sbt 文件如下所示
libraryDependencies += "org.scala-sbt" %% "collections" % sbtVersion.value
resolvers += sbtResolver.value
然后,将以下示例放入源文件 SettingsExample.scala
和 SettingsUsage.scala
中。最后,运行 sbt 并使用 console
进入 REPL。要查看下面描述的输出,请输入 SettingsUsage
。
示例的第一部分定义了自定义设置系统。它主要包含三个部分
Scope
类型。Scope
(加上 AttributeKey
)转换为 String
。Scope
序列。还有一个第四部分,但目前其用法可能特定于 sbt。此示例为此部分使用了微不足道的实现。
SettingsExample.scala
:
import sbt._
/** Define our settings system */
// A basic scope indexed by an integer.
final case class Scope(index: Int)
// Extend the Init trait.
// (It is done this way because the Scope type parameter is used everywhere in Init.
// Lots of type constructors would become binary, which as you may know requires lots of type lambdas
// when you want a type function with only one parameter.
// That would be a general pain.)
object SettingsExample extends Init[Scope]
{
// Provides a way of showing a Scope+AttributeKey[_]
val showFullKey: Show[ScopedKey[_]] = new Show[ScopedKey[_]] {
def apply(key: ScopedKey[_]) = key.scope.index + "/" + key.key.label
}
// A sample delegation function that delegates to a Scope with a lower index.
val delegates: Scope => Seq[Scope] = { case s @ Scope(index) =>
s +: (if(index <= 0) Nil else delegates(Scope(index-1)) )
}
// Not using this feature in this example.
val scopeLocal: ScopeLocal = _ => Nil
// These three functions + a scope (here, Scope) are sufficient for defining our settings system.
}
这部分展示了如何使用我们刚刚定义的系统。最终结果是一个 Settings[Scope]
值。此类型基本上是一个映射 Scope -> AttributeKey[T] -> Option[T]
。有关详细信息,请参阅 Settings API 文档。
SettingsUsage.scala
:
/** Usage Example **/
import sbt._
import SettingsExample._
import Types._
object SettingsUsage {
// Define some keys
val a = AttributeKey[Int]("a")
val b = AttributeKey[Int]("b")
// Scope these keys
val a3 = ScopedKey(Scope(3), a)
val a4 = ScopedKey(Scope(4), a)
val a5 = ScopedKey(Scope(5), a)
val b4 = ScopedKey(Scope(4), b)
// Define some settings
val mySettings: Seq[Setting[_]] = Seq(
setting( a3, value( 3 ) ),
setting( b4, map(a4)(_ * 3)),
update(a5)(_ + 1)
)
// "compiles" and applies the settings.
// This can be split into multiple steps to access intermediate results if desired.
// The 'inspect' command operates on the output of 'compile', for example.
val applied: Settings[Scope] = make(mySettings)(delegates, scopeLocal, showFullKey)
// Show results.
for(i <- 0 to 5; k <- Seq(a, b)) {
println( k.label + i + " = " + applied.get( Scope(i), k) )
}
}
运行后将产生以下输出
a0 = None
b0 = None
a1 = None
b1 = None
a2 = None
b2 = None
a3 = Some(3)
b3 = None
a4 = Some(3)
b4 = Some(9)
a5 = Some(4)
b5 = Some(9)
None
结果,我们从未定义该值,也没有任何值可供委托。a3
,我们明确将其定义为 3。a4
未定义,因此它根据我们的 delegates
函数委托给 a3
。b4
获取 a4
的值(它委托给 a3
,因此为 3),并将结果乘以 3。a5
被定义为 a5
的前一个值 + 1,由于未定义 a5
的前一个值,因此它委托给 a4
,最终结果为 3+1=4。b5
没有明确定义,因此它委托给 b4
,因此也等于 9。sbt 定义了一个比这里显示的更复杂的范围,用于构建中设置的标准用法。此范围包含四个组件:项目轴、配置轴、任务轴和额外轴。每个组件可以是 Zero(没有特定值)、This(当前上下文)或 Select(包含特定值)。sbt 将 This_
解析为 Zero 或 Select,具体取决于上下文。
例如,在一个项目中,一个 This 项目轴将成为一个 Select,引用定义的项目。所有其他轴,其值是 This,都将转换为 Zero。诸如 inConfig
和 inTask
之类的函数将 This
转换为 Select,以获得特定值。例如,inConfig(Compile)(someSettings)
将 someSettings 中所有设置的配置轴转换为 Select(Compile)
,如果轴值是 This。
因此,从示例和 sbt 的范围可以看出,核心设置引擎不会对范围的结构强加太多限制。它只需要一个 delegates
函数 Scope => Seq[Scope]
和一个 display
函数。您可以选择适合您情况的范围类型。
app
、value
、update
和相关方法是构造设置的核心方法。此示例显然看起来与 sbt 的接口大不相同,因为这些方法通常不会直接使用,而是被包装在更高级别的抽象中。
使用核心设置引擎,您可以使用 HList
来访问其他设置。在 sbt 的更高级别系统中,存在围绕 HList
的包装器,用于 TupleN
和 FunctionN
,其中 N = 1-9(除了 Tuple1
实际上没有使用)。当使用任意元数时,在尽可能高的级别上创建这些包装器非常有用。这是因为,一旦定义了包装器,就必须为每个 N 复制代码。通过在顶层创建包装器,只需要复制一个级别。
此外,sbt 将其任务引擎统一集成到设置系统中。底层设置引擎没有任务的概念。这就是 sbt 使用 SettingKey
类型和 TaskKey
类型的原因。底层 TaskKey[T]
上的方法基本上被转换为对底层 SettingKey[Task[T]]
的操作(它们都包装了底层 AttributeKey
)。
例如,对于一个 SettingKey a,a := 3
大致会转换为 setting(a, value(3))
。对于一个 TaskKey a,它大致会转换为 setting(a, value( task { 3 } ) )
。有关详细信息,请参阅 main/Structure.scala。
sbt 还提供了一种方法,可以在文件中定义这些设置(build.sbt
和 Build.scala
)。这对于 build.sbt
是使用基本的解析完成的,然后将生成的代码块传递给 compile/Eval.scala
。对于所有定义,sbt 管理类路径和重新编译过程以获取设置。它还提供了一种方法,允许用户定义项目、任务和配置委托,最终由 delegates
函数使用。