项目中的 build.sbt
中发现定义任务时有 task2 <<= task1 map {...}
这样任务依赖的写法, 这个 <<=
方法有点晦涩难懂, 读过 sbt in action
之后才知道这是 sbt 0.12 或之前的做法, sbt 0.13 之后不这么用了, 直接访问下 task1.value 就行, 因此前面可改写为 task2 := {task1.value; ...}
, 这也使得定义任务依赖时与普通任务一致风格了. 新的写法得益于 Scala 2.10 的宏特性, 后面还会讲到.
对于依赖于多个任务的情况, 在 sbt 0.13 前面分别是这样的, 假定有三个 Task(task1, task2, 和 task3)
val task1 = taskKey[Int]("task 1")
val task2 = taskKey[Int]("task 2")
val task3 = taskKey[Int]("task 3")
如果 task3 依赖于 task1 和 task 2
sbt 0.13 前后版本中的写法分别是
1 2 3 4 5 6 |
//sbt 0.12 及更早版本, 当然 sbt 0.13 中也能这样写 //http://www.scala-sbt.org/0.12.2/docs/Detailed-Topics/Tasks.html#dependencies task3 <<= (task1, task2).map((t1, t2) => t1 + t2) //task1, task2 并发执行 //sbt 0.13 后, 推荐这种新写法 task3 := task1.value + task2.value //task1, task2 也是并发执行 |
像任务的 .value
属性其实是一个宏定义, 源码
1 2 |
@compileTimeOnly("`value` can only be used within a task or setting macro, such as :=, +=, ++=, Def.task, or Def.setting.") def value: T = macro InputWrapper.valueMacroImpl[T] |
所谓的 Scala 的宏与 C/C++ 的宏效果很类似, 试想 sbt 在执行 task3 之前会有一个宏替换的操作, 在 task1.value
,task2.value
处执行 task1 和 task2 任务, 并把结果放在此处, 最后才把两结果相加. sbt 只对 task 或 setting 定义中的 task.value
进行宏处理, 如果在自定义的方法中也调用 task.value
你将会收到宏定义上一行一样的编译错误.
为说明宏的先于本身任务先执行, 可以看下面一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
task1 := { println("task1"); 1 } task2 := { println("task2"); 2 } task3 := { if(false) { println("task3") //因为 if 条件是 false, 所以不会打印 task3 task1.value //这个宏处理时执行的, 所以不受控于前面的 if 条件 } else { task2.value } } |
执行后是这样的
➜ test sbt
[info] Set current project to test (in build file:/Users/yanbin/test/)
> show task3
task1
task2
[info] 2
[success] Total time: 0 s, completed Apr 22, 2016 10:54:20 PM
> inspect tree task3
[info] *:task3 = Task[Int]
[info] +-*:task1 = Task[Int]
[info] +-*:task2 = Task[Int]
由上可见, 尽管 task1.value 写在了 if(false) 条件中, 但是它仍然被执行了, 这就是宏处理的功劳, 而其中的 `println("task3") 受控于 false 条件.
sbt 中被依赖的任务是并发执行的, 所以不因为谁写在前面就谁先执行, 看个例子
1 2 3 4 5 6 7 8 9 10 |
task1 := { Thread.sleep(500) //暂停半秒 println("task1"); 1 } task2 := { println("task2"); 2 } task3 := task1.value + task2.value //尽管 task1 写在 task2 之前, 但 task2 的结果先打印出, 可知它们是并发执行的 |
执行 task3
> task3
task2
task1
可见 task1 和 task2 是并发执行的, task2 并不需要等待 task1
那么如何让任务依赖有先后顺序呢? 可以使用 dependsOn()
方法了, 同样针对 <<=
和 :=
可以有两种写法
1 2 3 |
task3 <<= task2.dependsOn(task1) task3 := task2.dependsOn(task1).value //推荐这个新的写法 |
这样 task2 排在 task1 之后执行, dependsOn()
可以串取起来, 并且它的原型是 dependsOn(tasks: AnyInitTask*)
, 所以它可以同时接受多个参数(它们要并发执行了)
如何在内置任务之前做些事情呢? 把原任务呼出使其依赖别的任务, 比如在 run
之前执行某个任务, 同样列出新旧两种写法
1 2 3 |
run <<= (run in Runtime).dependsOn task2 run := (run in Runtime).dependsOn(task2).evaluated //推荐这个新的写法 |
sbt 官方文档 : Modifying an Existing Task.
如何让依赖间条件依赖? 像是动态依赖, 还是接回上面的例子, 想要在 task
执行结果为 100 时才执行 run in Runtime
, 想像中的代码如下:
1 2 3 4 5 |
run := { if(task1.value == 100) { (run in Runtime).evaluated } } |
这里有两个问题: 1) task1 和 (run in Runtime) 是并发执行的, 2) (run in Runtime) 在判断条件之前的宏处理阶段就已经先执行完了. 所以 (run in Runtime) 无论如何都会执行, 我行我素, 完全与 if 条件无关.
针对于此, 我们有一个不甚完美的解决, 还是用 dependsOn()
方法, task1 在某种情况下调用 sys.error("error occurs")
, 那么它后面的所有任务就不再执行了, 回到 sbt 控制后. 片断代码如下:
1 2 3 4 5 6 7 8 9 10 |
task1 := { Thread.sleep(500) println(12); 1 } task1 := { if(parallelExecution.value) sys.error("can't run in parallel mode") else 2 } |
项目中有一个 Scala 文件
1 2 3 |
object Main extends App { println("Hello World.") } |
所以如果 run 正常执行了可以输出 Hello World.
, 现在来看看条件控制执行 run
parallelExecution
默认为 true, 所以第一次执行 task3 时 run 没被执行直接退到 sbt 控制台, 然后把 parallelExecution
设置为 false 后, task3 就能执行 run 任务了.
参见 sbt 官方文档: Handling Failure
sbt 0.13 中好像有一种更精确控制条件执行任务的办法: Dynamic Computations with Def.taskDyn, 试了一下还是可以的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
val stringTask = taskKey[String]("string task") val intTask = taskKey[Int]("int task") val myTask = taskKey[Unit]("my task") stringTask := "dev" intTask := { println("intTask"); 5 } val dynamic = Def.taskDyn { // decide what to evaluate based on the value of `stringTask` if(stringTask.value == "dev") Def.task { 3 } else // create the production task: only evaluated if the value // of the stringTask is not "dev" Def.task { intTask.value + 5 } } myTask := { val num = dynamic.value println(s"Number selected was $num") } |
执行效果:
方向是对了, 只是对于 InputTask run in Runtime
来说在 Def.task
中无处安身, 写成
1 2 3 |
Def.task { (run in Runtime).value } |
这样不会去执行内置的 run
任务, 在 Def.task
又不允许写成 (run in Runtime).evaluated
. 目前还尚不清楚如何把动态任务应用到修改内置的 run
任务.
本文链接 https://yanbin.blog/sbt-task-dependency/, 来自 隔叶黄莺 Yanbin Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。