Merge branch 'wip/prep' of sbt-dependency-graph into wip/dependencygraph

This commit is contained in:
Eugene Yokota 2020-09-21 15:14:18 -04:00
commit 894d005f12
71 changed files with 2580 additions and 0 deletions

View File

@ -0,0 +1,149 @@
# Changelog
## Unreleased
* [#184](https://github.com/jrudolph/sbt-dependency-graph/pull/184): Fix regression in 0.10.0-RC1 for recent sbt versions when
`cachedResolution` (with coursier turned off). Thanks [@bjaglin](https://github.com/bjaglin) for the report and the fix.
## Version 0.10.0-RC1 (2019-07-24)
* [#136](https://github.com/jrudolph/sbt-dependency-graph/pull/136): Added `dependencyBrowseTree` to open a searchable dependency tree in the browser.
Thanks, [@pcejrowski](https://github.com/pcejrowski) for contributing this feature.
* [#163](https://github.com/jrudolph/sbt-dependency-graph/pull/163): Remove some usage of internal sbt APIs, this allows to run sbt-dependency-graph with sbt 1.3.0
but results are not entirely correct.
* [#165](https://github.com/jrudolph/sbt-dependency-graph/pull/165): For common operations introduce `asString`, `printToConsole`, and `toFile` subtasks
## Version 0.9.2 (2018-08-26)
* [#159](https://github.com/jrudolph/sbt-dependency-graph/pull/159): Fixed regression in `whatDependsOn` where task parser failed when no other sbt-dependency-graph task was called before
## Version 0.9.1 (2018-07-17)
* [#110](https://github.com/jrudolph/sbt-dependency-graph/issues/110): `whatDependsOn` can now be called without specifying a version. Thanks, @chikei for the initial implementation.
* [#150](https://github.com/jrudolph/sbt-dependency-graph/issues/150): `ivyReport` now reports correct path again even for older sbt versions (< 0.13.16)
## Version 0.9.0 (2017-10-25)
This version (finally!) adds support for sbt 1.0. *sbt-dependency-graph* depends on a lot of internals from sbt to do its
work which is why it was quite an effort to do the migration. Thanks [@MasseGuillaume](https://github.com/MasseGuillaume) from Scala Center,
[@2m](https://github.com/2m), and [@xuwei-k](https://github.com/xuwei-k) for helping out with the effort.
The plugin is cross-built for sbt 0.13 (and will continued to be for while). The `dependencyGraph` task is currently not
supported on sbt 1.0. Use `dependencyBrowseGraph`, instead.
## Version 0.8.2 (2016-02-01)
This is a maintenance release [fixing](https://github.com/jrudolph/sbt-dependency-graph/issues/89) `dependencyBrowseGraph`
in the latest Chrome versions. Thanks [@chtefi](https://github.com/chtefi)!
## Version 0.8.1 (2016-01-08)
This is a maintenance release fixing a regression in 0.8.0 and adding two small features.
All changes:
* [#84](https://github.com/jrudolph/sbt-dependency-graph/issues/84): Fix regression of DOT label rendering introduced in 0.8.0.
* [#83](https://github.com/jrudolph/sbt-dependency-graph/issues/83): Added new task `dependencyStats` which prints a
simple table of jar sizes for all your dependencies. Handy if you want to know why your assembled jar gets so big.
* [#85](https://github.com/jrudolph/sbt-dependency-graph/issues/85): Added new task `dependencyList` which prints a
flat, deduplicated list of all the transitive dependencies.
## Version 0.8.0 (2015-11-26)
sbt-dependency-graph is finally an AutoPlugin and can now show the dependency graph in the browser directly.
### New features
- (experimental) open dependency graph directly in the browser with `dependencyBrowseGraph` ([#29](https://github.com/jrudolph/sbt-dependency-graph/issues/29))
![dependencyBrowseGraph in action](https://gist.githubusercontent.com/jrudolph/941754bcf67a0fafe495/raw/7d80d766feb7af6ba2a69494e1f3ceb1fd40d4da/Screenshot%2520from%25202015-11-26%252014:18:19.png)
- this plugin is finally an sbt AutoPlugin and it is automatically enabled
([#51](https://github.com/jrudolph/sbt-dependency-graph/issues/51))
**Note: To update from 0.7.x remove the `net.virtualvoid.sbt.graph.Plugin.graphSettings` line from your configurations.**
### Other changes
- a new backend was implemented which accesses the in-memory dependency data structures of sbt directly. The plugin doesn't
require accessing the ivy report XML any more (the old backend can still be wired in for comparisons if needed) which
should have solved the race condition and the dreaded `FileNotFoundException` ([#39](https://github.com/jrudolph/sbt-dependency-graph/issues/39))
in multi-module projects. The new backend is only used for sbt >= 0.13.6.
- code was restructured which touched a lot of the classes but didn't change the function or syntax of settings and tasks.
- fixed [#77](https://github.com/jrudolph/sbt-dependency-graph/issues/77)
## Version 0.7.5 (2015-03-30)
This is a maintenance release adding support for sbt 0.13.8.
All changes:
* [#67](https://github.com/jrudolph/sbt-dependency-graph/issues/67): Added support for sbt 0.13.8. Thanks
[@eed3si9n](https://github.com/eed3si9n) for the fix.
* [#37](https://github.com/jrudolph/sbt-dependency-graph/issues/37): Don't fail with StringIndexOutOfBoundsException
for deep trees.
* [#44](https://github.com/jrudolph/sbt-dependency-graph/issues/44): Only match scala lib by org/name.
Thanks [@2beaucoup](https://github.com/2beaucoup) for the fix.
## Version 0.7.4 (2013-06-26)
This is a maintenance release fixing an exception when generating graphs without a terminal [#32](https://github.com/jrudolph/sbt-dependency-graph/issues/32).
## Version 0.7.3 (2013-04-28)
This is a maintenance release. Following issues have been fixed:
* [#27](https://github.com/jrudolph/sbt-dependency-graph/issues/27): A dependency configured with
a version range was not properly associated with its dependant.
* [#30](https://github.com/jrudolph/sbt-dependency-graph/issues/30) & [#31](https://github.com/jrudolph/sbt-dependency-graph/issues/31):
Make it work again with sbt 0.12.3. The path of the dependency resolution file changed in sbt 0.12.3.
Thanks [ebowman](https://github.com/ebowman) for the fix.
## Version 0.7.2 (2013-03-02)
This is a maintenance release. Following issues have been fixed:
* [#27](https://github.com/jrudolph/sbt-dependency-graph/issues/27): A dependency configured with
a version range was not properly associated with its dependant.
## Version 0.7.1
New features in this version:
* `dependency-license-info`: show dependencies grouped by declared license
* `dependency-dot`: create dot file from dependency graph. Contributed by
[berleon](https://github.com/berleon).
## Version 0.7.0 (2012-10-24)
New features in this version:
* `dependency-graph` now renders a real graph. Thanks go to [Matt Russell](https://github.com/mdr/) for
this added awesomeness.
* The tree output from previous versions is now available with `dependency-tree`.
* New task `what-depends-on` showing reverse dependency tree for a selected module (incl. tab-completion for modules)
* Don't fail in cases of a missing dependency. Show errors directly in the output.
* Show info about evicted versions.
* By default, exclude scala-library dependency and append `[S]` to the artifact name instead. Set
`filter-scala-library` to `false` to disable this feature.
* Works with sbt 0.12.1. The ivy report files were moved to a new location making an update necessary.
## Version 0.6.0 (2012-05-23)
New features in this version:
* `dependency-graph` task now prints the dependency graph to the console
(contributed by @gseitz)
* `dependency-graph-ml` contains now the old functionality of `dependency-graph`
which generates a `.graphml` file. Nodes now contain the dependency version as well (contributed by @gseitz).
* The output filename of `dependency-graph-ml` has been changed to include the configuration name. It is now
configurable using the `dependency-graph-ml-file` setting.
* The common `scalaVersion in update` idiom to support Scala 2.9.1 libraries in a
Scala 2.9.2 broke the plugin in 0.5.2, because it wouldn't find the ivy report xml file
any more. This was fixed.
* All tasks are scoped by configuration.
## Version 0.5.2 (2012-02-13)
## Version 0.5.1 (2011-11-18)
## Version 0.5 (2011-11-15)

View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,26 @@
AsciiTreeLayout.scala is copied from sbt and is distributed under the following license:
Copyright (c) 2008, 2009, 2010 Steven Blundy, Josh Cough, Mark Harrah, Stuart Roebuck, Tony Sloane, Vesa Vilhonen, Jason Zaugg
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,12 @@
Before publishing:
* create entry in CHANGELOG
* update version in README
* run `;^ clean; ^ test:compile; ^ test; ^ scripted; ^publishSigned`
* test staged version in sonatype
* release from sonatype
After publishing:
* push master and tags

View File

@ -0,0 +1,97 @@
# sbt-dependency-graph
[![Join the chat at https://gitter.im/jrudolph/sbt-dependency-graph](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/jrudolph/sbt-dependency-graph?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Visualize your project's dependencies.
**Note: Under sbt >= 1.3.x some features might currently not work as expected or not at all (like `dependencyLicenses`).**
## Usage Instructions
sbt-dependency-graph is an informational tool rather than one that changes your build, so you will more than likely wish to
install it as a [global plugin] so that you can use it in any SBT project without the need to explicitly add it to each one. To do
this, add the plugin dependency to `~/.sbt/0.13/plugins/plugins.sbt` for sbt 0.13 or `~/.sbt/1.0/plugins/plugins.sbt` for sbt 1.0:
```scala
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.10.0-RC1")
```
To add the plugin only to a single project, put this line into `project/plugins.sbt` of your project, instead.
The plugin currently supports sbt versions >= 0.13.10 and sbt 1.0.x. For versions supporting older versions of sbt see
the notes of version [0.8.2](https://github.com/jrudolph/sbt-dependency-graph/tree/v0.8.2#compatibility-notes).
## Main Tasks
* `dependencyTree`: Shows an ASCII tree representation of the project's dependencies
* `dependencyBrowseGraph`: Opens a browser window with a visualization of the dependency graph (courtesy of graphlib-dot + dagre-d3).
* `dependencyBrowseTree`: Opens a browser window with a visualization of the dependency tree (courtesy of jstree).
* `dependencyList`: Shows a flat list of all transitive dependencies on the sbt console (sorted by organization and name)
* `whatDependsOn <organization> <module> <revision>?`: Find out what depends on an artifact. Shows a reverse dependency
tree for the selected module. The `<revision>` argument is optional.
* `dependencyLicenseInfo`: show dependencies grouped by declared license
* `dependencyStats`: Shows a table with each module a row with (transitive) Jar sizes and number of dependencies
* `dependencyGraphMl`: Generates a `.graphml` file with the project's dependencies to `target/dependencies-<config>.graphml`.
Use e.g. [yEd](http://www.yworks.com/en/products_yed_about.html) to format the graph to your needs.
* `dependencyDot`: Generates a .dot file with the project's dependencies to `target/dependencies-<config>.dot`.
Use [graphviz](http://www.graphviz.org/) to render it to your preferred graphic format.
* `dependencyGraph`: Shows an ASCII graph of the project's dependencies on the sbt console (only supported on sbt 0.13)
* `ivyReport`: Lets ivy generate the resolution report for you project. Use
`show ivyReport` for the filename of the generated report
The following tasks also support the `toFile` subtask to save the contents to a file:
* `dependencyTree`
* `dependencyList`
* `dependencyStats`
* `dependencyLicenseInfo`
The `toFile` subtask has the following syntax:
```
<config>:<task>::toFile <filename> [-f|--force]
```
Use `-f` to force overwriting an existing file.
E.g. `test:dependencyStats::toFile target/depstats.txt` will write the output of the `dependencyStats` in the `test`
configuration to the file `target/depstats.txt` but would not overwrite an existing file.
All tasks can be scoped to a configuration to get the report for a specific configuration. `test:dependencyGraph`,
for example, prints the dependencies in the `test` configuration. If you don't specify any configuration, `compile` is
assumed as usual.
Note: If you want to run tasks with parameters from outside the sbt shell, make sure to put the whole task invocation in
quotes, e.g. `sbt "whatDependsOn <org> <module> <version>"`.
## Configuration settings
* `filterScalaLibrary`: Defines if the scala library should be excluded from the output of the dependency-* functions.
If `true`, instead of showing the dependency `"[S]"` is appended to the artifact name. Set to `false` if
you want the scala-library dependency to appear in the output. (default: true)
* `dependencyGraphMLFile`: a setting which allows configuring the output path of `dependency-graph-ml`.
* `dependencyDotFile`: a setting which allows configuring the output path of `dependency-dot`.
* `dependencyDotHeader`: a setting to customize the header of the dot file (e.g. to set your preferred node shapes).
* `dependencyDotNodeLabel`: defines the format of a node label
(default set to `[organisation]<BR/><B>[name]</B><BR/>[version]`)
E.g. in `build.sbt` you can change configuration settings like this:
```scala
filterScalaLibrary := false // include scala library in output
dependencyDotFile := file("dependencies.dot") //render dot file to `./dependencies.dot`
```
## Known issues
* [#19]: There's an unfixed bug with graph generation for particular layouts. Workaround:
Use `dependency-tree` instead of `dependency-graph`.
## License
Published under the [Apache License 2.0](http://en.wikipedia.org/wiki/Apache_license).
[global plugin]: http://www.scala-sbt.org/0.13/tutorial/Using-Plugins.html#Global+plugins
[global build configuration]: http://www.scala-sbt.org/0.13/docs/Global-Settings.html
[#19]: https://github.com/jrudolph/sbt-dependency-graph/issues/19

View File

@ -0,0 +1,30 @@
enablePlugins(ScriptedPlugin)
scriptedLaunchOpts += s"-Dproject.version=${version.value}"
libraryDependencies ++= {
if ((sbtVersion in pluginCrossBuild).value startsWith "0.13")
Seq("com.github.mdr" %% "ascii-graphs" % "0.0.3")
else
Nil
}
libraryDependencies += "org.specs2" %% "specs2-core" % "3.10.0" % Test
libraryDependencies += Defaults.sbtPluginExtra(
"com.dwijnand" % "sbt-compat" % "1.2.6",
(sbtBinaryVersion in pluginCrossBuild).value,
(scalaBinaryVersion in update).value
)
crossSbtVersions := Seq("1.2.7", "0.13.18")
scalacOptions ++= Seq(
"-deprecation",
"-encoding", "UTF-8",
"-feature",
"-unchecked"
)
ScalariformSupport.formatSettings

View File

@ -0,0 +1,9 @@
sbtPlugin := true
name := "sbt-dependency-graph"
organization := "net.virtual-void"
homepage := Some(url("http://github.com/jrudolph/sbt-dependency-graph"))
licenses in GlobalScope += "Apache License 2.0" -> url("https://github.com/jrudolph/sbt-dependency-graph/raw/master/LICENSE")

View File

@ -0,0 +1,20 @@
import sbt._
import com.typesafe.sbt.SbtScalariform
import com.typesafe.sbt.SbtScalariform.ScalariformKeys
object ScalariformSupport {
lazy val formatSettings = SbtScalariform.scalariformSettings ++ Seq(
ScalariformKeys.preferences in Compile := formattingPreferences,
ScalariformKeys.preferences in Test := formattingPreferences
)
import scalariform.formatter.preferences._
def formattingPreferences =
FormattingPreferences()
.setPreference(RewriteArrowSymbols, true)
.setPreference(AlignParameters, true)
.setPreference(AlignSingleLineCaseStatements, true)
.setPreference(DoubleIndentClassDeclaration, true)
}

View File

@ -0,0 +1 @@
sbt.version=1.2.7

View File

@ -0,0 +1 @@
addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.0")

View File

@ -0,0 +1,4 @@
libraryDependencies += "org.scala-sbt" %% "scripted-plugin" % sbtVersion.value
addSbtPlugin("org.scalariform" % "sbt-scalariform" % "1.8.2")
addSbtPlugin("com.dwijnand" % "sbt-dynver" % "2.0.0")

View File

@ -0,0 +1,31 @@
publishTo := {
val nexus = "https://oss.sonatype.org/"
Some {
if (version.value.trim.contains("+")) "snapshots" at nexus + "content/repositories/snapshots"
else "releases" at nexus + "service/local/staging/deploy/maven2"
}
}
publishMavenStyle := true
publishArtifact in Test := false
pomIncludeRepository := { _ => false }
scmInfo := Some(
ScmInfo(
browseUrl = url("https://github.com/jrudolph/sbt-dependency-graph"),
connection = "scm:git:git@github.com:jrudolph/sbt-dependency-graph.git"
)
)
developers := List(
Developer(
"jrudolph",
"Johannes Rudolph",
"johannes.rudolph@gmail.com",
url("https://virtual-void.net")
)
)
useGpg := true

View File

@ -0,0 +1,115 @@
<!doctype html>
<!--
Based on https://github.com/cpettitt/dagre-d3/blob/d215446e7e40ebfca303f4733e746e96420e3b46/demo/interactive-demo.html
which is published under this license:
Copyright (c) 2013 Chris Pettitt
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<meta charset="utf-8">
<title>Dependency Graph</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="https://www.samsarin.com/project/graphlib-dot/v0.6.1/graphlib-dot.js"></script>
<script src="https://www.samsarin.com/project/dagre-d3/v0.4.16/dagre-d3.min.js"></script>
<script src="dependencies.dot.js"></script>
<style>
body {
margin: 0;
overflow: hidden;
}
.node {
white-space: nowrap;
}
.node rect,
.node circle,
.node ellipse {
stroke: #333;
fill: #fff;
stroke-width: 1.5px;
}
.cluster rect {
stroke: #333;
fill: #000;
fill-opacity: 0.1;
stroke-width: 1.5px;
}
.edgePath path.path {
stroke: #333;
stroke-width: 1.5px;
fill: none;
}
</style>
<style>
h1, h2 {
color: #333;
}
</style>
<body onLoad="initialize()">
<svg width=1280 height=1024>
<g/>
</svg>
<script>
function initialize() {
// Set up zoom support
var svg = d3.select("svg"),
inner = d3.select("svg g"),
zoom = d3.behavior.zoom().on("zoom", function() {
inner.attr("transform", "translate(" + d3.event.translate + ")" +
"scale(" + d3.event.scale + ")");
});
svg.attr("width", window.innerWidth);
svg.call(zoom);
// Create and configure the renderer
var render = dagreD3.render();
function tryDraw(inputGraph) {
var g;
{
g = graphlibDot.read(inputGraph);
g.graph().rankdir = "LR";
d3.select("svg g").call(render, g);
// Center the graph
var initialScale = 0.10;
zoom
.translate([(svg.attr("width") - g.graph().width * initialScale) / 2, 20])
.scale(initialScale)
.event(svg);
svg.attr('height', g.graph().height * initialScale + 40);
}
}
tryDraw(decodeURIComponent(data));
}
</script>
</body>

View File

@ -0,0 +1,49 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>dependencyBrowseTree</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/jstree.min.js"></script>
<script src="tree.data.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/themes/default/style.min.css"/>
<style>
#input-field {
display: inline;
}
#tree_div {
font-family: monospace
}
</style>
</head>
<body>
<h1>Dependencies</h1>
Search: <input type="search" id="input-field" value="">
<div id="tree_div" class="jstree-no-icons"></div>
<script>
$(function() {
$('#tree_div').jstree({
"plugins": ["search", "sort"],
"core": {
"data": tree_data
}
});
var to = false;
$('#input-field').keyup(function() {
if (to) {
clearTimeout(to);
}
to = setTimeout(function() {
var v = $('#input-field').val();
$('#tree_div').jstree(true).search(v);
}, 250);
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,3 @@
package net.virtualvoid.sbt.graph
trait ModuleGraphProtocolCompat

View File

@ -0,0 +1,59 @@
/*
* Copyright 2015 Johannes Rudolph
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.virtualvoid.sbt.graph
package rendering
import com.github.mdr.ascii.layout._
import net.virtualvoid.sbt.graph.DependencyGraphKeys._
import sbt.Keys._
object AsciiGraph {
def asciiGraph(graph: ModuleGraph): String =
Layouter.renderGraph(buildAsciiGraph(graph))
private def buildAsciiGraph(moduleGraph: ModuleGraph): Graph[String] = {
def renderVertex(module: Module): String =
module.id.name + module.extraInfo + "\n" +
module.id.organisation + "\n" +
module.id.version +
module.error.map("\nerror: " + _).getOrElse("") +
module.evictedByVersion.map(_ formatted "\nevicted by: %s").getOrElse("")
val vertices = moduleGraph.nodes.map(renderVertex).toList
val edges = moduleGraph.edges.toList.map { case (from, to) (renderVertex(moduleGraph.module(from)), renderVertex(moduleGraph.module(to))) }
Graph(vertices, edges)
}
def asciiGraphSetttings = Seq[sbt.Def.Setting[_]](
DependencyGraphKeys.asciiGraph := asciiGraph(moduleGraph.value),
dependencyGraph := {
val force = DependencyGraphSettings.shouldForceParser.parsed
val log = streams.value.log
if (force || moduleGraph.value.nodes.size < 15) {
log.info(rendering.AsciiGraph.asciiGraph(moduleGraph.value))
log.info("\n\n")
log.info("Note: The old tree layout is still available by using `dependency-tree`")
}
log.info(rendering.AsciiTree.asciiTree(moduleGraph.value))
if (!force) {
log.info("\n")
log.info("Note: The graph was estimated to be too big to display (> 15 nodes). Use `sbt 'dependency-graph --force'` (with the single quotes) to force graph display.")
}
})
}

View File

@ -0,0 +1,15 @@
package sbt
package dependencygraph
import scala.language.implicitConversions
object DependencyGraphSbtCompat {
object Implicits {
implicit def convertConfig(config: sbt.Configuration): String = config.toString
implicit class RichUpdateConfiguration(val updateConfig: UpdateConfiguration) extends AnyVal {
def withMissingOk(missingOk: Boolean): UpdateConfiguration =
updateConfig.copy(missingOk = missingOk)
}
}
}

View File

@ -0,0 +1,25 @@
package net.virtualvoid.sbt.graph
import java.io.{ ByteArrayInputStream, ByteArrayOutputStream, File }
import java.util.Base64
import sbinary.{ Format, JavaInput, JavaOutput }
import sjsonnew.{ Builder, Unbuilder }
trait ModuleGraphProtocolCompat {
implicit def sjsonNewAndShinyTransformAndTranspileAdapterFactoryModuleImplementation[T](implicit format: Format[T]): sjsonnew.JsonFormat[T] =
new sjsonnew.JsonFormat[T] {
// note, how this is simpler to write than to learn any sjonnew protocol syntax
def write[J](obj: T, builder: Builder[J]): Unit = {
val baos = new ByteArrayOutputStream()
format.writes(new JavaOutput(baos), obj)
val str = Base64.getEncoder.encodeToString(baos.toByteArray)
builder.writeString(str)
}
def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): T = {
val str = unbuilder.readString(jsOpt.get)
val bais = new ByteArrayInputStream(Base64.getDecoder.decode(str))
format.reads(new JavaInput(bais))
}
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright 2017 Johannes Rudolph
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.virtualvoid.sbt.graph
package rendering
object AsciiGraph {
def asciiGraphSetttings = Seq.empty[sbt.Def.Setting[_]]
}

View File

@ -0,0 +1,6 @@
package sbt
package dependencygraph
object DependencyGraphSbtCompat {
object Implicits
}

View File

@ -0,0 +1,113 @@
/*
* Copyright 2014 Johannes Rudolph
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.virtualvoid.sbt.graph
import sbt._
trait DependencyGraphKeys {
val asString = TaskKey[String]("asString", "Provides the string value for the task it is scoped for")
val printToConsole = TaskKey[Unit]("printToConsole", "Prints the tasks value to the console")
val toFile = InputKey[File]("toFile", "Writes the task value to the given file")
val dependencyGraphMLFile = SettingKey[File](
"dependency-graph-ml-file",
"The location the graphml file should be generated at")
val dependencyGraphML = TaskKey[File](
"dependency-graph-ml",
"Creates a graphml file containing the dependency-graph for a project")
val dependencyDotFile = SettingKey[File](
"dependency-dot-file",
"The location the dot file should be generated at")
val dependencyDotNodeLabel = SettingKey[(String, String, String) String](
"dependency-dot-node-label",
"Returns a formated string of a dependency. Takes organisation, name and version as parameters")
val dependencyDotHeader = SettingKey[String](
"dependency-dot-header",
"The header of the dot file. (e.g. to set your preferred node shapes)")
val dependencyDot = TaskKey[File](
"dependency-dot",
"Creates a dot file containing the dependency-graph for a project")
val dependencyDotString = TaskKey[String](
"dependency-dot-string",
"Creates a String containing the dependency-graph for a project in dot format")
val dependencyBrowseGraphTarget = SettingKey[File](
"dependency-browse-graph-target",
"The location dependency browse graph files should be put.")
val dependencyBrowseGraphHTML = TaskKey[URI](
"dependency-browse-graph-html",
"Creates an HTML page that can be used to view the graph.")
val dependencyBrowseGraph = TaskKey[URI](
"dependency-browse-graph",
"Opens an HTML page that can be used to view the graph.")
val dependencyBrowseTreeTarget = SettingKey[File](
"dependency-browse-tree-target",
"The location dependency browse tree files should be put.")
val dependencyBrowseTreeHTML = TaskKey[URI](
"dependency-browse-tree-html",
"Creates an HTML page that can be used to view the dependency tree")
val dependencyBrowseTree = TaskKey[URI](
"dependency-browse-tree",
"Opens an HTML page that can be used to view the dependency tree")
val moduleGraph = TaskKey[ModuleGraph](
"module-graph",
"The dependency graph for a project")
val moduleGraphIvyReport = TaskKey[ModuleGraph](
"module-graph-ivy-report",
"The dependency graph for a project as generated from an Ivy Report XML")
val moduleGraphSbt = TaskKey[ModuleGraph](
"module-graph-sbt",
"The dependency graph for a project as generated from SBT data structures.")
val asciiGraph = TaskKey[String](
"dependency-graph-string",
"Returns a string containing the ascii representation of the dependency graph for a project")
val dependencyGraph = InputKey[Unit](
"dependency-graph",
"Prints the ascii graph to the console")
val asciiTree = TaskKey[String](
"dependency-tree-string",
"Returns a string containing an ascii tree representation of the dependency graph for a project")
val dependencyTree = TaskKey[Unit](
"dependency-tree",
"Prints an ascii tree of all the dependencies to the console")
val dependencyList = TaskKey[Unit](
"dependency-list",
"Prints a list of all dependencies to the console")
val dependencyStats = TaskKey[Unit](
"dependency-stats",
"Prints statistics for all dependencies to the console")
val ivyReportFunction = TaskKey[String File](
"ivy-report-function",
"A function which returns the file containing the ivy report from the ivy cache for a given configuration")
val ivyReport = TaskKey[File](
"ivy-report",
"A task which returns the location of the ivy report file for a given configuration (default `compile`).")
val filterScalaLibrary = SettingKey[Boolean](
"filter-scala-library",
"Specifies if scala dependency should be filtered in dependency-* output")
val licenseInfo = TaskKey[Unit](
"dependency-license-info",
"Aggregates and shows information about the licenses of dependencies")
// internal
private[graph] val ignoreMissingUpdate = TaskKey[UpdateReport]("dependencyUpdate", "sbt-dependency-graph version of update")
private[graph] val moduleGraphStore = TaskKey[ModuleGraph]("module-graph-store", "The stored module-graph from the last run")
val whatDependsOn = InputKey[String]("what-depends-on", "Shows information about what depends on the given module")
private[graph] val crossProjectId = SettingKey[ModuleID]("dependency-graph-cross-project-id")
}
object DependencyGraphKeys extends DependencyGraphKeys

View File

@ -0,0 +1,28 @@
/*
* Copyright 2011, 2012 Johannes Rudolph
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.virtualvoid.sbt.graph
import sbt._
import Keys._
object DependencyGraphPlugin extends AutoPlugin {
object autoImport extends DependencyGraphKeys
override def projectSettings: Seq[Def.Setting[_]] = DependencyGraphSettings.graphSettings
override def trigger: PluginTrigger = AllRequirements
}

View File

@ -0,0 +1,279 @@
/*
* Copyright 2015 Johannes Rudolph
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.virtualvoid.sbt.graph
import scala.language.reflectiveCalls
import sbt._
import Keys._
import sbt.complete.Parser
import net.virtualvoid.sbt.graph.backend.{ IvyReport, SbtUpdateReport }
import net.virtualvoid.sbt.graph.rendering.{ AsciiGraph, DagreHTML, TreeView }
import net.virtualvoid.sbt.graph.util.IOUtil
import internal.librarymanagement._
import librarymanagement._
import sbt.dependencygraph.SbtAccess
import sbt.dependencygraph.DependencyGraphSbtCompat.Implicits._
import sbt.complete.Parsers
object DependencyGraphSettings {
import DependencyGraphKeys._
import ModuleGraphProtocol._
def graphSettings = baseSettings ++ reportSettings
def baseSettings = Seq(
ivyReportFunction := ivyReportFunctionTask.value,
// disable the cached resolution engine (exposing a scoped `ivyModule` used directly by `updateTask`), as it
// generates artificial module descriptors which are internal to sbt, making it hard to reconstruct the
// dependency tree
updateOptions in ignoreMissingUpdate := updateOptions.value.withCachedResolution(false),
ivyConfiguration in ignoreMissingUpdate :=
// inTask will make sure the new definition will pick up `updateOptions in ignoreMissingUpdate`
SbtAccess.inTask(ignoreMissingUpdate, Classpaths.mkIvyConfiguration).value,
ivyModule in ignoreMissingUpdate := {
// concatenating & inlining ivySbt & ivyModule default task implementations, as `SbtAccess.inTask` does
// NOT correctly force the scope when applied to `TaskKey.toTask` instances (as opposed to raw
// implementations like `Classpaths.mkIvyConfiguration` or `Classpaths.updateTask`)
val is = new IvySbt((ivyConfiguration in ignoreMissingUpdate).value)
new is.Module(moduleSettings.value)
},
// don't fail on missing dependencies
updateConfiguration in ignoreMissingUpdate := updateConfiguration.value.withMissingOk(true),
ignoreMissingUpdate :=
// inTask will make sure the new definition will pick up `ivyModule/updateConfiguration in ignoreMissingUpdate`
SbtAccess.inTask(ignoreMissingUpdate, Classpaths.updateTask).value,
filterScalaLibrary in Global := true)
def reportSettings =
Seq(Compile, Test, IntegrationTest, Runtime, Provided, Optional).flatMap(ivyReportForConfig)
val renderingAlternatives: Seq[(TaskKey[Unit], ModuleGraph String)] =
Seq(
dependencyTree -> rendering.AsciiTree.asciiTree _,
dependencyList -> rendering.FlatList.render(_.id.idString),
dependencyStats -> rendering.Statistics.renderModuleStatsList _,
licenseInfo -> rendering.LicenseInfo.render _)
def ivyReportForConfig(config: Configuration) = inConfig(config)(
Seq(
ivyReport := { Def.task { ivyReportFunction.value.apply(config.toString) } dependsOn (ignoreMissingUpdate) }.value,
crossProjectId := sbt.CrossVersion(scalaVersion.value, scalaBinaryVersion.value)(projectID.value),
moduleGraphSbt :=
ignoreMissingUpdate.value.configuration(configuration.value).map(report SbtUpdateReport.fromConfigurationReport(report, crossProjectId.value)).getOrElse(ModuleGraph.empty),
moduleGraphIvyReport := IvyReport.fromReportFile(absoluteReportPath(ivyReport.value)),
moduleGraph := {
sbtVersion.value match {
case Version(0, 13, x, _) if x >= 6 moduleGraphSbt.value
case Version(1, _, _, _) moduleGraphSbt.value
}
},
moduleGraph := {
// FIXME: remove busywork
val scalaVersion = Keys.scalaVersion.value
val moduleGraph = DependencyGraphKeys.moduleGraph.value
if (filterScalaLibrary.value) GraphTransformations.ignoreScalaLibrary(scalaVersion, moduleGraph)
else moduleGraph
},
moduleGraphStore := (moduleGraph storeAs moduleGraphStore triggeredBy moduleGraph).value,
// browse
dependencyBrowseGraphTarget := { target.value / "browse-dependency-graph" },
dependencyBrowseGraphHTML := browseGraphHTMLTask.value,
dependencyBrowseGraph := openBrowser(dependencyBrowseGraphHTML).value,
dependencyBrowseTreeTarget := { target.value / "browse-dependency-tree" },
dependencyBrowseTreeHTML := browseTreeHTMLTask.value,
dependencyBrowseTree := openBrowser(dependencyBrowseTreeHTML).value,
// dot support
dependencyDotFile := { target.value / "dependencies-%s.dot".format(config.toString) },
dependencyDotString := rendering.DOT.dotGraph(moduleGraph.value, dependencyDotHeader.value, dependencyDotNodeLabel.value, rendering.DOT.AngleBrackets),
dependencyDot := writeToFile(dependencyDotString, dependencyDotFile).value,
dependencyDotHeader :=
"""|digraph "dependency-graph" {
| graph[rankdir="LR"]
| edge [
| arrowtail="none"
| ]""".stripMargin,
dependencyDotNodeLabel := { (organisation: String, name: String, version: String)
"""%s<BR/><B>%s</B><BR/>%s""".format(organisation, name, version)
},
// GraphML support
dependencyGraphMLFile := { target.value / "dependencies-%s.graphml".format(config.toString) },
dependencyGraphML := dependencyGraphMLTask.value,
whatDependsOn := {
val ArtifactPattern(org, name, versionFilter) = artifactPatternParser.parsed
val graph = moduleGraph.value
val modules =
versionFilter match {
case Some(version) ModuleId(org, name, version) :: Nil
case None graph.nodes.filter(m m.id.organisation == org && m.id.name == name).map(_.id)
}
val output =
modules
.map { module
rendering.AsciiTree.asciiTree(GraphTransformations.reverseGraphStartingAt(graph, module))
}
.mkString("\n")
streams.value.log.info(output)
output
},
// deprecated settings
asciiTree := (asString in dependencyTree).value) ++
renderingAlternatives.flatMap((renderingTaskSettings _).tupled) ++
AsciiGraph.asciiGraphSetttings)
def renderingTaskSettings(key: TaskKey[Unit], renderer: ModuleGraph String): Seq[Setting[_]] =
Seq(
asString in key := renderer(moduleGraph.value),
printToConsole in key := streams.value.log.info((asString in key).value),
toFile in key := {
val (targetFile, force) = targetFileAndForceParser.parsed
writeToFile(key.key.label, (asString in key).value, targetFile, force, streams.value)
},
key := (printToConsole in key).value)
def ivyReportFunctionTask = Def.task {
val ivyConfig = Keys.ivyConfiguration.value.asInstanceOf[InlineIvyConfiguration]
val projectID = Keys.projectID.value
val ivyModule = Keys.ivyModule.value
(config: String) {
val org = projectID.organization
val name = crossName(ivyModule)
new File(ivyConfig.resolutionCacheDir.get, s"reports/$org-$name-$config.xml")
}
}
def dependencyGraphMLTask =
Def.task {
val resultFile = dependencyGraphMLFile.value
rendering.GraphML.saveAsGraphML(moduleGraph.value, resultFile.getAbsolutePath)
streams.value.log.info("Wrote dependency graph to '%s'" format resultFile)
resultFile
}
def browseGraphHTMLTask =
Def.task {
val dotGraph = rendering.DOT.dotGraph(moduleGraph.value, dependencyDotHeader.value, dependencyDotNodeLabel.value, rendering.DOT.LabelTypeHtml)
val link = DagreHTML.createLink(dotGraph, target.value)
streams.value.log.info(s"HTML graph written to $link")
link
}
def browseTreeHTMLTask =
Def.task {
val renderedTree = TreeView.createJson(moduleGraph.value)
val link = TreeView.createLink(renderedTree, target.value)
streams.value.log.info(s"HTML tree written to $link")
link
}
def writeToFile(dataTask: TaskKey[String], fileTask: SettingKey[File]) =
Def.task {
val outFile = fileTask.value
IOUtil.writeToFile(dataTask.value, outFile)
streams.value.log.info("Wrote dependency graph to '%s'" format outFile)
outFile
}
def writeToFile(what: String, data: String, targetFile: File, force: Boolean, streams: TaskStreams): File =
if (targetFile.exists && !force)
throw new RuntimeException(s"Target file for $what already exists at ${targetFile.getAbsolutePath}. Use '-f' to override")
else {
IOUtil.writeToFile(data, targetFile)
streams.log.info(s"Wrote $what to '$targetFile'")
targetFile
}
def absoluteReportPath = (file: File) file.getAbsolutePath
def openBrowser(uriKey: TaskKey[URI]) =
Def.task {
val uri = uriKey.value
streams.value.log.info("Opening in browser...")
java.awt.Desktop.getDesktop.browse(uri)
uri
}
case class ArtifactPattern(
organisation: String,
name: String,
version: Option[String])
import sbt.complete.DefaultParsers._
val artifactPatternParser: Def.Initialize[State Parser[ArtifactPattern]] =
resolvedScoped { ctx (state: State)
val graph = loadFromContext(moduleGraphStore, ctx, state) getOrElse ModuleGraph(Nil, Nil)
graph.nodes
.map(_.id)
.groupBy(m (m.organisation, m.name))
.map {
case ((org, name), modules)
val versionParsers: Seq[Parser[Option[String]]] =
modules.map { id
token(Space ~> id.version).?
}
(Space ~> token(org) ~ token(Space ~> name) ~ oneOf(versionParsers)).map {
case ((org, name), version) ArtifactPattern(org, name, version)
}
}
.reduceOption(_ | _).getOrElse {
// If the moduleGraphStore couldn't be loaded because no dependency tree command was run before, we should still provide a parser for the command.
((Space ~> token(StringBasic, "<organization>")) ~ (Space ~> token(StringBasic, "<module>")) ~ (Space ~> token(StringBasic, "<version?>")).?).map {
case ((org, mod), version)
ArtifactPattern(org, mod, version)
}
}
}
val shouldForceParser: Parser[Boolean] = (Space ~> (Parser.literal("-f") | "--force")).?.map(_.isDefined)
val targetFileAndForceParser: Parser[(File, Boolean)] =
Parsers.fileParser(new File(".")) ~ shouldForceParser
// This is to support 0.13.8's InlineConfigurationWithExcludes while not forcing 0.13.8
type HasModule = {
val module: ModuleID
}
def crossName(ivyModule: IvySbt#Module) =
ivyModule.moduleSettings match {
case ic: InlineConfiguration ic.module.name
case hm: HasModule @unchecked if hm.getClass.getName == "sbt.InlineConfigurationWithExcludes" hm.module.name
case _
throw new IllegalStateException("sbt-dependency-graph plugin currently only supports InlineConfiguration of ivy settings (the default in sbt)")
}
val VersionPattern = """(\d+)\.(\d+)\.(\d+)(?:-(.*))?""".r
object Version {
def unapply(str: String): Option[(Int, Int, Int, Option[String])] = str match {
case VersionPattern(major, minor, fix, appendix) Some((major.toInt, minor.toInt, fix.toInt, Option(appendix)))
case _ None
}
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 2011, 2012 Johannes Rudolph
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.virtualvoid.sbt.graph
object GraphTransformations {
def reverseGraphStartingAt(graph: ModuleGraph, root: ModuleId): ModuleGraph = {
val deps = graph.reverseDependencyMap
def visit(module: ModuleId, visited: Set[ModuleId]): Seq[(ModuleId, ModuleId)] =
if (visited(module))
Nil
else
deps.get(module) match {
case Some(deps)
deps.flatMap { to
(module, to.id) +: visit(to.id, visited + module)
}
case None Nil
}
val edges = visit(root, Set.empty)
val nodes = edges.foldLeft(Set.empty[ModuleId])((set, edge) set + edge._1 + edge._2).map(graph.module)
ModuleGraph(nodes.toSeq, edges)
}
def ignoreScalaLibrary(scalaVersion: String, graph: ModuleGraph): ModuleGraph = {
def isScalaLibrary(m: Module) = isScalaLibraryId(m.id)
def isScalaLibraryId(id: ModuleId) = id.organisation == "org.scala-lang" && id.name == "scala-library"
def dependsOnScalaLibrary(m: Module): Boolean =
graph.dependencyMap(m.id).exists(isScalaLibrary)
def addScalaLibraryAnnotation(m: Module): Module = {
if (dependsOnScalaLibrary(m))
m.copy(extraInfo = m.extraInfo + " [S]")
else
m
}
val newNodes = graph.nodes.map(addScalaLibraryAnnotation).filterNot(isScalaLibrary)
val newEdges = graph.edges.filterNot(e isScalaLibraryId(e._2))
ModuleGraph(newNodes, newEdges)
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright 2015 Johannes Rudolph
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.virtualvoid.sbt.graph
import java.io.File
import net.virtualvoid.sbt.graph.backend.IvyReport
object Main extends App {
def die(msg: String): Nothing = {
println(msg)
sys.exit(1)
}
def usage: String =
"Usage: <ivy-report-file> <output-file>"
val reportFile = args.lift(0).filter(f new File(f).exists).getOrElse(die(usage))
val outputFile = args.lift(1).getOrElse(die(usage))
val graph = IvyReport.fromReportFile(reportFile)
rendering.GraphML.saveAsGraphML(graph, outputFile)
}

View File

@ -0,0 +1,61 @@
/*
* Copyright 2015 Johannes Rudolph
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.virtualvoid.sbt.graph
package backend
import scala.xml.{ NodeSeq, Document, Node }
import scala.xml.parsing.ConstructingParser
object IvyReport {
def fromReportFile(ivyReportFile: String): ModuleGraph =
fromReportXML(loadXML(ivyReportFile))
def fromReportXML(doc: Document): ModuleGraph = {
def edgesForModule(id: ModuleId, revision: NodeSeq): Seq[Edge] =
for {
caller revision \ "caller"
callerModule = moduleIdFromElement(caller, caller.attribute("callerrev").get.text)
} yield (moduleIdFromElement(caller, caller.attribute("callerrev").get.text), id)
val moduleEdges: Seq[(Module, Seq[Edge])] = for {
mod doc \ "dependencies" \ "module"
revision mod \ "revision"
rev = revision.attribute("name").get.text
moduleId = moduleIdFromElement(mod, rev)
module = Module(
moduleId,
(revision \ "license").headOption.flatMap(_.attribute("name")).map(_.text),
evictedByVersion = (revision \ "evicted-by").headOption.flatMap(_.attribute("rev").map(_.text)),
error = revision.attribute("error").map(_.text))
} yield (module, edgesForModule(moduleId, revision))
val (nodes, edges) = moduleEdges.unzip
val info = (doc \ "info").head
def infoAttr(name: String): String =
info.attribute(name).getOrElse(throw new IllegalArgumentException("Missing attribute " + name)).text
val rootModule = Module(ModuleId(infoAttr("organisation"), infoAttr("module"), infoAttr("revision")))
ModuleGraph(rootModule +: nodes, edges.flatten)
}
private def moduleIdFromElement(element: Node, version: String): ModuleId =
ModuleId(element.attribute("organisation").get.text, element.attribute("name").get.text, version)
private def loadXML(ivyReportFile: String) =
ConstructingParser.fromSource(io.Source.fromFile(ivyReportFile), preserveWS = false).document()
}

View File

@ -0,0 +1,56 @@
/*
* Copyright 2015 Johannes Rudolph
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.virtualvoid.sbt.graph
package backend
import scala.language.implicitConversions
import scala.language.reflectiveCalls
import sbt._
object SbtUpdateReport {
type OrganizationArtifactReport = {
def modules: Seq[ModuleReport]
}
def fromConfigurationReport(report: ConfigurationReport, rootInfo: sbt.ModuleID): ModuleGraph = {
implicit def id(sbtId: sbt.ModuleID): ModuleId = ModuleId(sbtId.organization, sbtId.name, sbtId.revision)
def moduleEdges(orgArt: OrganizationArtifactReport): Seq[(Module, Seq[Edge])] = {
val chosenVersion = orgArt.modules.find(!_.evicted).map(_.module.revision)
orgArt.modules.map(moduleEdge(chosenVersion))
}
def moduleEdge(chosenVersion: Option[String])(report: ModuleReport): (Module, Seq[Edge]) = {
val evictedByVersion = if (report.evicted) chosenVersion else None
val jarFile = report.artifacts.find(_._1.`type` == "jar").orElse(report.artifacts.find(_._1.extension == "jar")).map(_._2)
(
Module(
id = report.module,
license = report.licenses.headOption.map(_._1),
evictedByVersion = evictedByVersion,
jarFile = jarFile,
error = report.problem),
report.callers.map(caller Edge(caller.caller, report.module)))
}
val (nodes, edges) = report.details.flatMap(moduleEdges).unzip
val root = Module(rootInfo)
ModuleGraph(root +: nodes, edges.flatten)
}
}

View File

@ -0,0 +1,79 @@
/*
* Copyright 2015 Johannes Rudolph
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.virtualvoid.sbt.graph
import java.io.File
import sbinary.Format
import scala.collection.mutable.{ HashMap, MultiMap, Set }
case class ModuleId(
organisation: String,
name: String,
version: String) {
def idString: String = organisation + ":" + name + ":" + version
}
case class Module(
id: ModuleId,
license: Option[String] = None,
extraInfo: String = "",
evictedByVersion: Option[String] = None,
jarFile: Option[File] = None,
error: Option[String] = None) {
def hadError: Boolean = error.isDefined
def isUsed: Boolean = !isEvicted
def isEvicted: Boolean = evictedByVersion.isDefined
}
object ModuleGraph {
val empty = ModuleGraph(Seq.empty, Seq.empty)
}
case class ModuleGraph(nodes: Seq[Module], edges: Seq[Edge]) {
lazy val modules: Map[ModuleId, Module] =
nodes.map(n (n.id, n)).toMap
def module(id: ModuleId): Module = modules(id)
lazy val dependencyMap: Map[ModuleId, Seq[Module]] =
createMap(identity)
lazy val reverseDependencyMap: Map[ModuleId, Seq[Module]] =
createMap { case (a, b) (b, a) }
def createMap(bindingFor: ((ModuleId, ModuleId)) (ModuleId, ModuleId)): Map[ModuleId, Seq[Module]] = {
val m = new HashMap[ModuleId, Set[Module]] with MultiMap[ModuleId, Module]
edges.foreach { entry
val (f, t) = bindingFor(entry)
m.addBinding(f, module(t))
}
m.toMap.mapValues(_.toSeq.sortBy(_.id.idString)).withDefaultValue(Nil)
}
def roots: Seq[Module] =
nodes.filter(n !edges.exists(_._2 == n.id)).sortBy(_.id.idString)
}
object ModuleGraphProtocol extends ModuleGraphProtocolCompat {
import sbinary.DefaultProtocol._
implicit def seqFormat[T: Format]: Format[Seq[T]] = wrap[Seq[T], List[T]](_.toList, _.toSeq)
implicit val ModuleIdFormat: Format[ModuleId] = asProduct3(ModuleId)(ModuleId.unapply(_).get)
implicit val ModuleFormat: Format[Module] = asProduct6(Module)(Module.unapply(_).get)
implicit val ModuleGraphFormat: Format[ModuleGraph] = asProduct2(ModuleGraph.apply _)(ModuleGraph.unapply(_).get)
}

View File

@ -0,0 +1,22 @@
/*
* Copyright 2015 Johannes Rudolph
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.virtualvoid.sbt
package object graph {
type Edge = (ModuleId, ModuleId)
def Edge(from: ModuleId, to: ModuleId): Edge = from -> to
}

View File

@ -0,0 +1,39 @@
/*
* Copyright 2015 Johannes Rudolph
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.virtualvoid.sbt.graph
package rendering
import util.AsciiTreeLayout
import util.ConsoleUtils._
object AsciiTree {
def asciiTree(graph: ModuleGraph): String = {
val deps = graph.dependencyMap
// there should only be one root node (the project itself)
val roots = graph.roots
roots.map { root
AsciiTreeLayout.toAscii[Module](root, node deps.getOrElse(node.id, Seq.empty[Module]), displayModule)
}.mkString("\n")
}
def displayModule(module: Module): String =
red(module.id.idString +
module.extraInfo +
module.error.map(" (error: " + _ + ")").getOrElse("") +
module.evictedByVersion.map(_ formatted " (evicted by: %s)").getOrElse(""), module.hadError)
}

View File

@ -0,0 +1,85 @@
/*
* Copyright 2015 Johannes Rudolph
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.virtualvoid.sbt.graph
package rendering
object DOT {
val EvictedStyle = "stroke-dasharray: 5,5"
def dotGraph(
graph: ModuleGraph,
dotHead: String,
nodeFormation: (String, String, String) String,
labelRendering: HTMLLabelRendering): String = {
val nodes = {
for (n graph.nodes) yield {
val style = if (n.isEvicted) EvictedStyle else ""
val label = nodeFormation(n.id.organisation, n.id.name, n.id.version)
""" "%s"[%s style="%s"]""".format(
n.id.idString,
labelRendering.renderLabel(label),
style)
}
}.mkString("\n")
def originWasEvicted(edge: Edge): Boolean = graph.module(edge._1).isEvicted
def targetWasEvicted(edge: Edge): Boolean = graph.module(edge._2).isEvicted
// add extra edges from evicted to evicted-by module
val evictedByEdges: Seq[Edge] =
graph.nodes.filter(_.isEvicted).map(m Edge(m.id, m.id.copy(version = m.evictedByVersion.get)))
// remove edges to new evicted-by module which is now replaced by a chain
// dependend -> [evicted] -> dependee
val evictionTargetEdges =
graph.edges.filter(targetWasEvicted).map {
case (from, evicted) (from, evicted.copy(version = graph.module(evicted).evictedByVersion.get))
}.toSet
val filteredEdges =
graph.edges
.filterNot(e originWasEvicted(e) || evictionTargetEdges(e)) ++ evictedByEdges
val edges = {
for (e filteredEdges) yield {
val extra = if (graph.module(e._1).isEvicted)
s""" [label="Evicted By" style="$EvictedStyle"]""" else ""
""" "%s" -> "%s"%s""".format(e._1.idString, e._2.idString, extra)
}
}.mkString("\n")
"%s\n%s\n%s\n}".format(dotHead, nodes, edges)
}
sealed trait HTMLLabelRendering {
def renderLabel(labelText: String): String
}
/**
* Render HTML labels in Angle brackets as defined at http://graphviz.org/content/node-shapes#html
*/
case object AngleBrackets extends HTMLLabelRendering {
def renderLabel(labelText: String): String = s"label=<$labelText>"
}
/**
* Render HTML labels with `labelType="html"` and label content in double quotes as supported by
* dagre-d3
*/
case object LabelTypeHtml extends HTMLLabelRendering {
def renderLabel(labelText: String): String = s"""labelType="html" label="$labelText""""
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2015 Johannes Rudolph
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.virtualvoid.sbt.graph
package rendering
import java.io.File
import java.net.{ URLEncoder, URI }
import net.virtualvoid.sbt.graph.util.IOUtil
object DagreHTML {
def createLink(dotGraph: String, targetDirectory: File): URI = {
targetDirectory.mkdirs()
val graphHTML = new File(targetDirectory, "graph.html")
IOUtil.saveResource("graph.html", graphHTML)
IOUtil.writeToFile(dotGraph, new File(targetDirectory, "dependencies.dot"))
val graphString =
URLEncoder.encode(dotGraph, "utf8")
.replaceAllLiterally("+", "%20")
IOUtil.writeToFile(s"""data = "$graphString";""", new File(targetDirectory, "dependencies.dot.js"))
new URI(graphHTML.toURI.toString)
}
}

View File

@ -0,0 +1,28 @@
/*
* Copyright 2016 Johannes Rudolph
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.virtualvoid.sbt.graph
package rendering
object FlatList {
def render(display: Module String)(graph: ModuleGraph): String =
graph.modules.values.toSeq
.distinct
.filterNot(_.isEvicted)
.sortBy(m (m.id.organisation, m.id.name))
.map(display)
.mkString("\n")
}

View File

@ -0,0 +1,48 @@
/*
* Copyright 2015 Johannes Rudolph
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.virtualvoid.sbt.graph.rendering
import net.virtualvoid.sbt.graph.ModuleGraph
import scala.xml.XML
object GraphML {
def saveAsGraphML(graph: ModuleGraph, outputFile: String): Unit = {
val nodesXml =
for (n graph.nodes)
yield <node id={ n.id.idString }><data key="d0">
<y:ShapeNode>
<y:NodeLabel>{ n.id.idString }</y:NodeLabel>
</y:ShapeNode>
</data></node>
val edgesXml =
for (e graph.edges)
yield <edge source={ e._1.idString } target={ e._2.idString }/>
val xml =
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">
<key for="node" id="d0" yfiles.type="nodegraphics"/>
<graph id="Graph" edgedefault="undirected">
{ nodesXml }
{ edgesXml }
</graph>
</graphml>
XML.save(outputFile, xml)
}
}

View File

@ -0,0 +1,12 @@
package net.virtualvoid.sbt.graph.rendering
import net.virtualvoid.sbt.graph.ModuleGraph
object LicenseInfo {
def render(graph: ModuleGraph): String =
graph.nodes.filter(_.isUsed).groupBy(_.license).toSeq.sortBy(_._1).map {
case (license, modules)
license.getOrElse("No license specified") + "\n" +
modules.map(_.id.idString formatted "\t %s").mkString("\n")
}.mkString("\n\n")
}

View File

@ -0,0 +1,71 @@
/*
* Copyright 2016 Johannes Rudolph
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.virtualvoid.sbt.graph
package rendering
object Statistics {
def renderModuleStatsList(graph: ModuleGraph): String = {
case class ModuleStats(
id: ModuleId,
numDirectDependencies: Int,
numTransitiveDependencies: Int,
selfSize: Option[Long],
transitiveSize: Long,
transitiveDependencyStats: Map[ModuleId, ModuleStats]) {
def transitiveStatsWithSelf: Map[ModuleId, ModuleStats] = transitiveDependencyStats + (id -> this)
}
def statsFor(moduleId: ModuleId): ModuleStats = {
val directDependencies = graph.dependencyMap(moduleId).filterNot(_.isEvicted).map(_.id)
val dependencyStats = directDependencies.map(statsFor).flatMap(_.transitiveStatsWithSelf).toMap
val selfSize = graph.module(moduleId).jarFile.filter(_.exists).map(_.length)
val numDirectDependencies = directDependencies.size
val numTransitiveDependencies = dependencyStats.size
val transitiveSize = selfSize.getOrElse(0L) + dependencyStats.map(_._2.selfSize.getOrElse(0L)).sum
ModuleStats(moduleId, numDirectDependencies, numTransitiveDependencies, selfSize, transitiveSize, dependencyStats)
}
def format(stats: ModuleStats): String = {
import stats._
def mb(bytes: Long): Double = bytes.toDouble / 1000000
val selfSize =
stats.selfSize match {
case Some(size) f"${mb(size)}%7.3f"
case None "-------"
}
f"${mb(transitiveSize)}%7.3f MB $selfSize MB $numTransitiveDependencies%4d $numDirectDependencies%4d ${id.idString}%s"
}
val allStats =
graph.roots.flatMap(r statsFor(r.id).transitiveStatsWithSelf).toMap.values.toSeq
.sortBy(s (-s.transitiveSize, -s.numTransitiveDependencies))
val header = " TotSize JarSize #TDe #Dep Module\n"
header +
allStats.map(format).mkString("\n") +
"""
|
|Columns are
| - Jar-Size including dependencies
| - Jar-Size
| - Number of transitive dependencies
| - Number of direct dependencies
| - ModuleID""".stripMargin
}
}

View File

@ -0,0 +1,42 @@
package net.virtualvoid.sbt.graph.rendering
import java.io.File
import java.net.URI
import net.virtualvoid.sbt.graph.util.IOUtil
import net.virtualvoid.sbt.graph.{ Module, ModuleGraph }
import scala.util.parsing.json.{ JSONArray, JSONObject }
object TreeView {
def createJson(graph: ModuleGraph): String = {
val trees = graph.roots
.map(module processSubtree(graph, module))
.toList
JSONArray(trees).toString
}
def createLink(graphJson: String, targetDirectory: File): URI = {
targetDirectory.mkdirs()
val graphHTML = new File(targetDirectory, "tree.html")
IOUtil.saveResource("tree.html", graphHTML)
IOUtil.writeToFile(graphJson, new File(targetDirectory, "tree.json"))
IOUtil.writeToFile(s"tree_data = $graphJson;", new File(targetDirectory, "tree.data.js"))
new URI(graphHTML.toURI.toString)
}
private def processSubtree(graph: ModuleGraph, module: Module): JSONObject = {
val children = graph.dependencyMap
.getOrElse(module.id, List())
.map(module processSubtree(graph, module))
.toList
moduleAsJson(module, children)
}
private def moduleAsJson(module: Module, children: List[JSONObject]): JSONObject = {
val eviction = module.evictedByVersion.map(version s" (evicted by $version)").getOrElse("")
val error = module.error.map(err s" (errors: $err)").getOrElse("")
val text = module.id.idString + eviction + error
JSONObject(Map("text" -> text, "children" -> JSONArray(children)))
}
}

View File

@ -0,0 +1,62 @@
/* sbt -- Simple Build Tool
* Copyright 2011 Mark Harrah, Eugene Yokota
*
* Copied from sbt 0.12 source code
*/
package net.virtualvoid.sbt.graph.util
import sbt.dependencygraph.SbtAccess
object AsciiTreeLayout {
// [info] foo
// [info] +-bar
// [info] | +-baz
// [info] |
// [info] +-quux
def toAscii[A](
top: A,
children: A Seq[A],
display: A String,
maxColumn: Int = defaultColumnSize): String = {
val twoSpaces = " " + " " // prevent accidentally being converted into a tab
def limitLine(s: String): String =
if (s.length > maxColumn) s.slice(0, maxColumn - 2) + ".."
else s
def insertBar(s: String, at: Int): String =
if (at < s.length)
s.slice(0, at) +
(s(at).toString match {
case " " "|"
case x x
}) +
s.slice(at + 1, s.length)
else s
def toAsciiLines(node: A, level: Int, parents: Set[A]): Vector[String] =
if (parents contains node) // cycle
Vector(limitLine((twoSpaces * level) + "#-" + display(node) + " (cycle)"))
else {
val line = limitLine((twoSpaces * level) + (if (level == 0) "" else "+-") + display(node))
val cs = Vector(children(node): _*)
val childLines = cs map {
toAsciiLines(_, level + 1, parents + node)
}
val withBar = childLines.zipWithIndex flatMap {
case (lines, pos) if pos < (cs.size - 1) lines map {
insertBar(_, 2 * (level + 1))
}
case (lines, pos)
if (lines.last.trim != "") lines ++ Vector(twoSpaces * (level + 1))
else lines
}
line +: withBar
}
toAsciiLines(top, 0, Set.empty).mkString("\n")
}
def defaultColumnSize: Int = {
val termWidth = SbtAccess.getTerminalWidth
if (termWidth > 20) termWidth - 8
else 80 // ignore termWidth
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright 2015 Johannes Rudolph
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.virtualvoid.sbt.graph.util
import sbt.ConsoleLogger
object ConsoleUtils {
def red(str: String, doRed: Boolean): String =
if (ConsoleLogger.formatEnabled && doRed)
Console.RED + str + Console.RESET
else
str
}

View File

@ -0,0 +1,55 @@
/*
* Copyright 2015 Johannes Rudolph
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.virtualvoid.sbt.graph.util
import java.io.{ OutputStream, InputStream, FileOutputStream, File }
import java.nio.charset.Charset
import scala.annotation.tailrec
object IOUtil {
val utf8 = Charset.forName("utf8")
def writeToFile(string: String, file: File): Unit =
sbt.IO.write(file, string, utf8)
def saveResource(resourcePath: String, to: File): Unit = {
val is = getClass.getClassLoader.getResourceAsStream(resourcePath)
require(is ne null, s"Couldn't load '$resourcePath' from classpath.")
val fos = new FileOutputStream(to)
try copy(is, fos)
finally {
is.close()
fos.close()
}
}
def copy(from: InputStream, to: OutputStream): Unit = {
val buffer = new Array[Byte](65536)
@tailrec def rec(): Unit = {
val read = from.read(buffer)
if (read > 0) {
to.write(buffer, 0, read)
rec()
} else if (read == 0)
throw new IllegalStateException("InputStream.read returned 0")
}
rec()
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright 2015 Johannes Rudolph
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sbt
package dependencygraph
import Def._
/** Accessors to private[sbt] symbols. */
object SbtAccess {
val unmanagedScalaInstanceOnly = Defaults.unmanagedScalaInstanceOnly
def getTerminalWidth: Int = sbt.internal.util.JLine.usingTerminal(_.getWidth)
def inTask[T](t: Scoped, i: Initialize[T]): Initialize[T] = _root_.sbt.inTask(t, i)
}

View File

@ -0,0 +1,17 @@
scalaVersion := "2.12.9"
libraryDependencies += "org.slf4j" % "slf4j-api" % "1.7.28"
updateOptions := updateOptions.value.withCachedResolution(true)
TaskKey[Unit]("check") := {
val report = (ivyReport in Test).value
val graph = (asciiTree in Test).value
def sanitize(str: String): String = str.split('\n').drop(1).mkString("\n")
val expectedGraph =
"""default:cachedresolution_2.12:0.1.0-SNAPSHOT
| +-org.slf4j:slf4j-api:1.7.28
| """.stripMargin
require(sanitize(graph) == sanitize(expectedGraph), "Graph for report %s was '\n%s' but should have been '\n%s'" format (report, sanitize(graph), sanitize(expectedGraph)))
()
}

View File

@ -0,0 +1 @@
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % sys.props("project.version"))

View File

@ -0,0 +1,25 @@
scalaVersion := "2.9.2"
libraryDependencies ++= Seq(
"org.slf4j" % "slf4j-api" % "1.7.2",
"ch.qos.logback" % "logback-classic" % "1.0.7"
)
TaskKey[Unit]("check") := {
val report = (ivyReport in Test).value
val graph = (asciiTree in Test).value
def sanitize(str: String): String = str.split('\n').drop(1).map(_.trim).mkString("\n")
val expectedGraph =
"""default:default-e95e05_2.9.2:0.1-SNAPSHOT [S]
| +-ch.qos.logback:logback-classic:1.0.7
| | +-ch.qos.logback:logback-core:1.0.7
| | +-org.slf4j:slf4j-api:1.6.6 (evicted by: 1.7.2)
| | +-org.slf4j:slf4j-api:1.7.2
| |
| +-org.slf4j:slf4j-api:1.7.2
| """.stripMargin
IO.writeLines(file("/tmp/blib"), sanitize(graph).split("\n"))
IO.writeLines(file("/tmp/blub"), sanitize(expectedGraph).split("\n"))
require(sanitize(graph) == sanitize(expectedGraph), "Graph for report %s was '\n%s' but should have been '\n%s'" format (report, sanitize(graph), sanitize(expectedGraph)))
()
}

View File

@ -0,0 +1,25 @@
scalaVersion := "2.9.1"
resolvers += "typesafe maven" at "https://repo.typesafe.com/typesafe/maven-releases/"
libraryDependencies ++= Seq(
"com.codahale" % "jerkson_2.9.1" % "0.5.0"
)
TaskKey[Unit]("check") := {
val report = (ivyReport in Test).value
val graph = (asciiTree in Test).value
def sanitize(str: String): String = str.split('\n').drop(1).map(_.trim).mkString("\n")
val expectedGraph =
"""default:default-dbc48d_2.9.2:0.1-SNAPSHOT [S]
| +-com.codahale:jerkson_2.9.1:0.5.0 [S]
| +-org.codehaus.jackson:jackson-core-asl:1.9.11
| +-org.codehaus.jackson:jackson-mapper-asl:1.9.11
| +-org.codehaus.jackson:jackson-core-asl:1.9.11
| """.stripMargin
IO.writeLines(file("/tmp/blib"), sanitize(graph).split("\n"))
IO.writeLines(file("/tmp/blub"), sanitize(expectedGraph).split("\n"))
require(sanitize(graph) == sanitize(expectedGraph), "Graph for report %s was '\n%s' but should have been '\n%s'" format (report, sanitize(graph), sanitize(expectedGraph)))
()
}

View File

@ -0,0 +1 @@
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % sys.props("project.version"))

View File

@ -0,0 +1,17 @@
scalaVersion := "2.9.2"
libraryDependencies +=
"at.blub" % "blib" % "1.2.3" % "test"
TaskKey[Unit]("check") := {
val report = (ivyReport in Test).value
val graph = (asciiTree in Test).value
def sanitize(str: String): String = str.split('\n').drop(1).mkString("\n")
val expectedGraph =
"""default:default-91180e_2.9.2:0.1-SNAPSHOT
| +-%sat.blub:blib:1.2.3 (error: not found)%s
| """.stripMargin.format(scala.Console.RED, scala.Console.RESET)
require(sanitize(graph) == sanitize(expectedGraph), "Graph for report %s was '\n%s' but should have been '\n%s'" format (report, sanitize(graph), sanitize(expectedGraph)))
()
}

View File

@ -0,0 +1,68 @@
import collection.mutable.ListBuffer
import net.virtualvoid.sbt.graph.DependencyGraphKeys.dependencyDot
import scala.collection.mutable.ListBuffer
def defaultSettings =
Seq(
scalaVersion := "2.9.2",
version := "0.1-SNAPSHOT"
)
lazy val justATransiviteDependencyEndpointProject =
Project("just-a-transitive-dependency-endpoint", file("a"))
.settings(defaultSettings: _*)
lazy val justATransitiveDependencyProject =
Project("just-a-transitive-dependency", file("b"))
.settings(defaultSettings: _*)
.dependsOn(justATransiviteDependencyEndpointProject)
lazy val justADependencyProject =
Project("just-a-dependency", file("c"))
.settings(defaultSettings: _*)
lazy val test_project =
Project("test-dot-file-generation", file("d"))
.settings(defaultSettings: _*)
.settings(
TaskKey[Unit]("check") := {
val dotFile = (dependencyDot in Compile).value
val expectedGraph =
"""digraph "dependency-graph" {
| graph[rankdir="LR"]
| edge [
| arrowtail="none"
| ]
| "test-dot-file-generation:test-dot-file-generation_2.9.2:0.1-SNAPSHOT"[label=<test-dot-file-generation<BR/><B>test-dot-file-generation_2.9.2</B><BR/>0.1-SNAPSHOT> style=""]
| "just-a-transitive-dependency:just-a-transitive-dependency_2.9.2:0.1-SNAPSHOT"[label=<just-a-transitive-dependency<BR/><B>just-a-transitive-dependency_2.9.2</B><BR/>0.1-SNAPSHOT> style=""]
| "just-a-transitive-dependency-endpoint:just-a-transitive-dependency-endpoint_2.9.2:0.1-SNAPSHOT"[label=<just-a-transitive-dependency-endpoint<BR/><B>just-a-transitive-dependency-endpoint_2.9.2</B><BR/>0.1-SNAPSHOT> style=""]
| "just-a-dependency:just-a-dependency_2.9.2:0.1-SNAPSHOT"[label=<just-a-dependency<BR/><B>just-a-dependency_2.9.2</B><BR/>0.1-SNAPSHOT> style=""]
| "test-dot-file-generation:test-dot-file-generation_2.9.2:0.1-SNAPSHOT" -> "just-a-transitive-dependency:just-a-transitive-dependency_2.9.2:0.1-SNAPSHOT"
| "just-a-transitive-dependency:just-a-transitive-dependency_2.9.2:0.1-SNAPSHOT" -> "just-a-transitive-dependency-endpoint:just-a-transitive-dependency-endpoint_2.9.2:0.1-SNAPSHOT"
| "test-dot-file-generation:test-dot-file-generation_2.9.2:0.1-SNAPSHOT" -> "just-a-dependency:just-a-dependency_2.9.2:0.1-SNAPSHOT"
|}
""".stripMargin
val graph : String = scala.io.Source.fromFile(dotFile.getAbsolutePath).mkString
val errors = compareByLine(graph, expectedGraph)
require(errors.isEmpty , errors.mkString("\n"))
()
}
)
.dependsOn(justADependencyProject, justATransitiveDependencyProject)
def compareByLine(got : String, expected : String) : Seq[String] = {
val errors = ListBuffer[String]()
got.split("\n").zip(expected.split("\n").toSeq).zipWithIndex.foreach { case((got_line : String, expected_line : String), i : Int) =>
if(got_line != expected_line) {
errors.append(
"""not matching lines at line %s
|expected: %s
|got: %s
|""".stripMargin.format(i,expected_line, got_line))
}
}
errors
}

View File

@ -0,0 +1,2 @@
> project test-dot-file-generation
> check

View File

@ -0,0 +1,26 @@
scalaVersion := "2.12.6"
organization := "org.example"
name := "blubber"
version := "0.1"
libraryDependencies ++= Seq(
"com.codahale" % "jerkson_2.9.1" % "0.5.0"
)
TaskKey[Unit]("check") := {
val candidates = "tree list stats licenses".split(' ').map(_.trim)
candidates.foreach { c =>
val expected = new File(s"expected/$c.txt")
val actual = new File(s"target/$c.txt")
import sys.process._
val exit = s"diff -U3 ${expected.getPath} ${actual.getPath}".!
require(exit == 0, s"Diff was non-zero for ${actual.getName}")
}
//require(sanitize(graph) == sanitize(expectedGraph), "Graph for report %s was '\n%s' but should have been '\n%s'" format (report, sanitize(graph), sanitize(expectedGraph)))
()
}

View File

@ -0,0 +1,9 @@
No license specified
org.example:blubber_2.12:0.1
The Apache Software License, Version 2.0
org.codehaus.jackson:jackson-mapper-asl:1.9.11
org.codehaus.jackson:jackson-core-asl:1.9.11
The MIT License
com.codahale:jerkson_2.9.1:0.5.0

View File

@ -0,0 +1,4 @@
com.codahale:jerkson_2.9.1:0.5.0
org.codehaus.jackson:jackson-core-asl:1.9.11
org.codehaus.jackson:jackson-mapper-asl:1.9.11
org.example:blubber_2.12:0.1

View File

@ -0,0 +1,12 @@
TotSize JarSize #TDe #Dep Module
1.754 MB ------- MB 3 1 org.example:blubber_2.12:0.1
1.754 MB 0.741 MB 2 2 com.codahale:jerkson_2.9.1:0.5.0
1.013 MB 0.780 MB 1 1 org.codehaus.jackson:jackson-mapper-asl:1.9.11
0.232 MB 0.232 MB 0 0 org.codehaus.jackson:jackson-core-asl:1.9.11
Columns are
- Jar-Size including dependencies
- Jar-Size
- Number of transitive dependencies
- Number of direct dependencies
- ModuleID

View File

@ -0,0 +1,6 @@
org.example:blubber_2.12:0.1 [S]
+-com.codahale:jerkson_2.9.1:0.5.0 [S]
+-org.codehaus.jackson:jackson-core-asl:1.9.11
+-org.codehaus.jackson:jackson-mapper-asl:1.9.11
+-org.codehaus.jackson:jackson-core-asl:1.9.11

View File

@ -0,0 +1 @@
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % sys.props("project.version"))

View File

@ -0,0 +1,5 @@
> dependencyTree::toFile target/tree.txt
> dependencyList::toFile target/list.txt
> dependencyStats::toFile target/stats.txt
> dependencyLicenseInfo::toFile target/licenses.txt
> check

View File

@ -0,0 +1,56 @@
version := "0.1.0-SNAPSHOT"
organization := "default"
name := "whatDependsOn"
scalaVersion := "2.9.1"
resolvers += "typesafe maven" at "https://repo.typesafe.com/typesafe/maven-releases/"
libraryDependencies ++= Seq(
"com.codahale" % "jerkson_2.9.1" % "0.5.0",
"org.codehaus.jackson" % "jackson-mapper-asl" % "1.9.10" // as another version of asl
)
val check = TaskKey[Unit]("check")
check := {
def sanitize(str: String): String = str.split('\n').map(_.trim).mkString("\n")
def checkOutput(output: String, expected: String): Unit =
require(sanitize(expected) == sanitize(output), s"Tree should have been [\n${sanitize(expected)}\n] but was [\n${sanitize(output)}\n]")
val withVersion =
(whatDependsOn in Compile)
.toTask(" org.codehaus.jackson jackson-core-asl 1.9.11")
.value
val expectedGraphWithVersion =
"""org.codehaus.jackson:jackson-core-asl:1.9.11
| +-com.codahale:jerkson_2.9.1:0.5.0 [S]
| | +-default:whatdependson_2.9.1:0.1.0-SNAPSHOT [S]
| |
| +-org.codehaus.jackson:jackson-mapper-asl:1.9.11
| +-com.codahale:jerkson_2.9.1:0.5.0 [S]
| | +-default:whatdependson_2.9.1:0.1.0-SNAPSHOT [S]
| |
| +-default:whatdependson_2.9.1:0.1.0-SNAPSHOT [S]
| """.stripMargin
checkOutput(withVersion, expectedGraphWithVersion)
val withoutVersion =
(whatDependsOn in Compile)
.toTask(" org.codehaus.jackson jackson-mapper-asl")
.value
val expectedGraphWithoutVersion =
"""org.codehaus.jackson:jackson-mapper-asl:1.9.11
| +-com.codahale:jerkson_2.9.1:0.5.0 [S]
| | +-default:whatdependson_2.9.1:0.1.0-SNAPSHOT [S]
| |
| +-default:whatdependson_2.9.1:0.1.0-SNAPSHOT [S]
|
|org.codehaus.jackson:jackson-mapper-asl:1.9.10 (evicted by: 1.9.11)
| +-default:whatdependson_2.9.1:0.1.0-SNAPSHOT [S]
| """.stripMargin
checkOutput(withoutVersion, expectedGraphWithoutVersion)
}

View File

@ -0,0 +1 @@
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % sys.props("project.version"))

View File

@ -0,0 +1,2 @@
# same as whatDependsOn test but without the initialization to prime the parser
> check

View File

@ -0,0 +1,52 @@
version := "0.1.0-SNAPSHOT"
scalaVersion := "2.9.1"
resolvers += "typesafe maven" at "https://repo.typesafe.com/typesafe/maven-releases/"
libraryDependencies ++= Seq(
"com.codahale" % "jerkson_2.9.1" % "0.5.0",
"org.codehaus.jackson" % "jackson-mapper-asl" % "1.9.10" // as another version of asl
)
val check = TaskKey[Unit]("check")
check := {
def sanitize(str: String): String = str.split('\n').map(_.trim).mkString("\n")
def checkOutput(output: String, expected: String): Unit =
require(sanitize(expected) == sanitize(output), s"Tree should have been [\n${sanitize(expected)}\n] but was [\n${sanitize(output)}\n]")
val withVersion =
(whatDependsOn in Compile)
.toTask(" org.codehaus.jackson jackson-core-asl 1.9.11")
.value
val expectedGraphWithVersion =
"""org.codehaus.jackson:jackson-core-asl:1.9.11
| +-com.codahale:jerkson_2.9.1:0.5.0 [S]
| | +-default:whatdependson_2.9.1:0.1.0-SNAPSHOT [S]
| |
| +-org.codehaus.jackson:jackson-mapper-asl:1.9.11
| +-com.codahale:jerkson_2.9.1:0.5.0 [S]
| | +-default:whatdependson_2.9.1:0.1.0-SNAPSHOT [S]
| |
| +-default:whatdependson_2.9.1:0.1.0-SNAPSHOT [S]
| """.stripMargin
checkOutput(withVersion, expectedGraphWithVersion)
val withoutVersion =
(whatDependsOn in Compile)
.toTask(" org.codehaus.jackson jackson-mapper-asl")
.value
val expectedGraphWithoutVersion =
"""org.codehaus.jackson:jackson-mapper-asl:1.9.11
| +-com.codahale:jerkson_2.9.1:0.5.0 [S]
| | +-default:whatdependson_2.9.1:0.1.0-SNAPSHOT [S]
| |
| +-default:whatdependson_2.9.1:0.1.0-SNAPSHOT [S]
|
|org.codehaus.jackson:jackson-mapper-asl:1.9.10 (evicted by: 1.9.11)
| +-default:whatdependson_2.9.1:0.1.0-SNAPSHOT [S]
| """.stripMargin
checkOutput(withoutVersion, expectedGraphWithoutVersion)
}

View File

@ -0,0 +1 @@
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % sys.props("project.version"))

View File

@ -0,0 +1,3 @@
# to initialize parser with deps
> compile:moduleGraph
> check

View File

@ -0,0 +1,94 @@
package net.virtualvoid.sbt.graph.util
import org.specs2.mutable.Specification
class AsciiTreeLayoutSpecs extends Specification {
sealed trait Tree
case class Branch(left: Tree, right: Tree) extends Tree
case class Leaf(i: Int) extends Tree
def children(t: Tree): Seq[Tree] = t match {
case Branch(left, right) Seq(left, right)
case _: Leaf Nil
}
def display(t: Tree): String = t match {
case Branch(left, right) "Branch"
case Leaf(value) value.toString * value
}
"Graph" should {
"layout simple graphs" in {
val simple = Branch(Branch(Leaf(1), Leaf(2)), Leaf(3))
AsciiTreeLayout.toAscii(simple, children, display, 20) ===
"""Branch
| +-Branch
| | +-1
| | +-22
| |\u0020
| +-333
| """.stripMargin
}
"add separator lines where applicable" in {
val simple = Branch(Branch(Leaf(1), Branch(Leaf(2), Leaf(3))), Leaf(4))
AsciiTreeLayout.toAscii(simple, children, display, 20) ===
"""Branch
| +-Branch
| | +-1
| | +-Branch
| | +-22
| | +-333
| |\u0020\u0020\u0020
| +-4444
| """.stripMargin
}
"layout deep graphs" in {
val simple = Branch(Branch(Branch(Branch(Branch(Branch(Leaf(1), Leaf(1)), Leaf(1)), Leaf(1)), Leaf(2)), Leaf(3)), Leaf(4))
AsciiTreeLayout.toAscii(simple, children, display, 10) ===
"""Branch
| +-Branch
| | +-Br..
| | | +-..
| | | | ..
| | | | ..
| | | | ..
| | | | ..
| | | | | |\u0020
| | | | ..
| | | | |\u0020
| | | | ..
| | | |\u0020
| | | +-22
| | |\u0020
| | +-333
| |\u0020
| +-4444
| """.stripMargin
}
"cut off cycles" in {
AsciiTreeLayout.toAscii[Int](1, Map(
1 -> Seq(2, 3, 4),
2 -> Seq(4, 5),
3 -> Seq(),
4 -> Seq(3),
5 -> Seq(1, 4, 6, 7),
6 -> Seq(),
7 -> Seq()), _.toString).trim ===
"""1
| +-2
| | +-4
| | | +-3
| | |\u0020
| | +-5
| | #-1 (cycle)
| | +-4
| | | +-3
| | |\u0020
| | +-6
| | +-7
| |\u0020\u0020\u0020
| +-3
| +-4
| +-3""".stripMargin.trim
}
}
}