在SBT中组合taskDyn和Tags

Nig*_*ape 2 scala sbt

我有一个SBT任务,它使用taskDyn从配置文本文件动态创建几个CPU密集型计算的任务.默认情况下,计算任务的计算并行运行,这很好,但现在我需要限制并行运行的数量(否则本机库OpenBLAS崩溃).我尝试使用Tag.CPU来强制执行此限制(此处为完整示例):

import sbt._
import Keys._
import Def.Initialize

// ...

lazy val runAllValidations = taskKey[Seq[Unit]]("Runs all standard validations")

lazy val validations = settingKey[Seq[String]]("All standard validations")

def validationTaskFor(arguments: String): Initialize[Task[Unit]] =
  (runMain in Compile).toTask(s" com.test.foo.validation.RunValidation $arguments") tag(Tags.CPU)

def validationTasksFor(arguments: Seq[String]): Initialize[Task[Seq[Unit]]] = Def.taskDyn {
  arguments.map(validationTaskFor).joinWith(_.join)
}

validations := {
  val fromFile = IO.read(file("validation_configs.txt"))
  fromFile.split("\n").map(_.trim).toList
}

runAllValidations := Def.taskDyn { validationTasksFor(validations.value) }.value

concurrentRestrictions in Global := Seq(
  Tags.limit(Tags.CPU, 2)
)
Run Code Online (Sandbox Code Playgroud)

不幸的是,我无法使taskDyn和Tags的组合工作,所有计算都立即启动:

Started with foo
Started with bla
Started with hoorray
Started with yeeha
Started with yeah
Finished with foo
Finished with hoorray
Finished with bla
Finished with yeeha
Finished with yeah
Run Code Online (Sandbox Code Playgroud)

知道这种组合是不起作用还是我把它弄错了?

谢谢!

jsu*_*eth 5

您是Scala中Any => Unit转换的受害者.你正在做的是在一个任务中计算一些超级复杂的东西,然后通过将它转换为()来忽略它,并让scala忽略它.

这里有一些代码使用较低级别的InitializeAPI来折叠任务(我希望将来更直接地公开)

我的build.sbt:

def dummyTaskGen(name: String): Def.Initialize[Task[Unit]] = Def.task {
   System.err.println(s"Started  ${name}")
   Thread.sleep(1000L*6)
   System.err.println(s"Finished ${name}")
}


lazy val validations = taskKey[Unit]("Run everything, one at a time.")

lazy val names = Seq("foo", "bar", "baz", "biz", "buzz", "bunk")

lazy val allRuns: Def.Initialize[Task[Unit]] = Def.settingDyn {
  val zero: Def.Initialize[Seq[Task[Unit]]] = Def.setting {  Seq(task(())) }
  names.map(dummyTaskGen).foldLeft(zero) { (acc, current) =>
     acc.zipWith(current) {  case (taskSeq, task) =>
       taskSeq :+ task.tag(Tags.CPU)
     }
  } apply { tasks: Seq[Task[Unit]] =>
    tasks.join map { seq => () /* Ignore the sequence of unit returned */ }
  }
}

validations := allRuns.value

concurrentRestrictions in Global ++= Seq(
  Tags.limit(Tags.CPU, 2)
)
Run Code Online (Sandbox Code Playgroud)

并在命令行上:

> validations
Started  bar
Started  buzz
Finished buzz
Finished bar
Started  biz
Started  bunk
Finished biz
Finished bunk
Started  baz
Started  foo
Finished baz
Finished foo
[success] Total time: 18 s, completed Jul 7, 2014 2:11:43 PM
Run Code Online (Sandbox Code Playgroud)

让我们深入研究我们在这里使用的方法:

首先,关键是你将zipWith一起完成任务的整个初始化.

例如,在您的项目中,您有一种机制来生成一个名为的标记任务validationTaskFor.返回a Def.Intiailize[Task[Unit]],任务被适当地调整.

所以,现在你有了一系列字符串,你可以把它变成一系列初始化.从这里开始,我们需要合并所有Initialization外层以获得层中多汁的优点Task[_].

请参阅:http: //www.scala-sbt.org/0.13.5/api/index.html#sbt.Init $ Initialize for initialize.

注: 大多数情况下,初始化时宏背后隐藏的(像Def.task, Def.setting,Def.inputTask,:=和朋友).但是,在使用动态任务生成时,暴露的API不够充分,并且您遇到了scala的隐式推理+我们的宏导致编译的内容之一,这些内容本质上是荒谬的(即丢弃所有任务[_]实例生成和返回()没有充分理由).

现在,关于功能的肉:

  1. Def.settingDyn需要一个Initialize[T].在我们的例子中,TTask[Unit].所以我们知道我们需要将下面的表达式转换为a Initialize[Task[Unit],所以让我们allRuns适当地键入值以获得更好的编译器错误消息.

    def allRuns: Def.Initialize[Task[Unit]] = Def.settingDyn { ... }

  2. Def.Initialize是一个应用程序,我们将有一系列这些.这意味着折叠,所以我们应该创建zero折叠.在这种情况下,当我们将所有任务合并在一起时,我们将返回一个Initialize[Seq[Task[Unit]]所有任务都一个Initialize容器的地方,我们可以在那里与它们进行交互.(这类似于确保在我们尝试使用它们之前从State正确初始化/构造所有任务).

    val zero: Def.Initialize[Seq[Task[Unit]]] = Def.setting { Seq(task( () )) }

  3. 现在,我们将我们Seq[String]转换为Seq[Initialize[Task[Unit]] names.map(dummyTaskGen)`

  4. 我们折叠这个序列:

    names.map(dummyTaskGen).foldLeft(zero) { (acc, current) => ... }

  5. 我们定义了如何Initialize[Seq[Task[Unit]]使用`Initialize [Task [Unit]] 加入一个,即我们将新任务添加到现有的任务列表中:

    acc.zipWith(current) { case (taskSeq, task) => taskSeq :+ task.tag(Tags.CPU) }

  6. 我们现在有一个Initialize[Seq[Task[Unit]]加入所有这些:

    tasks.join /* Task[Seq[Unit]] */

  7. 我们忽略Seq [Unit]结果,将其转换为Unit

    tasks.join.map { ignore => () }

你应该做三件事:

  1. 打开一个关于公开Def.taskDef.taskDyn宏中的机制的故障,以直接向任务添加标记,从而删除要求您进入此级别API的两个事项之一.
  2. 打开一个关于直接公开加入任务机制的故障单.例如, Initialize[Seq[Initialize[Task[T]]]] 应该可以在没有喧闹的情况下加入,因为每次有人想要这样做都是相同的脏代码.
  3. 打开一张关于自动任务宏和Scala的隐式Any => Unit的不良交互的故障单,导致非常严重的错误.