【问题标题】:Defining sbt task that invokes method from project code?定义从项目代码调用方法的 sbt 任务?
【发布时间】:2014-06-18 01:36:12
【问题描述】:

我正在使用 SBT 构建一个 scala 项目。我想定义一个非常简单的任务,当我在 sbt 中输入generate 时:

sbt> generate

它将调用我的my.App.main(..) 方法来生成一些东西。

myproject/src/main/scala/my中有一个App.scala文件,简化代码如下:

object App {
   def main(args: Array[String]) {
       val source = readContentOfFile("mysource.txt")
       val result = convert(source)
       writeToFile(result, "mytarget.txt");
   }
   // ignore some methods here
}

我尝试将以下代码添加到myproject/build.sbt:

lazy val generate = taskKey[Unit]("Generate my file")

generate := {
  my.App.main(Array())
}

但是由于找不到my.App而无法编译。

然后我尝试将其添加到myproject/project/build.scala:

import sbt._
import my._

object HelloBuild extends Build {

  lazy val generate = taskKey[Unit]("Generate my file")

  generate := {
    App.main(Array())
  }

}

但是还是无法编译,找不到包my。

如何在 SBT 中定义这样的任务?

【问题讨论】:

    标签: scala sbt


    【解决方案1】:

    @SethTisue 提出的解决方案将起作用。另一种方法是使其成为其他项目可以使用的单独项目。为此,您需要将您的 App 对象放入一个单独的项目中,并将其作为依赖项包含在 project/project 中,或者将其打包为一个包含此任务定义的 sbt 插件。

    有关如何创建打包为插件的库的示例,请查看snmp4s。 gen 目录包含生成一些代码的代码(类似于您的 App 代码),sbt 目录包含 gen 的 sbt 插件包装器。

    【讨论】:

      【解决方案2】:

      以.sbt 格式,执行:

      lazy val generate = taskKey[Unit]("Generate my file")
      
      fullRunTask(generate, Compile, "my.App")
      

      这在http://www.scala-sbt.org/0.13.2/docs/faq.html 中有记录,“除了运行之外,我如何创建自定义运行任务?”

      另一种方法是:

      lazy val generate = taskKey[Unit]("Generate my file")
      
      generate := (runMain in Compile).toTask(" my.App").value
      

      在简单的情况下可以正常工作,但不能自定义。

      更新:Jacek 建议改用 resourceGenerators 或 sourceGenerators 是很好的,如果它适合您的用例 - 从您的描述中无法判断它是否适合。

      【讨论】:

      • 在.scala 格式中,您可以将lazy val 部分放在任何位置,而generate := ... 部分是一个普通设置,您可以将它放在.scala 文件的相同部分中其余的设置。
      • 为什么我的...前面有一个空格不应该是"my.App"而不是" my.App"????
      • 我忘了这个空间是干什么用的。但是scala-sbt.org/0.13/docs/Input-Tasks.html 中的toTask 例子都有
      【解决方案3】:

      其他答案非常适合这个问题,但我认为 OP 也可能从我的问题中受益 :)

      OP 询问了 “我想定义一个非常简单的任务,当我在 sbt 中输入 generate 时,将调用我的 my.App.main(..) 方法来生成一些东西。” 这最终可能会使构建。

      Sbt 已经提供了一种在构建时生成文件的方法 - sourceGenerators 和 resourceGenerators - 在阅读了这个问题之后,我似乎没有注意到需要为此定义一个单独的任务。

      在Generating files(见the future version of the document in the commit)你可以阅读:

      sbt 为添加源或资源生成提供标准钩子 任务。

      根据知识可以想到以下解决方案:

      sourceGenerators in Compile += Def.task {
        my.App.main(Array()) // it's not going to work without one change, though
        Seq[File]()          // a workaround before the above change is in effect
      }.taskValue
      

      要完成这项工作,您应该返回一个包含生成文件的Seq[File](而不是空的Seq[File]())。

      代码工作的主要变化是将my.App 类移动到project 文件夹。然后它成为构建定义的一部分。它还反映了类的作用,因为它实际上是构建的一部分,而不是它的产物。当相同的代码是构建和工件本身的一部分时,您不会将不同的关注点分开。如果 my.App 类参与构建,它应该属于它 - 因此移动到 project 文件夹。

      项目的布局如下:

      $ tree
      .
      ├── build.sbt
      └── project
          ├── App.scala
          └── build.properties
      

      关注点分离(又名@joescii in da haus)

      @joescii 的答案中有一点(我在答案中进行了扩展)-“使其成为其他项目可以使用的单独项目。为此,您需要将您的 App 对象放入一个单独的项目并将其作为依赖项包含在project/project"中,即

      假设您有一个单独的项目build-utils,App.scala 在src/main/scala 下。这是一个只有 Scala 代码的常规 sbt 配置。

      jacek:~/sandbox/so/generate-project-code
      $ tree build-utils/
      build-utils/
      └── src
          └── main
              └── scala
                  └── App.scala
      

      您可以将其作为常规 Scala 应用程序进行测试,而不会弄乱 sbt。不需要额外的设置(并且让您从有时可能有益的 sbt 中解放出来 - 较少的设置总是有帮助的)。

      在另一个项目 - project-code - 使用 App.scala 应该是构建的基础,build.sbt 如下:

      项目代码/build.sbt

      lazy val generate = taskKey[Unit]("Generate my file")
      
      generate := {
        my.App.main(Array())
      }
      

      现在最重要的部分 - 项目之间的连接,因此 App 代码对于 project-code 的构建是可见的:

      project-code/project/build.sbt

      lazy val buildUtils = RootProject(
        uri("file:/Users/jacek/sandbox/so/generate-project-code/build-utils")
      )
      
      lazy val plugins = project in file(".") dependsOn buildUtils
      

      使用构建定义,执行generate 会为您提供以下信息:

      jacek:~/sandbox/so/generate-project-code/project-code
      $ sbt
      [info] Loading global plugins from /Users/jacek/.sbt/0.13/plugins
      [info] Loading project definition from /Users/jacek/sandbox/so/generate-project-code/project-code/project
      [info] Updating {file:/Users/jacek/sandbox/so/generate-project-code/build-utils/}build-utils...
      [info] Resolving org.fusesource.jansi#jansi;1.4 ...
      [info] Done updating.
      [info] Updating {file:/Users/jacek/sandbox/so/generate-project-code/project-code/project/}plugins...
      [info] Resolving org.fusesource.jansi#jansi;1.4 ...
      [info] Done updating.
      [info] Compiling 1 Scala source to /Users/jacek/sandbox/so/generate-project-code/build-utils/target/scala-2.10/classes...
      [info] Set current project to project-code (in build file:/Users/jacek/sandbox/so/generate-project-code/project-code/)
      > generate
      Hello from App.main
      [success] Total time: 0 s, completed May 2, 2014 2:54:29 PM
      

      我已将App 的代码更改为:

      > eval "cat ../build-utils/src/main/scala/App.scala"!
      package my
      
      object App {
        def main(args: Array[String]) {
          println("Hello from App.main")
        }
      }
      

      项目结构如下:

      jacek:~/sandbox/so/generate-project-code/project-code
      $ tree
      .
      ├── build.sbt
      └── project
          ├── build.properties
          └── build.sbt
      

      其他变化也就是好东西

      我还建议对源代码生成器的代码进行一些其他更改:

      • 将代码从main 方法移到一个单独的方法,该方法返回生成的文件并让main 调用它。这将使重用 sourceGenerators 中的代码更容易(无需不必要的 Array() 调用它以及显式返回文件)。
      • 对convert 使用filter 或map 函数(以增加功能性)。

      【讨论】:

      • 感谢您的解决方案,如果我想为 SBT 创建一些实用程序,它非常有用。对于这个问题,我不能选择它,因为我必须将我的 App.scala 保留在 src 目录中,以使其他人在没有太多 sbt 知识的情况下看到标准的 scala 项目
      • 嗨!您的解决方案将如何在多模块项目中发挥作用?子模块不能有自己的 project/build.sbt
      猜你喜欢
      • 2018-08-13
      • 2021-03-14
      • 1970-01-01
      • 1970-01-01
      • 2012-07-15
      • 2015-08-11
      • 1970-01-01
      • 2015-09-04
      • 1970-01-01
      相关资源
      最近更新 更多