Freewind @ Thoughtworks scala java javascript dart 工具 编程实践 月结 math python english [comments admin] [feed]

(2014-09-15) sbt中读懂inspect的输出

广告: 云梯:翻墙vpn (省10元) 土行孙:科研用户翻墙http proxy (有优惠)

在sbt中,我们可以使用inspect命令查看一个key,比如一个任务,一个设置等。

(如果本文中有一些内容不理解,可以参看我写的关于sbt的其它文章)

inspect name

先以最常见的name为例。我们都需要在build.sbt中定义一个name,表示所在的项目名称:

name := "my-project"

使用inspect来查看它:

$ sbt
> inspect name
[info] Setting: java.lang.String = my-project
[info] Description:
[info]  Project name.
[info] Provided by:
[info]  {file:/Users/freewind/workspace/sbt-scope-test/}sbt-scope-test/*:name
[info] Defined at:
[info]  /Users/freewind/workspace/sbt-scope-test/build.sbt:1
[info] Reverse dependencies:
[info]  *:onLoadMessage
[info]  *:description
[info]  *:normalizedName
[info]  *:projectInfo
[info] Delegates:
[info]  *:name
[info]  {.}/*:name
[info]  */*:name

如何看懂这些描述呢?

Setting

[info] Setting: java.lang.String = my-project
[info] Description:
[info]  Project name.

表示name是一个值类型为String的Setting,其值为my-project,说明信息是Project name

从这些内容我们可以反推出它的定义,应该形如:

lazy val name = settingKey[String]("Project name.")

name := "my-project"

Task

补充:除了Setting,还有Task类型的。比如我这样定义一个hello:

lazy val hello = taskKey[Unit]("hello task")

hello := println("hello")

则:

> inspect hello
[info] Task: Unit
[info] Description:
[info]  hello task
...

可以看到,这里显示的就是Task: Unit了。

Provided by

然后是Provided by:

[info] Provided by:
[info]  {file:/Users/freewind/workspace/sbt-scope-test/}sbt-scope-test/*:name

这句长长的信息是在告诉我们,它在哪里找到了符合我们输入的key的完整定义。我们输入的命令是:

inspect name

这里的name只给了key名,没有同时给出project/config/inkey等scope信息。根据sbt的定义,只给出名字时,这个key表示:

其所在项目的,以global为config,global为inkey的名为name的key

其中inkey表示在配置中定义的另一个key,可以是settingKey,taskKey和inputKey.

inspect会自动帮我们填上缺少的部分,然后寻找与其匹配的scope最接近的key,以Provided by的形式显示出来。

在匹配过程中,如果指定的project不存在,则去寻找ThisBuild,即整个build范围。如果指定的config或者inkey不存在,则去寻找global scope,直到找到最接近的一个。

比如:

{file:/Users/freewind/workspace/sbt-scope-test/}sbt-scope-test/*:name

这句话,就是告诉我们,对于我们输入的name这个key,它找到的最接近的是:位于file:/.../sbt-scope-test/目录下的sbt-scope-test项目中的,以global为config的,名为name的key

输入更完整的key

我们还可以通过指定其它scope信息,来输入更完整的key,比如:

inspect compile:name

根据前面所说,sbt在找不到精确匹配的情况下,会去寻找最接近的key。所以sbt会先去找有没有这样的定义:

name in compile := ???

如果找不到,再去找:

name in Global := ???

或者它的等价形式:

name := ???

试一下:

$ sbt
> inspect compile:name
[info] Setting: java.lang.String = my-project
[info] Description:
[info]  Project name.
[info] Provided by:
[info]  {file:/Users/freewind/workspace/sbt-scope-test/}sbt-scope-test/*:name
...

可以看到,它没有找到精确匹配compile:name的key,最终找到的还是*:name,跟前面一样。

但是如果我们在build.sbt给它添加一下:

name in Compile := "my-project-for-compile"

注意,这里写的是Compile,它对应的是一个config,而不是compile task.

则:

> inspect compile:name
[info] Setting: java.lang.String = my-project-for-compile
[info] Description:
[info]  Project name.
[info] Provided by:
[info]  {file:/Users/freewind/workspace/sbt-scope-test/}sbt-scope-test/compile:name
[info] Defined at:
[info]  /Users/freewind/workspace/sbt-scope-test/build.sbt:7
[info] Delegates:
[info]  compile:name
[info]  *:name
[info]  {.}/compile:name
[info]  {.}/*:name
[info]  */compile:name
[info]  */*:name
[info] Related:
[info]  *:name

可以看到,此时的Provided by就已经变成:

{file:/Users/freewind/workspace/sbt-scope-test/}sbt-scope-test/compile:name

而不是*:name了,说明它找到了更精确的key。

Defined at

接着是Defined at:

[info] Defined at:
[info]  /Users/freewind/workspace/sbt-scope-test/build.sbt:1

它很有用,直接告诉我们这个key是在哪个文件的哪一行定义的,很酷,方便我们去寻找定义。

Dependencies

这个Dependencies是说这个key依赖于别的哪些key。因为我们在例子中使用的name并不依赖于别的key,所以没有显示该节。在这一节我将换个例子:

lazy val myname = settingKey[String]("a setting of myname")

myname <<= name { n => n + "!!!"}

我们定义了一个myname的key,它依赖于key name的值。

$ sbt
> inspect myname
...
[info] Dependencies:
[info]  *:name

这里会显示Dependencies项,告诉我们这个key依赖另一个定义config scope为global的名为name的key.

Reverse dependencies

接下来是inspect name中的Reverse dependencies了。它表示其它哪些key依赖于本key:

[info] Reverse dependencies:
[info]  *:onLoadMessage
[info]  *:description
[info]  *:normalizedName
[info]  *:projectInfo

Delegates

前面说,当我们输入了一个key后,sbt会自动匹配跟它最接近的key。在匹配的过程中,是有一条由窄到宽的路径的,而Delegates就是告诉我们这条路径究竟是什么样的。

对于我们的例子inspect name来说,它的Delegates是这样的:

[info] Delegates:
[info]  *:name
[info]  {.}/*:name
[info]  */*:name

对于name来说,它的由窄到宽的路径是这样的:

name -> *:name -> {.}/*:name -> */*:name

其中{.}/*:name中的{.}/表示整个build(ThisBuild),*/*:name中的*/表示global project。我觉得它们两者之间很相似,但搜索顺序不同。

晚点会专门写一篇文章来讲ThisBuild和global project之间的区别,这里只要先知道它们的顺序就行了。

Related

inspect还有一个Related,在inspect name的例子中没有体现,这里换成inspect compile:name.

首先是build.sbt:

lazy val myname = settingKey[String]("a setting of myname")

myname in Test := "my-name-in-test"

然后运行:

$ sbt
> inspect compile:myname
[info] No entry for key.
[info] Description:
[info]  a setting of myname
[info] Delegates:
[info]  compile:myname
[info]  *:myname
[info]  {.}/compile:myname
[info]  {.}/*:myname
[info]  */compile:myname
[info]  */*:myname
[info] Related:
[info]  test:myname

可以看到,虽然它没有找到compile:myname的值,但是它发现myname在其它的scope中有值,便把它们显示在Related里。

comments powered by Disqus