1. 设置核心

设置核心 

本页描述了设置核心引擎。这对于在 sbt 之外使用它可能很有用。它可能也有助于理解 sbt 的内部工作原理。

文档包含两个部分。第一部分展示了在设置引擎之上构建的示例设置系统。第二部分评论了 sbt 的设置系统是如何在设置引擎之上构建的。这可能有助于阐明核心设置引擎提供的功能以及构建类似于 sbt 设置系统的东西所需的步骤。

示例 

设置 

要运行此示例,请先创建一个新的项目,其 build.sbt 文件如下所示

libraryDependencies += "org.scala-sbt" %% "collections" % sbtVersion.value

resolvers += sbtResolver.value

然后,将以下示例放入源文件 SettingsExample.scalaSettingsUsage.scala 中。最后,运行 sbt 并使用 console 进入 REPL。要查看下面描述的输出,请输入 SettingsUsage

示例设置系统 

示例的第一部分定义了自定义设置系统。它主要包含三个部分

  1. 定义 Scope 类型。
  2. 定义一个函数,将该 Scope(加上 AttributeKey)转换为 String
  3. 定义一个委托函数,定义要查找值的 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 设置讨论 

范围 

sbt 定义了一个比这里显示的更复杂的范围,用于构建中设置的标准用法。此范围包含四个组件:项目轴、配置轴、任务轴和额外轴。每个组件可以是 Zero(没有特定值)、This(当前上下文)或 Select(包含特定值)。sbt 将 This_ 解析为 ZeroSelect,具体取决于上下文。

例如,在一个项目中,一个 This 项目轴将成为一个 Select,引用定义的项目。所有其他轴,其值是 This,都将转换为 Zero。诸如 inConfiginTask 之类的函数将 This 转换为 Select,以获得特定值。例如,inConfig(Compile)(someSettings)someSettings 中所有设置的配置轴转换为 Select(Compile),如果轴值是 This

因此,从示例和 sbt 的范围可以看出,核心设置引擎不会对范围的结构强加太多限制。它只需要一个 delegates 函数 Scope => Seq[Scope] 和一个 display 函数。您可以选择适合您情况的范围类型。

构造设置 

appvalueupdate 和相关方法是构造设置的核心方法。此示例显然看起来与 sbt 的接口大不相同,因为这些方法通常不会直接使用,而是被包装在更高级别的抽象中。

使用核心设置引擎,您可以使用 HList 来访问其他设置。在 sbt 的更高级别系统中,存在围绕 HList 的包装器,用于 TupleNFunctionN,其中 N = 1-9(除了 Tuple1 实际上没有使用)。当使用任意元数时,在尽可能高的级别上创建这些包装器非常有用。这是因为,一旦定义了包装器,就必须为每个 N 复制代码。通过在顶层创建包装器,只需要复制一个级别。

此外,sbt 将其任务引擎统一集成到设置系统中。底层设置引擎没有任务的概念。这就是 sbt 使用 SettingKey 类型和 TaskKey 类型的原因。底层 TaskKey[T] 上的方法基本上被转换为对底层 SettingKey[Task[T]] 的操作(它们都包装了底层 AttributeKey)。

例如,对于一个 SettingKey aa := 3 大致会转换为 setting(a, value(3))。对于一个 TaskKey a,它大致会转换为 setting(a, value( task { 3 } ) )。有关详细信息,请参阅 main/Structure.scala

设置定义 

sbt 还提供了一种方法,可以在文件中定义这些设置(build.sbtBuild.scala)。这对于 build.sbt 是使用基本的解析完成的,然后将生成的代码块传递给 compile/Eval.scala。对于所有定义,sbt 管理类路径和重新编译过程以获取设置。它还提供了一种方法,允许用户定义项目、任务和配置委托,最终由 delegates 函数使用。