diff --git a/sbt-dependency-graph/CHANGELOG.md b/sbt-dependency-graph/CHANGELOG.md new file mode 100644 index 000000000..02eb5e41b --- /dev/null +++ b/sbt-dependency-graph/CHANGELOG.md @@ -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) \ No newline at end of file diff --git a/sbt-dependency-graph/LICENSE b/sbt-dependency-graph/LICENSE new file mode 100644 index 000000000..f49a4e16e --- /dev/null +++ b/sbt-dependency-graph/LICENSE @@ -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. \ No newline at end of file diff --git a/sbt-dependency-graph/NOTICE b/sbt-dependency-graph/NOTICE new file mode 100644 index 000000000..282e7e8e5 --- /dev/null +++ b/sbt-dependency-graph/NOTICE @@ -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. diff --git a/sbt-dependency-graph/PUBLISHING b/sbt-dependency-graph/PUBLISHING new file mode 100644 index 000000000..1f728acbf --- /dev/null +++ b/sbt-dependency-graph/PUBLISHING @@ -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 diff --git a/sbt-dependency-graph/README.md b/sbt-dependency-graph/README.md new file mode 100644 index 000000000..e2b07689f --- /dev/null +++ b/sbt-dependency-graph/README.md @@ -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 ?`: Find out what depends on an artifact. Shows a reverse dependency + tree for the selected module. The `` 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-.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-.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: + +``` +:::toFile [-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 "`. + +## 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]
[name]
[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 diff --git a/sbt-dependency-graph/build.sbt b/sbt-dependency-graph/build.sbt new file mode 100644 index 000000000..27799adea --- /dev/null +++ b/sbt-dependency-graph/build.sbt @@ -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 diff --git a/sbt-dependency-graph/project.sbt b/sbt-dependency-graph/project.sbt new file mode 100644 index 000000000..8d6305446 --- /dev/null +++ b/sbt-dependency-graph/project.sbt @@ -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") diff --git a/sbt-dependency-graph/project/ScalariformSupport.scala b/sbt-dependency-graph/project/ScalariformSupport.scala new file mode 100644 index 000000000..665a9e6fe --- /dev/null +++ b/sbt-dependency-graph/project/ScalariformSupport.scala @@ -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) + +} diff --git a/sbt-dependency-graph/project/build.properties b/sbt-dependency-graph/project/build.properties new file mode 100644 index 000000000..72f902892 --- /dev/null +++ b/sbt-dependency-graph/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.2.7 diff --git a/sbt-dependency-graph/project/pgp.sbt b/sbt-dependency-graph/project/pgp.sbt new file mode 100644 index 000000000..2efcc4b69 --- /dev/null +++ b/sbt-dependency-graph/project/pgp.sbt @@ -0,0 +1 @@ +addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.0") diff --git a/sbt-dependency-graph/project/plugins.sbt b/sbt-dependency-graph/project/plugins.sbt new file mode 100644 index 000000000..cb24a862c --- /dev/null +++ b/sbt-dependency-graph/project/plugins.sbt @@ -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") diff --git a/sbt-dependency-graph/publish.sbt b/sbt-dependency-graph/publish.sbt new file mode 100644 index 000000000..efef315bc --- /dev/null +++ b/sbt-dependency-graph/publish.sbt @@ -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 diff --git a/sbt-dependency-graph/src/main/resources/graph.html b/sbt-dependency-graph/src/main/resources/graph.html new file mode 100644 index 000000000..4ea8baba6 --- /dev/null +++ b/sbt-dependency-graph/src/main/resources/graph.html @@ -0,0 +1,115 @@ + + + + + +Dependency Graph + + + + + + + + + + + + + + + + + + + diff --git a/sbt-dependency-graph/src/main/resources/tree.html b/sbt-dependency-graph/src/main/resources/tree.html new file mode 100644 index 000000000..909a9b82e --- /dev/null +++ b/sbt-dependency-graph/src/main/resources/tree.html @@ -0,0 +1,49 @@ + + + + + dependencyBrowseTree + + + + + + + + + + + +

Dependencies

+Search: +
+ + + + \ No newline at end of file diff --git a/sbt-dependency-graph/src/main/scala-sbt-0.13/net/virtualvoid/sbt/graph/ModuleGraphProtocolCompat.scala b/sbt-dependency-graph/src/main/scala-sbt-0.13/net/virtualvoid/sbt/graph/ModuleGraphProtocolCompat.scala new file mode 100644 index 000000000..d28f20d8b --- /dev/null +++ b/sbt-dependency-graph/src/main/scala-sbt-0.13/net/virtualvoid/sbt/graph/ModuleGraphProtocolCompat.scala @@ -0,0 +1,3 @@ +package net.virtualvoid.sbt.graph + +trait ModuleGraphProtocolCompat diff --git a/sbt-dependency-graph/src/main/scala-sbt-0.13/net/virtualvoid/sbt/graph/rendering/AsciiGraph.scala b/sbt-dependency-graph/src/main/scala-sbt-0.13/net/virtualvoid/sbt/graph/rendering/AsciiGraph.scala new file mode 100644 index 000000000..f1c83ec18 --- /dev/null +++ b/sbt-dependency-graph/src/main/scala-sbt-0.13/net/virtualvoid/sbt/graph/rendering/AsciiGraph.scala @@ -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.") + } + }) +} diff --git a/sbt-dependency-graph/src/main/scala-sbt-0.13/sbt/dependencygraph/DependencyGraphSbtCompat.scala b/sbt-dependency-graph/src/main/scala-sbt-0.13/sbt/dependencygraph/DependencyGraphSbtCompat.scala new file mode 100644 index 000000000..5aa6bacc6 --- /dev/null +++ b/sbt-dependency-graph/src/main/scala-sbt-0.13/sbt/dependencygraph/DependencyGraphSbtCompat.scala @@ -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) + } + } +} diff --git a/sbt-dependency-graph/src/main/scala-sbt-1.0/net/virtualvoid/sbt/graph/ModuleGraphProtocolCompat.scala b/sbt-dependency-graph/src/main/scala-sbt-1.0/net/virtualvoid/sbt/graph/ModuleGraphProtocolCompat.scala new file mode 100644 index 000000000..1fb08ccc7 --- /dev/null +++ b/sbt-dependency-graph/src/main/scala-sbt-1.0/net/virtualvoid/sbt/graph/ModuleGraphProtocolCompat.scala @@ -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)) + } + } +} diff --git a/sbt-dependency-graph/src/main/scala-sbt-1.0/net/virtualvoid/sbt/graph/rendering/AsciiGraph.scala b/sbt-dependency-graph/src/main/scala-sbt-1.0/net/virtualvoid/sbt/graph/rendering/AsciiGraph.scala new file mode 100644 index 000000000..b9e187a3b --- /dev/null +++ b/sbt-dependency-graph/src/main/scala-sbt-1.0/net/virtualvoid/sbt/graph/rendering/AsciiGraph.scala @@ -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[_]] +} \ No newline at end of file diff --git a/sbt-dependency-graph/src/main/scala-sbt-1.0/sbt/dependencygraph/DependencyGraphSbtCompat.scala b/sbt-dependency-graph/src/main/scala-sbt-1.0/sbt/dependencygraph/DependencyGraphSbtCompat.scala new file mode 100644 index 000000000..ea196cb8f --- /dev/null +++ b/sbt-dependency-graph/src/main/scala-sbt-1.0/sbt/dependencygraph/DependencyGraphSbtCompat.scala @@ -0,0 +1,6 @@ +package sbt +package dependencygraph + +object DependencyGraphSbtCompat { + object Implicits +} diff --git a/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/DependencyGraphKeys.scala b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/DependencyGraphKeys.scala new file mode 100644 index 000000000..2abdd156d --- /dev/null +++ b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/DependencyGraphKeys.scala @@ -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 diff --git a/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/DependencyGraphPlugin.scala b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/DependencyGraphPlugin.scala new file mode 100755 index 000000000..31f36d729 --- /dev/null +++ b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/DependencyGraphPlugin.scala @@ -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 +} diff --git a/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/DependencyGraphSettings.scala b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/DependencyGraphSettings.scala new file mode 100644 index 000000000..bec65daf6 --- /dev/null +++ b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/DependencyGraphSettings.scala @@ -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
%s
%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, "")) ~ (Space ~> token(StringBasic, "")) ~ (Space ~> token(StringBasic, "")).?).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 + } + } +} diff --git a/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/GraphTransformations.scala b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/GraphTransformations.scala new file mode 100644 index 000000000..c5c4e8a21 --- /dev/null +++ b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/GraphTransformations.scala @@ -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) + } +} diff --git a/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/Main.scala b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/Main.scala new file mode 100644 index 000000000..fb51d60b7 --- /dev/null +++ b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/Main.scala @@ -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: " + + 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) +} diff --git a/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/backend/IvyReport.scala b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/backend/IvyReport.scala new file mode 100644 index 000000000..426a68a3e --- /dev/null +++ b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/backend/IvyReport.scala @@ -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() +} diff --git a/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/backend/SbtUpdateReport.scala b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/backend/SbtUpdateReport.scala new file mode 100644 index 000000000..44a4c6cec --- /dev/null +++ b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/backend/SbtUpdateReport.scala @@ -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) + } +} diff --git a/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/model.scala b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/model.scala new file mode 100644 index 000000000..cbfc43294 --- /dev/null +++ b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/model.scala @@ -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) +} diff --git a/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/package.scala b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/package.scala new file mode 100644 index 000000000..41921bcfa --- /dev/null +++ b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/package.scala @@ -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 +} diff --git a/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/rendering/AsciiTree.scala b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/rendering/AsciiTree.scala new file mode 100644 index 000000000..c2670e6a2 --- /dev/null +++ b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/rendering/AsciiTree.scala @@ -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) +} diff --git a/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/rendering/DOT.scala b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/rendering/DOT.scala new file mode 100644 index 000000000..dd63bacb3 --- /dev/null +++ b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/rendering/DOT.scala @@ -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"""" + } +} diff --git a/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/rendering/DagreHTML.scala b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/rendering/DagreHTML.scala new file mode 100644 index 000000000..3064ef502 --- /dev/null +++ b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/rendering/DagreHTML.scala @@ -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) + } +} diff --git a/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/rendering/FlatList.scala b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/rendering/FlatList.scala new file mode 100644 index 000000000..95f0676d6 --- /dev/null +++ b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/rendering/FlatList.scala @@ -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") +} diff --git a/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/rendering/GraphML.scala b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/rendering/GraphML.scala new file mode 100644 index 000000000..e1681fdad --- /dev/null +++ b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/rendering/GraphML.scala @@ -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 + + { n.id.idString } + + + + val edgesXml = + for (e ← graph.edges) + yield + + val xml = + + + + { nodesXml } + { edgesXml } + + + + XML.save(outputFile, xml) + } +} diff --git a/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/rendering/LicenseInfo.scala b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/rendering/LicenseInfo.scala new file mode 100644 index 000000000..cb97d3f0e --- /dev/null +++ b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/rendering/LicenseInfo.scala @@ -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") +} diff --git a/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/rendering/Statistics.scala b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/rendering/Statistics.scala new file mode 100644 index 000000000..a9d10fdba --- /dev/null +++ b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/rendering/Statistics.scala @@ -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 + } +} diff --git a/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/rendering/TreeView.scala b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/rendering/TreeView.scala new file mode 100644 index 000000000..582202def --- /dev/null +++ b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/rendering/TreeView.scala @@ -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))) + } +} diff --git a/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/util/AsciiTreeLayout.scala b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/util/AsciiTreeLayout.scala new file mode 100644 index 000000000..746dab6df --- /dev/null +++ b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/util/AsciiTreeLayout.scala @@ -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 + } +} diff --git a/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/util/ConsoleUtils.scala b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/util/ConsoleUtils.scala new file mode 100644 index 000000000..ceba7da65 --- /dev/null +++ b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/util/ConsoleUtils.scala @@ -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 +} diff --git a/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/util/IOUtil.scala b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/util/IOUtil.scala new file mode 100644 index 000000000..c9b8ebbf5 --- /dev/null +++ b/sbt-dependency-graph/src/main/scala/net/virtualvoid/sbt/graph/util/IOUtil.scala @@ -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() + } +} diff --git a/sbt-dependency-graph/src/main/scala/sbt/dependencygraph/SbtAccess.scala b/sbt-dependency-graph/src/main/scala/sbt/dependencygraph/SbtAccess.scala new file mode 100644 index 000000000..d1f4e596e --- /dev/null +++ b/sbt-dependency-graph/src/main/scala/sbt/dependencygraph/SbtAccess.scala @@ -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) +} diff --git a/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/cachedResolution/build.sbt b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/cachedResolution/build.sbt new file mode 100644 index 000000000..e9aef9b55 --- /dev/null +++ b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/cachedResolution/build.sbt @@ -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))) + () +} diff --git a/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/cachedResolution/project/plugins.sbt b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/cachedResolution/project/plugins.sbt new file mode 100644 index 000000000..6fdebb6d6 --- /dev/null +++ b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/cachedResolution/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % sys.props("project.version")) diff --git a/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/cachedResolution/test b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/cachedResolution/test new file mode 100644 index 000000000..a5912a391 --- /dev/null +++ b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/cachedResolution/test @@ -0,0 +1 @@ +> check \ No newline at end of file diff --git a/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/ignoreScalaLibrary/build.sbt b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/ignoreScalaLibrary/build.sbt new file mode 100644 index 000000000..1d004c5b6 --- /dev/null +++ b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/ignoreScalaLibrary/build.sbt @@ -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))) + () +} diff --git a/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/ignoreScalaLibrary/project/plugins.sbt b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/ignoreScalaLibrary/project/plugins.sbt new file mode 120000 index 000000000..0caf1de77 --- /dev/null +++ b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/ignoreScalaLibrary/project/plugins.sbt @@ -0,0 +1 @@ +../../plugins.sbt \ No newline at end of file diff --git a/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/ignoreScalaLibrary/test b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/ignoreScalaLibrary/test new file mode 100644 index 000000000..a5912a391 --- /dev/null +++ b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/ignoreScalaLibrary/test @@ -0,0 +1 @@ +> check \ No newline at end of file diff --git a/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/intervalRangedVersions/build.sbt b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/intervalRangedVersions/build.sbt new file mode 100644 index 000000000..b417c9e3b --- /dev/null +++ b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/intervalRangedVersions/build.sbt @@ -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))) + () +} diff --git a/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/intervalRangedVersions/project/plugins.sbt b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/intervalRangedVersions/project/plugins.sbt new file mode 120000 index 000000000..0caf1de77 --- /dev/null +++ b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/intervalRangedVersions/project/plugins.sbt @@ -0,0 +1 @@ +../../plugins.sbt \ No newline at end of file diff --git a/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/intervalRangedVersions/test b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/intervalRangedVersions/test new file mode 100644 index 000000000..a5912a391 --- /dev/null +++ b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/intervalRangedVersions/test @@ -0,0 +1 @@ +> check \ No newline at end of file diff --git a/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/plugins.sbt b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/plugins.sbt new file mode 100644 index 000000000..6fdebb6d6 --- /dev/null +++ b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % sys.props("project.version")) diff --git a/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/showMissingUpdates/build.sbt b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/showMissingUpdates/build.sbt new file mode 100644 index 000000000..da6ad1a2a --- /dev/null +++ b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/showMissingUpdates/build.sbt @@ -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))) + () +} diff --git a/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/showMissingUpdates/project/plugins.sbt b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/showMissingUpdates/project/plugins.sbt new file mode 120000 index 000000000..0caf1de77 --- /dev/null +++ b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/showMissingUpdates/project/plugins.sbt @@ -0,0 +1 @@ +../../plugins.sbt \ No newline at end of file diff --git a/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/showMissingUpdates/test b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/showMissingUpdates/test new file mode 100644 index 000000000..a5912a391 --- /dev/null +++ b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/showMissingUpdates/test @@ -0,0 +1 @@ +> check \ No newline at end of file diff --git a/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/testDotFileGeneration/build.sbt b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/testDotFileGeneration/build.sbt new file mode 100644 index 000000000..bb8469447 --- /dev/null +++ b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/testDotFileGeneration/build.sbt @@ -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_2.9.2
0.1-SNAPSHOT> style=""] + | "just-a-transitive-dependency:just-a-transitive-dependency_2.9.2:0.1-SNAPSHOT"[label=just-a-transitive-dependency_2.9.2
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_2.9.2
0.1-SNAPSHOT> style=""] + | "just-a-dependency:just-a-dependency_2.9.2:0.1-SNAPSHOT"[label=just-a-dependency_2.9.2
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 +} \ No newline at end of file diff --git a/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/testDotFileGeneration/project/plugins.sbt b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/testDotFileGeneration/project/plugins.sbt new file mode 120000 index 000000000..0caf1de77 --- /dev/null +++ b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/testDotFileGeneration/project/plugins.sbt @@ -0,0 +1 @@ +../../plugins.sbt \ No newline at end of file diff --git a/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/testDotFileGeneration/test b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/testDotFileGeneration/test new file mode 100644 index 000000000..f9fa19e83 --- /dev/null +++ b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/testDotFileGeneration/test @@ -0,0 +1,2 @@ +> project test-dot-file-generation +> check diff --git a/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/toFileSubTask/build.sbt b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/toFileSubTask/build.sbt new file mode 100644 index 000000000..d34ee89e9 --- /dev/null +++ b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/toFileSubTask/build.sbt @@ -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))) + () +} diff --git a/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/toFileSubTask/expected/licenses.txt b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/toFileSubTask/expected/licenses.txt new file mode 100644 index 000000000..826ed0153 --- /dev/null +++ b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/toFileSubTask/expected/licenses.txt @@ -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 \ No newline at end of file diff --git a/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/toFileSubTask/expected/list.txt b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/toFileSubTask/expected/list.txt new file mode 100644 index 000000000..4bf401868 --- /dev/null +++ b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/toFileSubTask/expected/list.txt @@ -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 \ No newline at end of file diff --git a/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/toFileSubTask/expected/stats.txt b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/toFileSubTask/expected/stats.txt new file mode 100644 index 000000000..6a76e3522 --- /dev/null +++ b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/toFileSubTask/expected/stats.txt @@ -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 \ No newline at end of file diff --git a/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/toFileSubTask/expected/tree.txt b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/toFileSubTask/expected/tree.txt new file mode 100644 index 000000000..5b1f1aa5e --- /dev/null +++ b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/toFileSubTask/expected/tree.txt @@ -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 + \ No newline at end of file diff --git a/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/toFileSubTask/project/plugins.sbt b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/toFileSubTask/project/plugins.sbt new file mode 100644 index 000000000..6fdebb6d6 --- /dev/null +++ b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/toFileSubTask/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % sys.props("project.version")) diff --git a/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/toFileSubTask/test b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/toFileSubTask/test new file mode 100644 index 000000000..83d872e3e --- /dev/null +++ b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/toFileSubTask/test @@ -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 diff --git a/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/whatDependsOn-without-previous-initialization/build.sbt b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/whatDependsOn-without-previous-initialization/build.sbt new file mode 100644 index 000000000..92bb3b2dd --- /dev/null +++ b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/whatDependsOn-without-previous-initialization/build.sbt @@ -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) +} diff --git a/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/whatDependsOn-without-previous-initialization/project/plugins.sbt b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/whatDependsOn-without-previous-initialization/project/plugins.sbt new file mode 100644 index 000000000..6fdebb6d6 --- /dev/null +++ b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/whatDependsOn-without-previous-initialization/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % sys.props("project.version")) diff --git a/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/whatDependsOn-without-previous-initialization/test b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/whatDependsOn-without-previous-initialization/test new file mode 100644 index 000000000..ddc84434f --- /dev/null +++ b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/whatDependsOn-without-previous-initialization/test @@ -0,0 +1,2 @@ +# same as whatDependsOn test but without the initialization to prime the parser +> check \ No newline at end of file diff --git a/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/whatDependsOn/build.sbt b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/whatDependsOn/build.sbt new file mode 100644 index 000000000..19c9108a1 --- /dev/null +++ b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/whatDependsOn/build.sbt @@ -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) +} diff --git a/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/whatDependsOn/project/plugins.sbt b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/whatDependsOn/project/plugins.sbt new file mode 100644 index 000000000..6fdebb6d6 --- /dev/null +++ b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/whatDependsOn/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % sys.props("project.version")) diff --git a/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/whatDependsOn/test b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/whatDependsOn/test new file mode 100644 index 000000000..961ba2cf3 --- /dev/null +++ b/sbt-dependency-graph/src/sbt-test/sbt-dependency-graph/whatDependsOn/test @@ -0,0 +1,3 @@ +# to initialize parser with deps +> compile:moduleGraph +> check \ No newline at end of file diff --git a/sbt-dependency-graph/src/test/scala/net/virtualvoid/sbt/graph/util/AsciiTreeLayoutSpecs.scala b/sbt-dependency-graph/src/test/scala/net/virtualvoid/sbt/graph/util/AsciiTreeLayoutSpecs.scala new file mode 100644 index 000000000..097b7adf6 --- /dev/null +++ b/sbt-dependency-graph/src/test/scala/net/virtualvoid/sbt/graph/util/AsciiTreeLayoutSpecs.scala @@ -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 + } + } +}