定义从项目代码调用方法的sbt任务?

Fre*_*ind 18 scala sbt

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

sbt> generate
Run Code Online (Sandbox Code Playgroud)

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

有一个App.scala文件myproject/src/main/scala/my,简化的代码是这样的:

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

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

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

generate := {
  my.App.main(Array())
}
Run Code Online (Sandbox Code Playgroud)

但是由于无法找到,因此无法编译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())
  }

}
Run Code Online (Sandbox Code Playgroud)

但它仍然无法编译,它无法找到包my.

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

Set*_*sue 14

.sbt格式中,执行:

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

fullRunTask(generate, Compile, "my.App")
Run Code Online (Sandbox Code Playgroud)

这在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
Run Code Online (Sandbox Code Playgroud)

在简单的情况下工作正常,但不是可定制的.

更新: Jacek建议使用resourceGeneratorssourceGenerators改为好,如果它适合您的用例 - 无法从您的描述中判断它是否适用.


Jac*_*ski 9

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

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

SBT已经提供了一种产生在构建时文件- sourceGeneratorsresourceGenerators-和我似乎无法注意到需要从已经阅读问题定义一个单独的任务这一点.

生成文件(请参阅提交中的文档的未来版本)中,您可以阅读:

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
Run Code Online (Sandbox Code Playgroud)

要做到这一点,你应该返回一个Seq[File]包含生成的文件(而不是空Seq[File]()).

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

该项目的布局如下:

$ tree
.
??? build.sbt
??? project
    ??? App.scala
    ??? build.properties
Run Code Online (Sandbox Code Playgroud)

关注点的分离(在da haus中也称为@joescii)

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

让我们假设你有一个单独的项目build-utilsApp.scalasrc/main/scala.这是一个只有Scala代码的常规sbt配置.

jacek:~/sandbox/so/generate-project-code
$ tree build-utils/
build-utils/
??? src
    ??? main
        ??? scala
            ??? App.scala
Run Code Online (Sandbox Code Playgroud)

您可以将其作为常规Scala应用程序进行测试,而不会破坏sbt.不需要额外的设置(并且可以让你的思想从某些方面获益 - 有时候设置总是有帮助的).

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

项目代码/ build.sbt

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

generate := {
  my.App.main(Array())
}
Run Code Online (Sandbox Code Playgroud)

现在最重要的部分 - 项目之间的连接,以便App代码可用于构建project-code:

项目代码/项目/ 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
Run Code Online (Sandbox Code Playgroud)

使用构建定义,执行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
Run Code Online (Sandbox Code Playgroud)

我已将代码更改App为:

> eval "cat ../build-utils/src/main/scala/App.scala"!
package my

object App {
  def main(args: Array[String]) {
    println("Hello from App.main")
  }
}
Run Code Online (Sandbox Code Playgroud)

项目结构如下:

jacek:~/sandbox/so/generate-project-code/project-code
$ tree
.
??? build.sbt
??? project
    ??? build.properties
    ??? build.sbt
Run Code Online (Sandbox Code Playgroud)

其他变化也就是好东西

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

  • main方法的代码移出一个单独的方法,该方法返回生成的文件并main调用它.它将使代码sourceGenerators更容易重用(没有必要Array()调用它以及显式返回文件).
  • 使用filtermap功能convert(添加更多功能的味道).


joe*_*cii 5

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

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