【问题标题】:Combining taskDyn and Tags in SBT在 SBT 中结合 taskDyn 和标签
【发布时间】:2014-07-08 02:35:54
【问题描述】:

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

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)
)

不幸的是,我无法让 taskDyn 和标签的组合工作,所有计算都立即开始:

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

这个组合是不是已知无效,还是我拿错了?

谢谢!

【问题讨论】:

    标签: scala sbt


    【解决方案1】:

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

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

    我的 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)
    )
    

    在命令行上:

    > 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
    

    让我们深入了解我们在这里使用的方法:

    首先,关键是您将zipWith-ing 一起完成任务的整个初始化。

    例如,在您的项目中,您有一种机制可以生成名为validationTaskFor 的标记任务。这将返回一个Def.Intiailize[Task[Unit]],其中任务被适当标记。

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

    initialize 的 API 见:http://www.scala-sbt.org/0.13.5/api/index.html#sbt.Init$Initialize。

    注意: 大多数情况下,初始化隐藏在宏后面(如Def.taskDef.settingDef.inputTask:= 和朋友们)。然而,当 使用动态任务生成,暴露的 API 并不完全 足够了,并且您遇到了 scala 隐含的那些领域之一 推理 + 我们的宏导致编译的东西在 性质(即丢弃所有生成的 Task[_] 实例和 无缘无故返回 ()。

    现在,进入函数的核心:

    1. Def.settingDyn 需要Initialize[T]。在我们的例子中,TTask[Unit]。所以我们知道我们需要将下面的表达式转换为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[Task[Unit]] 加入Initialize[Seq[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]]]] 应该可以在没有 hoopla 的情况下加入,因为每次有人想要这样做时,它都是相同的脏代码。
    3. 开一张关于自动任务宏和 Scala 的隐式 Any => 单元的不良交互导致非常严重的错误的票。

    【讨论】:

    • 我尝试根据我的用例调整您的示例,但是,当我从 runMain 创建任务时,它不再起作用,即限制被忽略并且任务再次同时开始:@ 987654322@
    猜你喜欢
    • 1970-01-01
    • 2013-05-02
    • 1970-01-01
    • 1970-01-01
    • 2011-06-25
    • 2012-03-06
    • 2014-08-03
    • 2012-03-31
    相关资源
    最近更新 更多