你如何在 sbt 中编写任务?

Posted

技术标签:

【中文标题】你如何在 sbt 中编写任务?【英文标题】:How do you compose tasks in sbt? 【发布时间】:2015-12-31 21:59:26 【问题描述】:

我正在尝试构建一个自定义任务,以便在我们的持续集成环境中构建我们的项目。这是一组类似的步骤

    向聊天室发送构建开始消息 编译模板 运行 npm 测试 运行 jshint 编译 构建应用程序工件 将工件上传到我们的部署服务器 运行测试 将测试结果发送到我们的部署服务器 将构建结果消息发送到聊天室

请注意,如果任何步骤失败,则应执行第 10 步,并且应根据失败的步骤自定义消息,例如如果第 5 步失败,它应该显示“编译失败”,如果第 8 步失败,它应该显示运行了多少测试以及失败了多少。

为了让事情变得更有趣,这是一个多项目构建,因此在运行测试和发布结果时,它应该运行所有测试并发布聚合结果。

为了让事情变得更有趣,npm 测试、jshint 和 artifact 仅在 webapp 子项目中才真正有意义,其中 javascript 和 Web 服务器所在的位置。

我一直在寻找 sbt-release 以获得灵感,但我对如何获取一项任务产生的价值并在下一项中使用它,如何聚合运行任务并获得产生的价值感到困惑(我在Extracted 中看到了一个运行聚合任务的方法,但它没有给出产生的值),如何在子项目中运行任务并获取产生的值,以及如何进行错误处理。

到目前为止,我已经尝试了两种方法

npmTest.result.value match                                                                     
  case Inc(inc) =>                                                                              
    println(inc)                                                                                
  case Value(res) => Def.taskDyn                                                               
    (executeTests in Test).result.value match                                                  
      case Inc(inc) =>                                                                          
        println(inc)                                                                            
      case Value(res) =>                                                                        
        println(res)                                                                            
                                                                                               
  

上面的问题是executeTests总是运行,即使npmTest失败。并且没有一个printlns 被执行。

npmTest.result.                                                                                    
 flatMap -                                                                                       
   case Inc(inc) =>                                                                               
     task  println(inc)                                                                         
   case Value(res) =>-                                                                            
     (executeTests in Test).result.                                                               
       flatMap                                                                                   
         case Inc(inc) =>                                                                         
           task  println(inc)                                                                   
         case Value(res) =>                                                                       
           task  println(res)                                                                   
                                                                                                 
                 

这个不能编译,因为(executeTasks in Test)... 产生一个Initialize[Task[Unit]] 值并且Task[Unit] 是必需的。

有没有办法用 sbt 完成这个?

【问题讨论】:

只是一个想法:您可能会考虑编写一个 shell 脚本。在 sbt 的框架中是否有严格要求这一切发生? 正如所写,这个问题非常广泛。除了说阅读手册外,很难知道如何回答,如果您遇到特定问题,请在此处打开一个或多个问题并显示您的代码。 我们目前正在使用一些被破解的 shell 脚本。问题在于有趣的信息,例如编译失败了吗?有多少测试运行/失败?不那么容易获得。到目前为止,我尝试做的是类似于 sendBuildStart.flatMap((compile in Compile).result.flatMap case Inc(inc) => sendCompileFailure(inc); case Value(_) => (test in Test).result.flatMap case Inc(inc) => sendTestFailed(inc); case Value(_) => .... 的事情,不幸的是,似乎没有任何进展。 我尝试过对宏进行类似操作,例如 sendBuildStart.result.value match case Inc(inc) => println(); case Value(_) => (compile in Compile).result.value match case Inc(inc) => sendCompileFailure(inc); case Value(_) => (test in Test).result.value match case Inc(inc) => sendTestFailed(inc); case Value(testReport) => ...,但似乎内部任务总是执行,即使更高级别的任务失败。跨度> 如何使用 0.13 dsl 有条件地执行任务:***.com/questions/42915843/… 【参考方案1】:

我找到了一个解决方案,可以让您使用旧的 flatMapmap 来编写任务。

sealed abstract class Step[A] 
  def run: Def.Initialize[Task[Result[A]]]
  def map[B](f: A => B): Step[B]
  def flatMap[B](f: A => Step[B]): Step[B]


object Step 
  val thisProjectRef = settingKey(Keys.thisProjectRef)
  val clean = taskKey(Keys.clean)
  val compile = taskKey(Keys.compile.in(Compile))
  val assembly = taskKey(sbtassembly.AssemblyPlugin.autoImport.assembly)

  private[this] def apply[A](task: Def.Initialize[Task[Result[A]]]): Step[A] =
    new Step[A] 
      val run = task

      def map[B](f: A => B): Step[B] =
        apply[B](Def.taskDyn 
          run.value match 
            case Inc(inc) => Def.task(Inc(inc): Result[B])
            case Value(a) => Def.task(Value(f(a)))
          
        )

      def flatMap[B](f: A => Step[B]): Step[B] =
        apply[B](Def.taskDyn 
          run.value match 
            case Inc(inc) => Def.task(Inc(inc): Result[B])
            case Value(a) => Def.task(f(a).run.value)
          
        )
    

  def task[A](t: Def.Initialize[Task[A]]): Step[A] =
    apply(t.result)

  def taskKey[A](t: TaskKey[A]): Step[A] =
    apply(Def.task(t.result.value))

  def settingKey[A](s: SettingKey[A]): Step[A] =
    apply(Def.task(s.value).result)

然后你可以将你的任务定义为

    rainicornPublish <<= 
      val result = 
        for 
          ass <- Step.assembly
          juri <- uploadAssemblyTask(ass)
          to <- runAllTests
          _ <- finish(ass, juri, to)
         yield (ass, to)

      Def.task(result.run.value match 
        case Inc(inc) => throw new RainicornException(None)
        case Value(v) => v
      )
    

每个任务都会按顺序发生,正如您所期望的那样。

【讨论】:

@pfn 这就是我最终采用的方法。我会更新我的答案。

以上是关于你如何在 sbt 中编写任务?的主要内容,如果未能解决你的问题,请参考以下文章

如何使 SBT 任务依赖于同一个 SBT 项目中定义的模块?

从 Eclipse 运行 SBT 任务

如何将任务依赖项从另一个插件添加到我的 SBT 插件?

如何在 sbt-plugin 中使用 sbt-assembly?

SBT任务将debian文件复制到其他文件夹

scala:如何在SBT设置中定义多个相关任务之间的执行顺序?