SBT:将输入预应用到 inputKeys

SBT: pre-applying input to inputKeys

在 SBT 中:我想定义一个 inputKey,它读取命令行参数,稍微更改它们并使用结果作为其他 inputKeys 的输入。

我试过了:

lazy val demo = inputKey[Unit]("A demo input task.")
lazy val root = (project in file(".")).settings(
  libraryDependencies += jUnitInterface
).settings(
  demo := {
    val args: Seq[String] = spaceDelimited("<arg>").parsed
    val one = (run in Compile).fullInput(args(0) + "foo").evaluated
  }
)

但我得到 error: Illegal dynamic reference: args

我也试过:

demo := {
  val args: Seq[String] = spaceDelimited("<arg>").parsed
  System.setProperty("args0", args(0))
  val one = (run in Compile).fullInput(System.getProperty(args0) + "foo").evaluated
}

这根本不提供输入。我怀疑这是执行顺序的问题(属性 在我需要时没有设置,因为 JVM 可以自由移动行)。

所以,在绝望中,我什至尝试了残忍的做法:

demo := {
  val args: Seq[String] = spaceDelimited("<arg>").parsed
  try {
    System.setProperty("args0", args(0))
  } finally {
    val one = (run in Compile).fullInput(System.getProperty(args0) + "foo").evaluated
  }
}

强制下单。这只会抛出 NullPointerException。

正如 Daniel C. Sobral 提到的,parsedevaluated 是宏,在 InputWrapper 中定义。

由于它们是在编译时执行的,而参数是在运行时检索的,因此它们不能很好地混合。特别是,args 的值仅在运行时真正定义,不能通过 evaluated 宏检索。

编辑:在与 OP 聊天后,我确定他的目标是写作的捷径 myTask Foo bar testOnly *Foo* -- --tests=*bar*,我相应地更新了我的答案。

更新答案

如前所述,由于您基本上想要 "macro" 来编写 myTask Foo bar 而不是 testOnly *Foo* -- --tests=*bar*,这是我的解决方案:

val filtersParser = {
    import complete.DefaultParsers._
    (token(Space) ~> token(StringBasic, "<classFilter>")) ~
        (token(Space) ~> token(StringBasic, "<methodFilter>"))
}

lazy val testFiltered = inputKey[Unit]("runs test methods matching *<methodFilter>* within classes matching *<classFilter>*")

testFiltered.in(Test) := Def.inputTaskDyn {
    val (classFilter, methodFilter) = filtersParser.parsed
    runTestsFiltered(classFilter, methodFilter)
}.evaluated

def runTestsFiltered(classFilter: String, methodFilter: String) = Def.taskDyn {
    (testOnly in Test).toTask(s" *$classFilter* -- --tests *$methodFilter*")
}

更详细

您需要一个自定义解析器来检索您期望的两个参数。这是通过以下代码实现的,它基本上定义了两个组,"chomping" 两个空格而不记得它们,以及两个 StringBasic 参数,它们是解析器的结果(filtersParser 是类型Parser[(String, String)])

val filtersParser = {
    import complete.DefaultParsers._
    (token(Space) ~> token(StringBasic, "<classFilter>")) ~
        (token(Space) ~> token(StringBasic, "<methodFilter>"))
}

然后你需要一个输入任务来使用解析器的结果并将它们转发给测试框架。 这是在下一个片段中完成的(如果有人比我更了解使用 inputTaskDyn 的微妙之处,我将很乐意得到启发 :) )。请注意任务范围的定义 .in(Test),它授予对测试依赖项的访问权限。

lazy val testFiltered = inputKey[Unit]("runs test methods matching *<methodFilter>* within classes matching *<classFilter>*")

testFiltered.in(Test) := Def.inputTaskDyn {
    val (classFilter, methodFilter) = filtersParser.parsed
    runTestsFiltered(classFilter, methodFilter)
}.evaluated

最后一段代码只是将参数转发给预先存在的 testOnly 任务:

def runTestsFiltered(classFilter: String, methodFilter: String) = Def.taskDyn {
    (testOnly in Test).toTask(s" *$classFilter* -- --tests *$methodFilter*")
}

上一个回答

但是,您应该能够通过在两个任务中拆分定义和用法来绕过它:

import sbt._
import complete.DefaultParsers._

lazy val loadArgTask = inputKey[Unit]("loads and transforms argument")

lazy val runStuff = taskKey[Unit]("Runs some stuff")

lazy val loadArgIntoPropertyTask: Def.Initialize[InputTask[Unit]] = Def.inputTask {
    val myArg = (token(Space) ~> token(StringBasic, "<myArg>")).parsed
    System.setProperty("myArg", myArg + "foo")
}

loadArgTask <<= loadArgIntoPropertyTask

runStuff := {
    println(System.getProperty("myArg"))
}

可以如下使用

> loadArgTask orange
[success] Total time: 0 s, completed [...]
> runStuff
orangefoo
[success] Total time: 0 s, completed [...]