sbt 任务间的依赖

项目中的 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 前后版本中的写法分别是

像任务的 .value 属性其实是一个宏定义, 源码

所谓的 Scala 的宏与 C/C++ 的宏效果很类似, 试想 sbt 在执行 task3 之前会有一个宏替换的操作, 在 task1.value ,task2.value 处执行 task1 和 task2 任务, 并把结果放在此处, 最后才把两结果相加. sbt 只对 task 或 setting 定义中的 task.value 进行宏处理, 如果在自定义的方法中也调用 task.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 中被依赖的任务是并发执行的, 所以不因为谁写在前面就谁先执行,  看个例子

执行 task3

> task3
task2
task1

可见 task1 和  task2 是并发执行的, task2 并不需要等待 task1

那么如何让任务依赖有先后顺序呢? 可以使用 dependsOn() 方法了, 同样针对 <<=:= 可以有两种写法

这样 task2 排在 task1 之后执行, dependsOn() 可以串取起来, 并且它的原型是 dependsOn(tasks: AnyInitTask*) , 所以它可以同时接受多个参数(它们要并发执行了)

如何在内置任务之前做些事情呢? 把原任务呼出使其依赖别的任务, 比如在 run 之前执行某个任务, 同样列出新旧两种写法

sbt 官方文档 : Modifying an Existing Task.

如何让依赖间条件依赖? 像是动态依赖, 还是接回上面的例子, 想要在 task 执行结果为 100 时才执行 run in Runtime, 想像中的代码如下:

这里有两个问题: 1) task1 和  (run in Runtime) 是并发执行的, 2) (run in Runtime) 在判断条件之前的宏处理阶段就已经先执行完了. 所以 (run in Runtime) 无论如何都会执行, 我行我素, 完全与 if 条件无关.

针对于此, 我们有一个不甚完美的解决, 还是用 dependsOn() 方法, task1 在某种情况下调用 sys.error("error occurs"), 那么它后面的所有任务就不再执行了, 回到 sbt 控制后. 片断代码如下:

项目中有一个 Scala 文件

所以如果 run 正常执行了可以输出 Hello World., 现在来看看条件控制执行 run

sbt-sys.error

parallelExecution 默认为 true, 所以第一次执行 task3 时 run 没被执行直接退到 sbt 控制台, 然后把 parallelExecution 设置为 false 后, task3 就能执行 run 任务了.

参见 sbt 官方文档: Handling Failure

sbt 0.13 中好像有一种更精确控制条件执行任务的办法: Dynamic Computations with Def.taskDyn, 试了一下还是可以的:

执行效果:

sbt-taskDyn

方向是对了, 只是对于 InputTask run in Runtime 来说在 Def.task 中无处安身, 写成

这样不会去执行内置的 run 任务, 在 Def.task 又不允许写成 (run in Runtime).evaluated. 目前还尚不清楚如何把动态任务应用到修改内置的 run 任务.

本文链接 https://yanbin.blog/sbt-task-dependency/, 来自 隔叶黄莺 Yanbin Blog

[版权声明] Creative Commons License 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。

Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments