如何使用<< =与定义为:=的那个定义的sbt任务不同于引用另一个设置的.value?

时间:2013-12-17 19:07:02

标签: scala sbt

我有以下使用sbt-assembly的build.sbt示例。 (我的assembly.sbt和project / assembly.sbt的设置如readme中所述。)

import AssemblyKeys._

organization := "com.example"

name := "hello-sbt"

version := "1.0"

scalaVersion := "2.10.3"

val hello = taskKey[Unit]("Prints hello")

hello := println(s"hello, ${assembly.value.getName}")

val hey = taskKey[Unit]("Prints hey")

hey <<= assembly map { (asm) => println(s"hey, ${asm.getName}") }

//val hi = taskKey[Unit]("Prints hi")

//hi <<= assembly { (asm) => println(s"hi, $asm") }

hellohey在功能上都是等价的,当我从sbt运行任一任务时,它们首先运行assembly并打印具有相同文件名的消息。 两者之间是否存在有意义的差异?(似乎hello的定义“略显神奇”,因为对汇编的依赖只是隐含在那里,而不是明确的。)

最后,我想了解为什么hey需要map来电。显然它会导致另一个对象传递到asm,但我不太确定如何在hi的定义中修复此类型错误:

sbt-hello/build.sbt:21: error: type mismatch;
 found   : Unit
 required: sbt.Task[Unit]
hi <<= assembly { (asm) => println(s"hi, $asm") }
                                  ^
[error] Type error in expression

看起来assembly这里是[sbt.TaskKey[java.io.File]][2],但我没有看到在那里定义map方法,所以我无法弄清楚hey类型中发生了什么{1}}以上。

1 个答案:

答案 0 :(得分:19)

sbt 0.12语法vs sbt 0.13语法

  

两者之间是否存在有意义的差异?

通过有意义的差异,如果你的意思是语义差异与编译代码行为的可观察差异,它们是相同的。

如果你的意思是代码中的任何预期差异,那就是sbt 0.12语法sbt 0.13语法之间的风格差异。从概念上讲,我认为sbt 0.13语法使得学习和编码更容易,因为您直接处理T而不是Initialize[T]。使用宏,sbt 0.13将x.value扩展为0.12当量的sbt。

&lt;&lt; = 的解剖结构
  

我试图理解为什么他们需要地图电话。

这实际上是差异宏之一,现在能够自动处理。 要理解sbt 0.12样式中需要map的原因,您需要了解sbt DSL表达式的类型,即Setting[_]。正如Getting Started guide所说:

  

相反,构建定义会创建一个包含类型Setting[T]的巨大对象列表,其中T是地图中值的类型。 Setting描述了对地图的转换,例如添加新的键值对或附加到现有值。

对于任务,DSL表达式的类型为Setting[Task[T]]。要将设置密钥设置为Setting[T],或将任务密钥设置为Setting[Task[T]],请使用相应密钥上定义的<<=方法。这在Structure.scala中实现(sbt 0.12代码库具有更简单的<<=实现,因此我将使用它作为参考。):

sealed trait SettingKey[T] extends ScopedTaskable[T] with KeyedInitialize[T] with Scoped.ScopingSetting[SettingKey[T]] with Scoped.DefinableSetting[T] with Scoped.ListSetting[T, Id] { ... }

sealed trait TaskKey[T] extends ScopedTaskable[T] with KeyedInitialize[Task[T]] with Scoped.ScopingSetting[TaskKey[T]] with Scoped.ListSetting[T, Task] with Scoped.DefinableTask[T] { ... }

object Scoped {
    sealed trait DefinableSetting[T] {
        final def <<= (app: Initialize[T]): Setting[T]  =  setting(scopedKey, app)
        ...
    }

    sealed trait DefinableTask[T] { self: TaskKey[T] =>
        def <<= (app: Initialize[Task[T]]): Setting[Task[T]]  =  Project.setting(scopedKey, app)
        ...
    }
}

请注意app参数的类型。设置密钥<<=需要Initialize[T],而任务密钥<<=需要Initialize[Task[T]]。换句话说,根据<<=表达式的lhs类型,rhs的类型会发生变化。这要求sbt 0.12用户了解密钥中的设置/任务差异。

假设您在lhs上有一个类似description的设置键,并假设您想依赖name设置并创建说明。要创建设置依赖关系表达式,请使用apply

description <<= name { n => n + " is good." }

apply单个密钥在Settings.scala中实现:

sealed trait Keyed[S, T] extends Initialize[T]
{
    def transform: S => T
    final def apply[Z](g: T => Z): Initialize[Z] = new GetValue(scopedKey, g compose transform)
}
trait KeyedInitialize[T] extends Keyed[T, T] {
    final val transform = idFun[T]
}

接下来,假设您要为description创建设置,而不是jarName in assembly。这是一个任务键,因此<<=的rhs需要Initialize[Task[T]],所以apply不好。这是map的来源:

jarName in assembly <<= name map { n => n + ".jar" }

这也在Structure.scala中实现:

final class RichInitialize[S](init: Initialize[S]) {
    def map[T](f: S => T): Initialize[Task[T]] = init(s => mktask(f(s)) )
}

由于设置键扩展KeyedInitialize[T]Initialize[T],并且因为Initialize[T]RichInitialize[T]之间存在隐式转换,因此上述name可用。这是定义map的一种奇怪方式,因为地图通常会保留结构。

如果您看到类似的任务键浓缩类,则可能更有意义:

final class RichInitializeTask[S](i: Initialize[Task[S]]) extends RichInitTaskBase[S, Task] {...}

sealed abstract class RichInitTaskBase[S, R[_]] {
    def map[T](f: S => T): Initialize[R[T]] = mapR(f compose successM)
}

因此,对于任务,map会将S类型的任务映射到T。对于设置,我们可以将其视为:map未在设置上定义,因此它会隐式地将自身转换为任务并对其进行映射。在任何情况下,这都让0.12用户认为:使用apply进行设置,map进行任务。请注意apply随着任务键的扩展Keyed[Task[T], Task[T]]而消失。这应该解释:

sbt-hello/build.sbt:21: error: type mismatch;
 found   : Unit
 required: sbt.Task[Unit]

然后是元组问题。到目前为止,我已经讨论了对单个设置的依赖性。如果您想依赖更多,sbt会隐式地将applymap添加到Tuple2..N来处理它。现在它是expanded to 15,但过去只有Tuple9。从新用户的角度来看,在map设置上调用Tuple9以便生成类似任务Initialize[Task[T]]的想法看起来很陌生。在不改变底层机制的情况下,sbt 0.13为开始提供了更清晰的表面。