我有以下使用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") }
hello
和hey
在功能上都是等价的,当我从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}}以上。
答案 0 :(得分:19)
两者之间是否存在有意义的差异?
通过有意义的差异,如果你的意思是语义差异与编译代码行为的可观察差异,它们是相同的。
如果你的意思是代码中的任何预期差异,那就是sbt 0.12语法sbt 0.13语法之间的风格差异。从概念上讲,我认为sbt 0.13语法使得学习和编码更容易,因为您直接处理T
而不是Initialize[T]
。使用宏,sbt 0.13将x.value
扩展为0.12当量的sbt。
我试图理解为什么他们需要地图电话。
这实际上是差异宏之一,现在能够自动处理。
要理解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会隐式地将apply
和map
添加到Tuple2..N
来处理它。现在它是expanded to 15,但过去只有Tuple9
。从新用户的角度来看,在map
设置上调用Tuple9
以便生成类似任务Initialize[Task[T]]
的想法看起来很陌生。在不改变底层机制的情况下,sbt 0.13为开始提供了更清晰的表面。