Scala 在实际操作中重现任务依赖关系

Scala 在实际操作中重现任务依赖关系,scala,sbt,Scala,Sbt,显示了使用依赖关系的示例。这是非常简单,人工,但它的工作!因此,我在我的项目/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"

显示了使用依赖关系的示例。这是非常简单,人工,但它的工作!因此,我在我的
项目/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,以便it任务可用于所有项目和所有配置
  • 如何在合成配置中重现此错误

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

问题是,您试图在没有正确的密码的情况下访问键值

文档在这里给了我们一些提示

默认情况下,与编译、打包和 正在运行的应用程序的作用域为配置,因此可能会工作 在每种配置中都不同。最明显的例子是 任务键编译、打包和运行;但是所有影响你的钥匙 这些键(例如源目录或scalac选项或 完整类路径)也适用于配置

让我们首先关注一个非常简单的示例,它可能没有多大意义,但说明了问题。假设您想将
编译任务重新定义为自身

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
,您将看到inspect将在
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
configuration来解决此问题

lazy val root = project.in(file(".")).settings(Seq(
  compile in Compile := {(compile in Compile).value}
): _*)
这是可行的,但是如果您想获得编译值,而不管它在哪里,该怎么办?这是方便的地方。回到你最初的例子。我假设您希望从所有项目中获取compile的分析对象

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。然后我们查找所有编译值

您可以配置
ScopeFilter
以满足您的需要,并且只针对特定项目/配置甚至任务进行筛选。但理解这个问题的关键是要记住,在SBT中,设置总是有范围的

编辑 您询问了
编译
不是全局定义的,而是每个项目都可以使用的。这是因为没有什么可以定义它。每个项目都包括它。如果您从构建定义中删除了
super.settings
,您会发现其中
compile
是未定义的

好像你应该这样做。通常情况下,不建议在插件中覆盖
设置。不过我建议你们把它和这一章一起读。它应该给你一个如何进行的想法

通过定义新任务并返回值,还可以从多个作用域中获取多个值。例如,要获得项目分析,可以使用以下代码段

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) }
    }

}

“合成配置”是什么意思?最小可能无意义的scala.build文件,它只会复制错误“引用未定义的设置”。我需要分别输出每个配置的分析,以检查其中包含哪些类。第一个例子中的作用域呢?它们是在全球范围内定义的,因此它们可以在全球范围内使用,正如我所假设的那样。编译不是全局定义的,问题就在于它。但是,如果compile不是全局定义的,那么它实际上是如何定义的并可用于所有项目的呢?我可以用同样的方式定义debugapi吗?我已经更新了我的答案,我希望它现在更清楚。请记住,键不是设置,它们不是作用域,只有它们的值存在于作用域中。