在实际操作中重现任务依赖性

时间:2014-05-18 15:48:02

标签: scala sbt

the sbt task documentation显示了使用依赖项的示例。它非常简单,人工但它有效!所以我在我的project/scala.build中没有问题地重现了它。

请注意,我选择全局范围来使任务可用于任何项目和任何配置

import sbt._
import Keys._

object TestBuild extends Build {
  lazy val sampleTask = taskKey[Int]("A sample task")
  lazy val intTask = taskKey[Int]("An int task")

  override lazy val settings = super.settings ++ Seq(
    intTask := 1 + 2 ,
    sampleTask := intTask.value + 1
  )

}

现在我正在尝试做一些有用的事情并用收集已编译类名的任务来丰富现有的sbt键定义

import sbt._
import Keys._
import sbt.inc.Analysis
import xsbti.api.ClassLike
import xsbt.api.Discovery.{isConcrete, isPublic}

object TestBuild extends Build {
  lazy val debugAPIs = taskKey[List[String]]("list of all top-level definitions")

  override lazy val settings = super.settings ++ Seq(
    debugAPIs := getAllTop( compile.value )
  )

  private def getAllTop(analysis : Analysis) : List[String] =
    Tests.allDefs(analysis).toList collect {
      case c : ClassLike if isConcrete(c) && isPublic(c) => c.name
    }
}

现在我从sbt获得错误:

Reference to undefined setting: 

  {.}/*:compile from {.}/*:debugAPIs (/home/sbt/project/build.scala:11)

所以我有两个问题:

  • 我应该如何正确定义debugAPI,以便它可以用于所有项目和所有配置?
  • 如何在合成配置中重现此错误?

实际上我对第二个问题更感兴趣。我希望深入了解sbt是如何工作的,因为我想为它编写一个插件。

1 个答案:

答案 0 :(得分:3)

问题是您尝试在没有正确Scope的情况下访问密钥值。

文档在这里提供了一些提示。

  

默认情况下,与编译,打包和关联的所有键   运行范围限定为配置,因此可能有效   每种配置都有所不同。最明显的例子是   任务键编译,打包和运行;但所有影响的关键   那些键(例如source-directory或scalac-options或   full-classpath)也是作用于配置的。

让我们首先关注一个非常简单的例子,它可能没什么意义,但说明了问题。让我们假设您要将compile任务重新定义为自己。

override lazy val settings = super.settings ++ Seq (
    compile := { compile.value }
)

在SBT中运行它会给你一个错误,这或多或少像这样

[error]   {.}/*:compile from {.}/*:compile (/tmp/q-23723818/project/Build.scala:12)
[error]      Did you mean compile:compile ?

我们没有指定范围,因此SBT选择了一些默认值。该项目设置为ThisBuild(表示没有特定项目),配置设置为Global。在该上下文中,该设置未定义。但是,了解密钥不是设置很重要。密钥可以不带范围存在,但密钥的值附加到范围。另请注意,如果SBT在请求的范围内找不到值,它可以委托给其他范围,但这是另一个主题。

我们如何检查?结果证明这很简单。让我们忽略错误,让SBT开始。

如果您输入inspect compile,您会看到检查会查找compile:compile,其中定义了值。我们可以强迫它查看特定范围,例如inspect {.}/*:compile,会查看给我们错误的范围。

> inspect {.}/*:compile
[info] No entry for key.

确实没有定义。

如何解决问题?您必须为SBT提供您正在寻找的范围。天真的你可以尝试添加一个配置范围。

// this will NOT work
override lazy val settings = super.settings ++ Seq (
    compile in Compile := { (compile in Compile).value }
)

但是没有全局编译,每个项目只有编译。您可以通过不覆盖全局设置,但是覆盖特定项目的设置,并在那里指定Compile配置来克服此问题。

lazy val root = project.in(file(".")).settings(Seq(
  compile in Compile := {(compile in Compile).value}
): _*)

这样可行,但是如果你想获得编译值而不管它在哪里呢?这是ScopeFilter派上用场的地方。回到原始示例。我假设您想从所有项目中获取编译的Analysis对象。

import sbt._
import Keys._
import sbt.inc.Analysis
import xsbti.api.ClassLike
import xsbt.api.Discovery.{isConcrete, isPublic}

object TestBuild extends Build {

  val debugAPIs = taskKey[Seq[String]]("list of all top-level definitions")

  val compileInAnyProject = ScopeFilter(inAnyProject, inConfigurations(Compile))

  override lazy val settings = super.settings ++ Seq(
    debugAPIs := { 
      getAllTop(compile.all(compileInAnyProject).value)
    }
  )

  private def getAllTop(analyses : Seq[Analysis]) : Seq[String] =
    analyses.flatMap { analysis =>
      Tests.allDefs(analysis) collect { case c : ClassLike if isConcrete(c) && isPublic(c) => c.name }
    }

}

我们创建的是针对任何项目的ScopeFilter过滤,以及用于Compile配置的项目。然后我们查找了所有编译值。

您可以配置ScopeFilter以满足您的需求,并仅筛选特定项目/配置甚至任务。但理解这个问题的关键是要记住,在SBT设置中始终是作用域。

修改

您已经问过compile如何在全球范围内定义但每个项目都可用。这是因为有Defaults.defaultSettings定义它。每个项目都包含它。如果您从构建定义中删除了super.settings,则会看到其他compile未定义。

好像你应该这样做。在Plugin Best Practices中,通常不鼓励在插件中覆盖settings。但是,我建议您与Plugins章一起阅读。它应该让你知道如何继续。

您还可以通过定义返回它们的新任务从多个范围获取多个值。例如,要使用项目进行分析,您可以使用以下代码。

object TestBuild extends Build {

  val debugAPIs = taskKey[Seq[(String, String)]]("list of all top-level definitions")

  val compileInAnyProject = ScopeFilter(inAnyProject, inConfigurations(Compile))

  override lazy val settings = super.settings ++ Seq(
    debugAPIs := { 
      getAllTop(analysisWithProject.all(compileInAnyProject).value)
    }
  )

  lazy val analysisWithProject = Def.task { (thisProject.value, compile.value) }

  private def getAllTop(analyses : Seq[(ResolvedProject, Analysis)]) : Seq[(String, String)] =
    analyses.flatMap { case (project, analysis) =>
      Tests.allDefs(analysis) collect { case c : ClassLike if isConcrete(c) && isPublic(c) => (project.id, c.name) }
    }

}