查看 sbt 项目的依赖关系树

sbt 是借助于 ivy 来管理项目依赖, 像 Maven 项目中可以用 dependency:tree 来显示依赖树, 那么对于 sbt 项目该如何查看项目依赖关系呢? 本文提及了三种方式来显示项目依赖, 它们是 Shell 脚本, 自定义 sbt 任务, 和 sbt-dependency-plugin 方式. 最后一个办法使得我们也能用 dependencyTree 显示出 Maven 的  dependency:tree 效果来, 还有更酷的的.

> dependencyTree
[info] default:test_2.10:0.1-SNAPSHOT [S]
[info]   +-ch.qos.logback:logback-classic:1.0.13
[info]     +-ch.qos.logback:logback-core:1.0.13
[info]     +-org.slf4j:slf4j-api:1.7.5
[info]
[success] Total time: 0 s, completed Apr 5, 2016 12:29:53 AM
下面是探索的全部过程.

通过 sbt 控制台的 tab  自动完成或用 help .*[Dd]ependenc.* 命令再进一步过滤出与依赖比较接近 sbt 控制台任务
dependencyClasspath  The classpath consisting of internal and external, managed and unmanaged dependencies.
externalDependencyClasspath  The classpath consisting of library dependencies, both managed and unmanaged.
internalDependencyClasspath  The internal (inter-project) classpath.

projectDependencies  Inter-project dependencies.
excludeDependencies  Declares managed dependency exclusions.
dependencyCacheDirectory  The base directory for cached dependencies.
allDependencies  Inter-project and library dependencies.
libraryDependencies  Declares managed dependencies.
dependencyOverrides  Declares managed dependency overrides.
trackInternalDependencies  The level of tracking for the internal (inter-project) dependency.
dependencyPositions  Source positions where the dependencies are defined.
来深入, 我们建立一个简单的项目, 文件目录如下
test
├── build.sbt
└── lib
    └── guava-18.0.jar
build.sbt 文件的内容是
libraryDependencies ++= Seq(
  "ch.qos.logback" % "logback-classic" % "1.0.13"
)
我们来看看 sbt 的  dependencyClasspath, externalDependencyClasspath, 和 internalDependencyClasspath 的输出
1> show dependencyClasspath
2[info] List(Attributed(/Users/yanbin/test/lib/guava-18.0.jar), Attributed(/Users/yanbin/.sbt/boot/scala-2.10.6/lib/scala-library.jar), Attributed(/Users/yanbin/.ivy2/cache/ch.qos.logback/logback-classic/jars/logback-classic-1.0.13.jar), Attributed(/Users/yanbin/.ivy2/cache/ch.qos.logback/logback-core/jars/logback-core-1.0.13.jar), Attributed(/Users/yanbin/.ivy2/cache/org.slf4j/slf4j-api/jars/slf4j-api-1.7.5.jar))
3[success] Total time: 0 s, completed Apr 4, 2016 11:54:57 PM
4> show externalDependencyClasspath
5[info] List(Attributed(/Users/yanbin/test/lib/guava-18.0.jar), Attributed(/Users/yanbin/.sbt/boot/scala-2.10.6/lib/scala-library.jar), Attributed(/Users/yanbin/.ivy2/cache/ch.qos.logback/logback-classic/jars/logback-classic-1.0.13.jar), Attributed(/Users/yanbin/.ivy2/cache/ch.qos.logback/logback-core/jars/logback-core-1.0.13.jar), Attributed(/Users/yanbin/.ivy2/cache/org.slf4j/slf4j-api/jars/slf4j-api-1.7.5.jar))
6[success] Total time: 0 s, completed Apr 4, 2016 11:55:03 PM
7> show internalDependencyClasspath
8[info] List()
9[success] Total time: 0 s, completed Apr 4, 2016 11:55:10 PM

这里我们没有定义子项目, 所以 internalDependencyClasspath 为空, dependencyClasspath 和  externalDependencyClasspath 是一样的.

一般来说我们关心的是 externalDependencyClasspath 的内容, 上面显示的是 List[Attribute[String]] 类型的内容, 为了可读性, 可以用 Shell 脚本或自定义 sbt 任务来格式化显示它.

Shell 脚本显示, 在项目目录下创建 dependencies.sh, 内容如下:
 1#!/bin/bash
 2
 3echo "Direct dependencies"
 4sbt 'show all-dependencies' | gawk 'match($0, /List\((.*)\)/, a) {print a[1]}' | tr -d ' ' | tr ',' '\n' | sort -t ':' | \
 5  tr ':' '\t' | expand -t 30
 6
 7echo -e "\nAll dependencies, including transitive dependencies"
 8sbt 'show managed-classpath' | tr -d ' ' | tr ',' '\n' | gawk 'match($0, /Attributed\((.*)\)/, a) {print a[1]}' | \
 9  tr -d '()' | sed "s^$HOME/.ivy2/cache/^^g" | sed "s^/jars^^" | \
10  gawk -F / '{print $1, $3}' | sort | tr ' ' '\t' | expand -t 30

Mac 下虽安装 gawk, 可用命令  brew install gawk 安装, 并用 chmod +x dependencies.sh 加上可执行属性, 完整命令如下:
1brew install gawk
2chmod +x dependencies.sh
3./dependencies.sh

显示结果大致如下:
 1Direct dependencies
 2ch.qos.logback                logback-classic               1.0.13
 3optional(default)
 4optional(default)
 5org.scala-lang                scala-compiler                2.10.6                        scala-tool->default
 6org.scala-lang                scala-library                 2.10.6
 7org.scala-lang                scala-library                 2.10.6                        scala-tool->default
 8
 9All dependencies, including transitive dependencies
10                              yanbin
11ch.qos.logback                logback-classic-1.0.13.jar
12ch.qos.logback                logback-core-1.0.13.jar
13org.slf4j                     slf4j-api-1.7.5.jar

自定义 sbt 任务, 在 build.sbt 中加入如下内容:
 1lazy val versionReport = TaskKey[String]("version-report")<br/><br/>
 2// Add this setting to your project.
 3versionReport <<= (externalDependencyClasspath in Compile, streams) map {
 4   (cp: Seq[Attributed[File]], streams) =>
 5     val report = cp.map {
 6       attributed =>
 7         attributed.get(Keys.moduleID.key) match {
 8           case Some(moduleId) => "%40s %20s %10s %10s".format(
 9             moduleId.organization,
10             moduleId.name,
11             moduleId.revision,
12             moduleId.configurations.getOrElse("")
13           )
14           case None           =>
15             // unmanaged JAR, just
16             attributed.data.getAbsolutePath
17         }
18     }.mkString("\n")
19     streams.log.info(report)
20     report
21  }

重新运行 sbt 或 reload 之后执行 versionReport 任务, 输出如下:
1> versionReport
2[info] /Users/yanbin/test/lib/guava-18.0.jar
3[info]                           org.scala-lang        scala-library     2.10.6
4[info]                           ch.qos.logback      logback-classic     1.0.13
5[info]                           ch.qos.logback         logback-core     1.0.13
6[info]                                org.slf4j            slf4j-api      1.7.5

这能让我们看到所有的依赖, 但并未显示成树状关系图, 还没有达到本文标题所称的目标, 所以终极办法也是最简单的办法就是不重新发明轮子, 使用现有的插件 https://github.com/jrudolph/sbt-dependency-graph.

sbt 插件显示依赖树

可以在 ~/.sbt/0.13/plugins/plugins.sbt 或项目中新建 project/plugins.sbt 中加入行
1addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.8.2")

这时在 sbt 的控制台下就增加了好多任务,
 1dependencyTree: Shows an ASCII tree representation of the project's dependencies
 2dependencyBrowseGraph: Opens a browser window with a visualization of the dependency graph (courtesy of graphlib-dot + dagre-d3).
 3dependencyGraph: Shows an ASCII graph of the project's dependencies on the sbt console
 4dependencyList: Shows a flat list of all transitive dependencies on the sbt console (sorted by organization and name)
 5whatDependsOn <organization> <module> <revision>: Find out what depends on an artifact. Shows a reverse dependency tree for the selected module.
 6dependencyLicenseInfo: show dependencies grouped by declared license
 7dependencyStats: Shows a table with each module a row with (transitive) Jar sizes and number of dependencies
 8dependencyGraphMl: Generates a .graphml file with the project's dependencies to target/dependencies-<config>.graphml. Use e.g. yEd to format the graph to your needs.
 9dependencyDot: Generates a .dot file with the project's dependencies to target/dependencies-<config>.dot. Use graphviz to render it to your preferred graphic format.
10ivyReport: let's ivy generate the resolution report for you project. Use show ivyReport for the filename of the generated report

我比较感兴趣的是 dependencyTree(显示像  Maven 的  dependency:tree 那样) 和 dependencyBrowseGraph(打开浏览器显示一个依赖关系图)
1> dependencyTree
2[info] default:test_2.10:0.1-SNAPSHOT [S]
3[info]   +-ch.qos.logback:logback-classic:1.0.13
4[info]     +-ch.qos.logback:logback-core:1.0.13
5[info]     +-org.slf4j:slf4j-api:1.7.5
6[info]
7[success] Total time: 0 s, completed Apr 5, 2016 12:29:53 AM

或者执行 dependencyBrowseGraph 后打开一个浏览器

还有 dependencyGraph 任务输出为文本图形, dependencyDot 能生成 dot 文件, 等等.

用 sbt-dependency-graph 插件的办法是最强大也是最简单, 而且如果在 ~/.sbt/0.13/plugins/plugins.sbt 加载该插件更可谓是一劳永逸的做法.

参考: 1. https://groups.google.com/forum/#!topic/simple-build-tool/rcPh-lWbDtk
        2. https://github.com/jrudolph/sbt-dependency-graph
        3. http://mikeslinn.blogspot.com/2012/07/bash-script-to-update-all-git.html

永久链接 https://yanbin.blog/show-sbt-dependency-tree/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。