mirror of https://github.com/sbt/sbt.git
Merge branch 'wip/prep' of sbt-dependency-graph into wip/dependencygraph
This commit is contained in:
commit
894d005f12
|
|
@ -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))
|
||||

|
||||
|
||||
- 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)
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
# sbt-dependency-graph
|
||||
|
||||
[](https://gitter.im/jrudolph/sbt-dependency-graph?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
Visualize your project's dependencies.
|
||||
|
||||
**Note: Under sbt >= 1.3.x some features might currently not work as expected or not at all (like `dependencyLicenses`).**
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
sbt-dependency-graph is an informational tool rather than one that changes your build, so you will more than likely wish to
|
||||
install it as a [global plugin] so that you can use it in any SBT project without the need to explicitly add it to each one. To do
|
||||
this, add the plugin dependency to `~/.sbt/0.13/plugins/plugins.sbt` for sbt 0.13 or `~/.sbt/1.0/plugins/plugins.sbt` for sbt 1.0:
|
||||
|
||||
```scala
|
||||
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.10.0-RC1")
|
||||
```
|
||||
|
||||
To add the plugin only to a single project, put this line into `project/plugins.sbt` of your project, instead.
|
||||
|
||||
The plugin currently supports sbt versions >= 0.13.10 and sbt 1.0.x. For versions supporting older versions of sbt see
|
||||
the notes of version [0.8.2](https://github.com/jrudolph/sbt-dependency-graph/tree/v0.8.2#compatibility-notes).
|
||||
|
||||
## Main Tasks
|
||||
|
||||
* `dependencyTree`: Shows an ASCII tree representation of the project's dependencies
|
||||
* `dependencyBrowseGraph`: Opens a browser window with a visualization of the dependency graph (courtesy of graphlib-dot + dagre-d3).
|
||||
* `dependencyBrowseTree`: Opens a browser window with a visualization of the dependency tree (courtesy of jstree).
|
||||
* `dependencyList`: Shows a flat list of all transitive dependencies on the sbt console (sorted by organization and name)
|
||||
* `whatDependsOn <organization> <module> <revision>?`: Find out what depends on an artifact. Shows a reverse dependency
|
||||
tree for the selected module. The `<revision>` argument is optional.
|
||||
* `dependencyLicenseInfo`: show dependencies grouped by declared license
|
||||
* `dependencyStats`: Shows a table with each module a row with (transitive) Jar sizes and number of dependencies
|
||||
* `dependencyGraphMl`: Generates a `.graphml` file with the project's dependencies to `target/dependencies-<config>.graphml`.
|
||||
Use e.g. [yEd](http://www.yworks.com/en/products_yed_about.html) to format the graph to your needs.
|
||||
* `dependencyDot`: Generates a .dot file with the project's dependencies to `target/dependencies-<config>.dot`.
|
||||
Use [graphviz](http://www.graphviz.org/) to render it to your preferred graphic format.
|
||||
* `dependencyGraph`: Shows an ASCII graph of the project's dependencies on the sbt console (only supported on sbt 0.13)
|
||||
* `ivyReport`: Lets ivy generate the resolution report for you project. Use
|
||||
`show ivyReport` for the filename of the generated report
|
||||
|
||||
The following tasks also support the `toFile` subtask to save the contents to a file:
|
||||
|
||||
* `dependencyTree`
|
||||
* `dependencyList`
|
||||
* `dependencyStats`
|
||||
* `dependencyLicenseInfo`
|
||||
|
||||
The `toFile` subtask has the following syntax:
|
||||
|
||||
```
|
||||
<config>:<task>::toFile <filename> [-f|--force]
|
||||
```
|
||||
|
||||
Use `-f` to force overwriting an existing file.
|
||||
|
||||
E.g. `test:dependencyStats::toFile target/depstats.txt` will write the output of the `dependencyStats` in the `test`
|
||||
configuration to the file `target/depstats.txt` but would not overwrite an existing file.
|
||||
|
||||
All tasks can be scoped to a configuration to get the report for a specific configuration. `test:dependencyGraph`,
|
||||
for example, prints the dependencies in the `test` configuration. If you don't specify any configuration, `compile` is
|
||||
assumed as usual.
|
||||
|
||||
Note: If you want to run tasks with parameters from outside the sbt shell, make sure to put the whole task invocation in
|
||||
quotes, e.g. `sbt "whatDependsOn <org> <module> <version>"`.
|
||||
|
||||
## Configuration settings
|
||||
|
||||
* `filterScalaLibrary`: Defines if the scala library should be excluded from the output of the dependency-* functions.
|
||||
If `true`, instead of showing the dependency `"[S]"` is appended to the artifact name. Set to `false` if
|
||||
you want the scala-library dependency to appear in the output. (default: true)
|
||||
* `dependencyGraphMLFile`: a setting which allows configuring the output path of `dependency-graph-ml`.
|
||||
* `dependencyDotFile`: a setting which allows configuring the output path of `dependency-dot`.
|
||||
* `dependencyDotHeader`: a setting to customize the header of the dot file (e.g. to set your preferred node shapes).
|
||||
* `dependencyDotNodeLabel`: defines the format of a node label
|
||||
(default set to `[organisation]<BR/><B>[name]</B><BR/>[version]`)
|
||||
|
||||
E.g. in `build.sbt` you can change configuration settings like this:
|
||||
|
||||
```scala
|
||||
filterScalaLibrary := false // include scala library in output
|
||||
|
||||
dependencyDotFile := file("dependencies.dot") //render dot file to `./dependencies.dot`
|
||||
```
|
||||
|
||||
## Known issues
|
||||
|
||||
* [#19]: There's an unfixed bug with graph generation for particular layouts. Workaround:
|
||||
Use `dependency-tree` instead of `dependency-graph`.
|
||||
|
||||
## License
|
||||
|
||||
Published under the [Apache License 2.0](http://en.wikipedia.org/wiki/Apache_license).
|
||||
|
||||
[global plugin]: http://www.scala-sbt.org/0.13/tutorial/Using-Plugins.html#Global+plugins
|
||||
[global build configuration]: http://www.scala-sbt.org/0.13/docs/Global-Settings.html
|
||||
[#19]: https://github.com/jrudolph/sbt-dependency-graph/issues/19
|
||||
|
|
@ -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
|
||||
|
|
@ -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")
|
||||
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
sbt.version=1.2.7
|
||||
|
|
@ -0,0 +1 @@
|
|||
addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.0")
|
||||
|
|
@ -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")
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
<!doctype html>
|
||||
|
||||
<!--
|
||||
|
||||
Based on https://github.com/cpettitt/dagre-d3/blob/d215446e7e40ebfca303f4733e746e96420e3b46/demo/interactive-demo.html
|
||||
which is published under this license:
|
||||
|
||||
Copyright (c) 2013 Chris Pettitt
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
-->
|
||||
|
||||
<meta charset="utf-8">
|
||||
<title>Dependency Graph</title>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
|
||||
<script src="https://www.samsarin.com/project/graphlib-dot/v0.6.1/graphlib-dot.js"></script>
|
||||
<script src="https://www.samsarin.com/project/dagre-d3/v0.4.16/dagre-d3.min.js"></script>
|
||||
<script src="dependencies.dot.js"></script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.node {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.node rect,
|
||||
.node circle,
|
||||
.node ellipse {
|
||||
stroke: #333;
|
||||
fill: #fff;
|
||||
stroke-width: 1.5px;
|
||||
}
|
||||
|
||||
.cluster rect {
|
||||
stroke: #333;
|
||||
fill: #000;
|
||||
fill-opacity: 0.1;
|
||||
stroke-width: 1.5px;
|
||||
}
|
||||
|
||||
.edgePath path.path {
|
||||
stroke: #333;
|
||||
stroke-width: 1.5px;
|
||||
fill: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
h1, h2 {
|
||||
color: #333;
|
||||
}
|
||||
</style>
|
||||
|
||||
<body onLoad="initialize()">
|
||||
|
||||
<svg width=1280 height=1024>
|
||||
<g/>
|
||||
</svg>
|
||||
|
||||
<script>
|
||||
function initialize() {
|
||||
// Set up zoom support
|
||||
var svg = d3.select("svg"),
|
||||
inner = d3.select("svg g"),
|
||||
zoom = d3.behavior.zoom().on("zoom", function() {
|
||||
inner.attr("transform", "translate(" + d3.event.translate + ")" +
|
||||
"scale(" + d3.event.scale + ")");
|
||||
});
|
||||
svg.attr("width", window.innerWidth);
|
||||
|
||||
svg.call(zoom);
|
||||
// Create and configure the renderer
|
||||
var render = dagreD3.render();
|
||||
function tryDraw(inputGraph) {
|
||||
var g;
|
||||
{
|
||||
g = graphlibDot.read(inputGraph);
|
||||
g.graph().rankdir = "LR";
|
||||
d3.select("svg g").call(render, g);
|
||||
|
||||
// Center the graph
|
||||
var initialScale = 0.10;
|
||||
zoom
|
||||
.translate([(svg.attr("width") - g.graph().width * initialScale) / 2, 20])
|
||||
.scale(initialScale)
|
||||
.event(svg);
|
||||
svg.attr('height', g.graph().height * initialScale + 40);
|
||||
}
|
||||
}
|
||||
tryDraw(decodeURIComponent(data));
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>dependencyBrowseTree</title>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/jstree.min.js"></script>
|
||||
<script src="tree.data.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/themes/default/style.min.css"/>
|
||||
|
||||
<style>
|
||||
#input-field {
|
||||
display: inline;
|
||||
}
|
||||
#tree_div {
|
||||
font-family: monospace
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Dependencies</h1>
|
||||
Search: <input type="search" id="input-field" value="">
|
||||
<div id="tree_div" class="jstree-no-icons"></div>
|
||||
|
||||
<script>
|
||||
$(function() {
|
||||
$('#tree_div').jstree({
|
||||
"plugins": ["search", "sort"],
|
||||
"core": {
|
||||
"data": tree_data
|
||||
}
|
||||
});
|
||||
var to = false;
|
||||
$('#input-field').keyup(function() {
|
||||
if (to) {
|
||||
clearTimeout(to);
|
||||
}
|
||||
to = setTimeout(function() {
|
||||
var v = $('#input-field').val();
|
||||
$('#tree_div').jstree(true).search(v);
|
||||
}, 250);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package net.virtualvoid.sbt.graph
|
||||
|
||||
trait ModuleGraphProtocolCompat
|
||||
|
|
@ -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.")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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[_]]
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package sbt
|
||||
package dependencygraph
|
||||
|
||||
object DependencyGraphSbtCompat {
|
||||
object Implicits
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -0,0 +1,279 @@
|
|||
/*
|
||||
* Copyright 2015 Johannes Rudolph
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.virtualvoid.sbt.graph
|
||||
|
||||
import scala.language.reflectiveCalls
|
||||
import sbt._
|
||||
import Keys._
|
||||
import sbt.complete.Parser
|
||||
import net.virtualvoid.sbt.graph.backend.{ IvyReport, SbtUpdateReport }
|
||||
import net.virtualvoid.sbt.graph.rendering.{ AsciiGraph, DagreHTML, TreeView }
|
||||
import net.virtualvoid.sbt.graph.util.IOUtil
|
||||
import internal.librarymanagement._
|
||||
import librarymanagement._
|
||||
import sbt.dependencygraph.SbtAccess
|
||||
import sbt.dependencygraph.DependencyGraphSbtCompat.Implicits._
|
||||
import sbt.complete.Parsers
|
||||
|
||||
object DependencyGraphSettings {
|
||||
import DependencyGraphKeys._
|
||||
import ModuleGraphProtocol._
|
||||
|
||||
def graphSettings = baseSettings ++ reportSettings
|
||||
|
||||
def baseSettings = Seq(
|
||||
ivyReportFunction := ivyReportFunctionTask.value,
|
||||
|
||||
// disable the cached resolution engine (exposing a scoped `ivyModule` used directly by `updateTask`), as it
|
||||
// generates artificial module descriptors which are internal to sbt, making it hard to reconstruct the
|
||||
// dependency tree
|
||||
updateOptions in ignoreMissingUpdate := updateOptions.value.withCachedResolution(false),
|
||||
ivyConfiguration in ignoreMissingUpdate :=
|
||||
// inTask will make sure the new definition will pick up `updateOptions in ignoreMissingUpdate`
|
||||
SbtAccess.inTask(ignoreMissingUpdate, Classpaths.mkIvyConfiguration).value,
|
||||
ivyModule in ignoreMissingUpdate := {
|
||||
// concatenating & inlining ivySbt & ivyModule default task implementations, as `SbtAccess.inTask` does
|
||||
// NOT correctly force the scope when applied to `TaskKey.toTask` instances (as opposed to raw
|
||||
// implementations like `Classpaths.mkIvyConfiguration` or `Classpaths.updateTask`)
|
||||
val is = new IvySbt((ivyConfiguration in ignoreMissingUpdate).value)
|
||||
new is.Module(moduleSettings.value)
|
||||
},
|
||||
|
||||
// don't fail on missing dependencies
|
||||
updateConfiguration in ignoreMissingUpdate := updateConfiguration.value.withMissingOk(true),
|
||||
|
||||
ignoreMissingUpdate :=
|
||||
// inTask will make sure the new definition will pick up `ivyModule/updateConfiguration in ignoreMissingUpdate`
|
||||
SbtAccess.inTask(ignoreMissingUpdate, Classpaths.updateTask).value,
|
||||
|
||||
filterScalaLibrary in Global := true)
|
||||
|
||||
def reportSettings =
|
||||
Seq(Compile, Test, IntegrationTest, Runtime, Provided, Optional).flatMap(ivyReportForConfig)
|
||||
|
||||
val renderingAlternatives: Seq[(TaskKey[Unit], ModuleGraph ⇒ String)] =
|
||||
Seq(
|
||||
dependencyTree -> rendering.AsciiTree.asciiTree _,
|
||||
dependencyList -> rendering.FlatList.render(_.id.idString),
|
||||
dependencyStats -> rendering.Statistics.renderModuleStatsList _,
|
||||
licenseInfo -> rendering.LicenseInfo.render _)
|
||||
|
||||
def ivyReportForConfig(config: Configuration) = inConfig(config)(
|
||||
Seq(
|
||||
ivyReport := { Def.task { ivyReportFunction.value.apply(config.toString) } dependsOn (ignoreMissingUpdate) }.value,
|
||||
crossProjectId := sbt.CrossVersion(scalaVersion.value, scalaBinaryVersion.value)(projectID.value),
|
||||
moduleGraphSbt :=
|
||||
ignoreMissingUpdate.value.configuration(configuration.value).map(report ⇒ SbtUpdateReport.fromConfigurationReport(report, crossProjectId.value)).getOrElse(ModuleGraph.empty),
|
||||
moduleGraphIvyReport := IvyReport.fromReportFile(absoluteReportPath(ivyReport.value)),
|
||||
moduleGraph := {
|
||||
sbtVersion.value match {
|
||||
case Version(0, 13, x, _) if x >= 6 ⇒ moduleGraphSbt.value
|
||||
case Version(1, _, _, _) ⇒ moduleGraphSbt.value
|
||||
}
|
||||
},
|
||||
moduleGraph := {
|
||||
// FIXME: remove busywork
|
||||
val scalaVersion = Keys.scalaVersion.value
|
||||
val moduleGraph = DependencyGraphKeys.moduleGraph.value
|
||||
|
||||
if (filterScalaLibrary.value) GraphTransformations.ignoreScalaLibrary(scalaVersion, moduleGraph)
|
||||
else moduleGraph
|
||||
},
|
||||
moduleGraphStore := (moduleGraph storeAs moduleGraphStore triggeredBy moduleGraph).value,
|
||||
|
||||
// browse
|
||||
dependencyBrowseGraphTarget := { target.value / "browse-dependency-graph" },
|
||||
dependencyBrowseGraphHTML := browseGraphHTMLTask.value,
|
||||
dependencyBrowseGraph := openBrowser(dependencyBrowseGraphHTML).value,
|
||||
|
||||
dependencyBrowseTreeTarget := { target.value / "browse-dependency-tree" },
|
||||
dependencyBrowseTreeHTML := browseTreeHTMLTask.value,
|
||||
dependencyBrowseTree := openBrowser(dependencyBrowseTreeHTML).value,
|
||||
|
||||
// dot support
|
||||
dependencyDotFile := { target.value / "dependencies-%s.dot".format(config.toString) },
|
||||
dependencyDotString := rendering.DOT.dotGraph(moduleGraph.value, dependencyDotHeader.value, dependencyDotNodeLabel.value, rendering.DOT.AngleBrackets),
|
||||
dependencyDot := writeToFile(dependencyDotString, dependencyDotFile).value,
|
||||
dependencyDotHeader :=
|
||||
"""|digraph "dependency-graph" {
|
||||
| graph[rankdir="LR"]
|
||||
| edge [
|
||||
| arrowtail="none"
|
||||
| ]""".stripMargin,
|
||||
dependencyDotNodeLabel := { (organisation: String, name: String, version: String) ⇒
|
||||
"""%s<BR/><B>%s</B><BR/>%s""".format(organisation, name, version)
|
||||
},
|
||||
|
||||
// GraphML support
|
||||
dependencyGraphMLFile := { target.value / "dependencies-%s.graphml".format(config.toString) },
|
||||
dependencyGraphML := dependencyGraphMLTask.value,
|
||||
|
||||
whatDependsOn := {
|
||||
val ArtifactPattern(org, name, versionFilter) = artifactPatternParser.parsed
|
||||
val graph = moduleGraph.value
|
||||
val modules =
|
||||
versionFilter match {
|
||||
case Some(version) ⇒ ModuleId(org, name, version) :: Nil
|
||||
case None ⇒ graph.nodes.filter(m ⇒ m.id.organisation == org && m.id.name == name).map(_.id)
|
||||
}
|
||||
val output =
|
||||
modules
|
||||
.map { module ⇒
|
||||
rendering.AsciiTree.asciiTree(GraphTransformations.reverseGraphStartingAt(graph, module))
|
||||
}
|
||||
.mkString("\n")
|
||||
|
||||
streams.value.log.info(output)
|
||||
output
|
||||
},
|
||||
// deprecated settings
|
||||
asciiTree := (asString in dependencyTree).value) ++
|
||||
renderingAlternatives.flatMap((renderingTaskSettings _).tupled) ++
|
||||
AsciiGraph.asciiGraphSetttings)
|
||||
|
||||
def renderingTaskSettings(key: TaskKey[Unit], renderer: ModuleGraph ⇒ String): Seq[Setting[_]] =
|
||||
Seq(
|
||||
asString in key := renderer(moduleGraph.value),
|
||||
printToConsole in key := streams.value.log.info((asString in key).value),
|
||||
toFile in key := {
|
||||
val (targetFile, force) = targetFileAndForceParser.parsed
|
||||
writeToFile(key.key.label, (asString in key).value, targetFile, force, streams.value)
|
||||
},
|
||||
key := (printToConsole in key).value)
|
||||
|
||||
def ivyReportFunctionTask = Def.task {
|
||||
val ivyConfig = Keys.ivyConfiguration.value.asInstanceOf[InlineIvyConfiguration]
|
||||
val projectID = Keys.projectID.value
|
||||
val ivyModule = Keys.ivyModule.value
|
||||
|
||||
(config: String) ⇒ {
|
||||
val org = projectID.organization
|
||||
val name = crossName(ivyModule)
|
||||
new File(ivyConfig.resolutionCacheDir.get, s"reports/$org-$name-$config.xml")
|
||||
}
|
||||
}
|
||||
|
||||
def dependencyGraphMLTask =
|
||||
Def.task {
|
||||
val resultFile = dependencyGraphMLFile.value
|
||||
rendering.GraphML.saveAsGraphML(moduleGraph.value, resultFile.getAbsolutePath)
|
||||
streams.value.log.info("Wrote dependency graph to '%s'" format resultFile)
|
||||
resultFile
|
||||
}
|
||||
|
||||
def browseGraphHTMLTask =
|
||||
Def.task {
|
||||
val dotGraph = rendering.DOT.dotGraph(moduleGraph.value, dependencyDotHeader.value, dependencyDotNodeLabel.value, rendering.DOT.LabelTypeHtml)
|
||||
val link = DagreHTML.createLink(dotGraph, target.value)
|
||||
streams.value.log.info(s"HTML graph written to $link")
|
||||
link
|
||||
}
|
||||
|
||||
def browseTreeHTMLTask =
|
||||
Def.task {
|
||||
val renderedTree = TreeView.createJson(moduleGraph.value)
|
||||
val link = TreeView.createLink(renderedTree, target.value)
|
||||
streams.value.log.info(s"HTML tree written to $link")
|
||||
link
|
||||
}
|
||||
|
||||
def writeToFile(dataTask: TaskKey[String], fileTask: SettingKey[File]) =
|
||||
Def.task {
|
||||
val outFile = fileTask.value
|
||||
IOUtil.writeToFile(dataTask.value, outFile)
|
||||
|
||||
streams.value.log.info("Wrote dependency graph to '%s'" format outFile)
|
||||
outFile
|
||||
}
|
||||
|
||||
def writeToFile(what: String, data: String, targetFile: File, force: Boolean, streams: TaskStreams): File =
|
||||
if (targetFile.exists && !force)
|
||||
throw new RuntimeException(s"Target file for $what already exists at ${targetFile.getAbsolutePath}. Use '-f' to override")
|
||||
else {
|
||||
IOUtil.writeToFile(data, targetFile)
|
||||
|
||||
streams.log.info(s"Wrote $what to '$targetFile'")
|
||||
targetFile
|
||||
}
|
||||
|
||||
def absoluteReportPath = (file: File) ⇒ file.getAbsolutePath
|
||||
|
||||
def openBrowser(uriKey: TaskKey[URI]) =
|
||||
Def.task {
|
||||
val uri = uriKey.value
|
||||
streams.value.log.info("Opening in browser...")
|
||||
java.awt.Desktop.getDesktop.browse(uri)
|
||||
uri
|
||||
}
|
||||
|
||||
case class ArtifactPattern(
|
||||
organisation: String,
|
||||
name: String,
|
||||
version: Option[String])
|
||||
|
||||
import sbt.complete.DefaultParsers._
|
||||
val artifactPatternParser: Def.Initialize[State ⇒ Parser[ArtifactPattern]] =
|
||||
resolvedScoped { ctx ⇒ (state: State) ⇒
|
||||
val graph = loadFromContext(moduleGraphStore, ctx, state) getOrElse ModuleGraph(Nil, Nil)
|
||||
|
||||
graph.nodes
|
||||
.map(_.id)
|
||||
.groupBy(m ⇒ (m.organisation, m.name))
|
||||
.map {
|
||||
case ((org, name), modules) ⇒
|
||||
val versionParsers: Seq[Parser[Option[String]]] =
|
||||
modules.map { id ⇒
|
||||
token(Space ~> id.version).?
|
||||
}
|
||||
|
||||
(Space ~> token(org) ~ token(Space ~> name) ~ oneOf(versionParsers)).map {
|
||||
case ((org, name), version) ⇒ ArtifactPattern(org, name, version)
|
||||
}
|
||||
}
|
||||
.reduceOption(_ | _).getOrElse {
|
||||
// If the moduleGraphStore couldn't be loaded because no dependency tree command was run before, we should still provide a parser for the command.
|
||||
((Space ~> token(StringBasic, "<organization>")) ~ (Space ~> token(StringBasic, "<module>")) ~ (Space ~> token(StringBasic, "<version?>")).?).map {
|
||||
case ((org, mod), version) ⇒
|
||||
ArtifactPattern(org, mod, version)
|
||||
}
|
||||
}
|
||||
}
|
||||
val shouldForceParser: Parser[Boolean] = (Space ~> (Parser.literal("-f") | "--force")).?.map(_.isDefined)
|
||||
|
||||
val targetFileAndForceParser: Parser[(File, Boolean)] =
|
||||
Parsers.fileParser(new File(".")) ~ shouldForceParser
|
||||
|
||||
// This is to support 0.13.8's InlineConfigurationWithExcludes while not forcing 0.13.8
|
||||
type HasModule = {
|
||||
val module: ModuleID
|
||||
}
|
||||
def crossName(ivyModule: IvySbt#Module) =
|
||||
ivyModule.moduleSettings match {
|
||||
case ic: InlineConfiguration ⇒ ic.module.name
|
||||
case hm: HasModule @unchecked if hm.getClass.getName == "sbt.InlineConfigurationWithExcludes" ⇒ hm.module.name
|
||||
case _ ⇒
|
||||
throw new IllegalStateException("sbt-dependency-graph plugin currently only supports InlineConfiguration of ivy settings (the default in sbt)")
|
||||
}
|
||||
|
||||
val VersionPattern = """(\d+)\.(\d+)\.(\d+)(?:-(.*))?""".r
|
||||
object Version {
|
||||
def unapply(str: String): Option[(Int, Int, Int, Option[String])] = str match {
|
||||
case VersionPattern(major, minor, fix, appendix) ⇒ Some((major.toInt, minor.toInt, fix.toInt, Option(appendix)))
|
||||
case _ ⇒ None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright 2015 Johannes Rudolph
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.virtualvoid.sbt.graph
|
||||
|
||||
import java.io.File
|
||||
|
||||
import net.virtualvoid.sbt.graph.backend.IvyReport
|
||||
|
||||
object Main extends App {
|
||||
def die(msg: String): Nothing = {
|
||||
println(msg)
|
||||
sys.exit(1)
|
||||
}
|
||||
def usage: String =
|
||||
"Usage: <ivy-report-file> <output-file>"
|
||||
|
||||
val reportFile = args.lift(0).filter(f ⇒ new File(f).exists).getOrElse(die(usage))
|
||||
val outputFile = args.lift(1).getOrElse(die(usage))
|
||||
val graph = IvyReport.fromReportFile(reportFile)
|
||||
rendering.GraphML.saveAsGraphML(graph, outputFile)
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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""""
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright 2015 Johannes Rudolph
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.virtualvoid.sbt.graph.rendering
|
||||
|
||||
import net.virtualvoid.sbt.graph.ModuleGraph
|
||||
|
||||
import scala.xml.XML
|
||||
|
||||
object GraphML {
|
||||
def saveAsGraphML(graph: ModuleGraph, outputFile: String): Unit = {
|
||||
val nodesXml =
|
||||
for (n ← graph.nodes)
|
||||
yield <node id={ n.id.idString }><data key="d0">
|
||||
<y:ShapeNode>
|
||||
<y:NodeLabel>{ n.id.idString }</y:NodeLabel>
|
||||
</y:ShapeNode>
|
||||
</data></node>
|
||||
|
||||
val edgesXml =
|
||||
for (e ← graph.edges)
|
||||
yield <edge source={ e._1.idString } target={ e._2.idString }/>
|
||||
|
||||
val xml =
|
||||
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">
|
||||
<key for="node" id="d0" yfiles.type="nodegraphics"/>
|
||||
<graph id="Graph" edgedefault="undirected">
|
||||
{ nodesXml }
|
||||
{ edgesXml }
|
||||
</graph>
|
||||
</graphml>
|
||||
|
||||
XML.save(outputFile, xml)
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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)))
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)))
|
||||
()
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % sys.props("project.version"))
|
||||
|
|
@ -0,0 +1 @@
|
|||
> check
|
||||
|
|
@ -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)))
|
||||
()
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
../../plugins.sbt
|
||||
|
|
@ -0,0 +1 @@
|
|||
> check
|
||||
|
|
@ -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)))
|
||||
()
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
../../plugins.sbt
|
||||
|
|
@ -0,0 +1 @@
|
|||
> check
|
||||
|
|
@ -0,0 +1 @@
|
|||
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % sys.props("project.version"))
|
||||
|
|
@ -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)))
|
||||
()
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
../../plugins.sbt
|
||||
|
|
@ -0,0 +1 @@
|
|||
> check
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
import collection.mutable.ListBuffer
|
||||
|
||||
import net.virtualvoid.sbt.graph.DependencyGraphKeys.dependencyDot
|
||||
|
||||
import scala.collection.mutable.ListBuffer
|
||||
|
||||
def defaultSettings =
|
||||
Seq(
|
||||
scalaVersion := "2.9.2",
|
||||
version := "0.1-SNAPSHOT"
|
||||
)
|
||||
|
||||
lazy val justATransiviteDependencyEndpointProject =
|
||||
Project("just-a-transitive-dependency-endpoint", file("a"))
|
||||
.settings(defaultSettings: _*)
|
||||
|
||||
lazy val justATransitiveDependencyProject =
|
||||
Project("just-a-transitive-dependency", file("b"))
|
||||
.settings(defaultSettings: _*)
|
||||
.dependsOn(justATransiviteDependencyEndpointProject)
|
||||
|
||||
lazy val justADependencyProject =
|
||||
Project("just-a-dependency", file("c"))
|
||||
.settings(defaultSettings: _*)
|
||||
|
||||
lazy val test_project =
|
||||
Project("test-dot-file-generation", file("d"))
|
||||
.settings(defaultSettings: _*)
|
||||
.settings(
|
||||
TaskKey[Unit]("check") := {
|
||||
val dotFile = (dependencyDot in Compile).value
|
||||
val expectedGraph =
|
||||
"""digraph "dependency-graph" {
|
||||
| graph[rankdir="LR"]
|
||||
| edge [
|
||||
| arrowtail="none"
|
||||
| ]
|
||||
| "test-dot-file-generation:test-dot-file-generation_2.9.2:0.1-SNAPSHOT"[label=<test-dot-file-generation<BR/><B>test-dot-file-generation_2.9.2</B><BR/>0.1-SNAPSHOT> style=""]
|
||||
| "just-a-transitive-dependency:just-a-transitive-dependency_2.9.2:0.1-SNAPSHOT"[label=<just-a-transitive-dependency<BR/><B>just-a-transitive-dependency_2.9.2</B><BR/>0.1-SNAPSHOT> style=""]
|
||||
| "just-a-transitive-dependency-endpoint:just-a-transitive-dependency-endpoint_2.9.2:0.1-SNAPSHOT"[label=<just-a-transitive-dependency-endpoint<BR/><B>just-a-transitive-dependency-endpoint_2.9.2</B><BR/>0.1-SNAPSHOT> style=""]
|
||||
| "just-a-dependency:just-a-dependency_2.9.2:0.1-SNAPSHOT"[label=<just-a-dependency<BR/><B>just-a-dependency_2.9.2</B><BR/>0.1-SNAPSHOT> style=""]
|
||||
| "test-dot-file-generation:test-dot-file-generation_2.9.2:0.1-SNAPSHOT" -> "just-a-transitive-dependency:just-a-transitive-dependency_2.9.2:0.1-SNAPSHOT"
|
||||
| "just-a-transitive-dependency:just-a-transitive-dependency_2.9.2:0.1-SNAPSHOT" -> "just-a-transitive-dependency-endpoint:just-a-transitive-dependency-endpoint_2.9.2:0.1-SNAPSHOT"
|
||||
| "test-dot-file-generation:test-dot-file-generation_2.9.2:0.1-SNAPSHOT" -> "just-a-dependency:just-a-dependency_2.9.2:0.1-SNAPSHOT"
|
||||
|}
|
||||
""".stripMargin
|
||||
|
||||
val graph : String = scala.io.Source.fromFile(dotFile.getAbsolutePath).mkString
|
||||
val errors = compareByLine(graph, expectedGraph)
|
||||
require(errors.isEmpty , errors.mkString("\n"))
|
||||
()
|
||||
}
|
||||
)
|
||||
.dependsOn(justADependencyProject, justATransitiveDependencyProject)
|
||||
|
||||
def compareByLine(got : String, expected : String) : Seq[String] = {
|
||||
val errors = ListBuffer[String]()
|
||||
got.split("\n").zip(expected.split("\n").toSeq).zipWithIndex.foreach { case((got_line : String, expected_line : String), i : Int) =>
|
||||
if(got_line != expected_line) {
|
||||
errors.append(
|
||||
"""not matching lines at line %s
|
||||
|expected: %s
|
||||
|got: %s
|
||||
|""".stripMargin.format(i,expected_line, got_line))
|
||||
}
|
||||
}
|
||||
errors
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
../../plugins.sbt
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
> project test-dot-file-generation
|
||||
> check
|
||||
|
|
@ -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)))
|
||||
()
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % sys.props("project.version"))
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % sys.props("project.version"))
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
# same as whatDependsOn test but without the initialization to prime the parser
|
||||
> check
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % sys.props("project.version"))
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# to initialize parser with deps
|
||||
> compile:moduleGraph
|
||||
> check
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue