mirror of
https://github.com/sbt/sbt.git
synced 2026-10-07 18:43:31 +02:00
Fixes #6886. dependencyTree, dependencyBrowseTree, and inspect tree re-explore the same node once per incoming edge. In a DAG with N levels and M children per node the rendered output is O(M^N) -- the OP needed >16 GB heap, #7360 has a 6 GB heap dump, and Friendseeker's analysis on the issue showed the exponential re-traversal directly. Fix: track a visited set across the renderer's recursion. The first time a node is encountered it is rendered in full; on subsequent visits the entry collapses to a one-line +- <id> (*) (ASCII) or a <id> (*) leaf (JSON), matching Maven's dependency:tree (*) convention. Cycle detection (separate parents set, (cycle) marker) is unchanged. Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
67 lines
2.2 KiB
Markdown
67 lines
2.2 KiB
Markdown
## Dependency tree: duplicate-subtree collapse
|
|
|
|
`dependencyTree`, `dependencyBrowseTree`, and `inspect tree` now collapse
|
|
duplicate subtrees in a DAG to a single line marked `(*)`, matching the
|
|
convention used by Maven's `dependency:tree`. The first occurrence is
|
|
rendered in full; subsequent occurrences appear as `+- <id> (*)`.
|
|
|
|
This fixes [#6886][i6886]: rendering a deep diamond DAG no longer
|
|
produces `O(M^N)` output (and the OOMs that came with it).
|
|
|
|
### Output change
|
|
|
|
Before:
|
|
|
|
```
|
|
o:root_2.13:0.1
|
|
+-o:subA_2.13:0.1 [S]
|
|
| +-o:common_2.13:0.1 [S]
|
|
| | +-org.scala-lang:scala-library:2.13.16 [S]
|
|
| +-org.scala-lang:scala-library:2.13.16 [S]
|
|
+-o:subB_2.13:0.1 [S]
|
|
| +-o:common_2.13:0.1 [S] # full subtree again
|
|
| | +-org.scala-lang:scala-library:2.13.16 [S]
|
|
| +-org.scala-lang:scala-library:2.13.16 [S]
|
|
+-org.scala-lang:scala-library:2.13.16 [S]
|
|
```
|
|
|
|
After:
|
|
|
|
```
|
|
o:root_2.13:0.1
|
|
+-o:subA_2.13:0.1 [S]
|
|
| +-o:common_2.13:0.1 [S]
|
|
| | +-org.scala-lang:scala-library:2.13.16 [S]
|
|
| +-org.scala-lang:scala-library:2.13.16 [S]
|
|
+-o:subB_2.13:0.1 [S]
|
|
| +-o:common_2.13:0.1 [S] (*) # collapsed
|
|
| +-org.scala-lang:scala-library:2.13.16 [S] (*)
|
|
+-org.scala-lang:scala-library:2.13.16 [S] (*)
|
|
```
|
|
|
|
### Affected surfaces
|
|
|
|
- `dependencyTree`: ASCII output (also `dependencyTreeList`,
|
|
`dependencyTreeStats`).
|
|
- `dependencyBrowseTree`: JSON / HTML view.
|
|
- `inspect tree`: the setting-graph renderer is the same code path, so
|
|
the `(*)` marker shows up there too. Most users don't think of
|
|
`inspect tree` as "the dependency tree" -- this note is the
|
|
heads-up.
|
|
|
|
### Contract for tooling consumers
|
|
|
|
A line whose entry ends with `(*)` is a reference to the canonical
|
|
(first-rendered) occurrence of that node within the same render.
|
|
Tools parsing `dependencyTree` / `dependencyBrowseTree` output should
|
|
treat `<id> (*)` as a back-pointer rather than a distinct dependency.
|
|
|
|
### Scope
|
|
|
|
Dedup is currently within a single root's subtree. Cross-root dedup
|
|
(when a `ModuleGraph` has multiple roots that share a transitive
|
|
closure) is tracked separately as [#9227][i9227].
|
|
|
|
[i6886]: https://github.com/sbt/sbt/issues/6886
|
|
[i9227]: https://github.com/sbt/sbt/issues/9227
|