conandeps: spell out each branch's path on indirect nodes in the tree

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYNdaGDrpaqDyT8QtrTQbM
This commit is contained in:
Ole-Morten Duesund 2026-08-25 15:45:48 +02:00
commit a7db279fb0
3 changed files with 11 additions and 4 deletions

View file

@ -116,10 +116,10 @@ Dependency hierarchy
MyProject/conanfile.py MyProject/conanfile.py
|-- MyDepA/1.0 |-- MyDepA/1.0
| `-- boost/1.8 | `-- boost/1.8 [MyDepA -> boost]
`-- MyDepB/1.0 `-- MyDepB/1.0
`-- Zigma/1.0 [MISSING: RelWithDebInfo] `-- Zigma/1.0 [MyDepB -> Zigma; MISSING: RelWithDebInfo]
`-- boost/1.8 [overrides boost/1.7 (implicit by your conanfile)] `-- boost/1.8 [MyDepB -> Zigma -> boost; overrides boost/1.7 (implicit by your conanfile)]
``` ```
On a terminal the tables are coloured (green = on the remote, red = missing, On a terminal the tables are coloured (green = on the remote, red = missing,
@ -135,7 +135,8 @@ package required by several others gets one line per parent (boost above is
pulled in by both MyDepA and Zigma), shortest path first. Rows are sorted pulled in by both MyDepA and Zigma), shortest path first. Rows are sorted
direct-first, then by path, so a branch's transitive dependencies stay direct-first, then by path, so a branch's transitive dependencies stay
together; the dependency hierarchy section at the end shows the same together; the dependency hierarchy section at the end shows the same
information as a tree. information as a tree, and every indirect node there also spells out the
path of its branch so deep indentation never has to be counted.
* a remote name (e.g. `myremote`) the remote has a binary for the package_id your * a remote name (e.g. `myremote`) the remote has a binary for the package_id your

View file

@ -450,6 +450,8 @@ def _tree_lines(report: Report) -> List[str]:
lines.append(prefix + branch + name + " (not in graph)") lines.append(prefix + branch + name + " (not in graph)")
return return
tags = [] tags = []
if seen: # indirect: spell out this branch's path so indentation need not be counted
tags.append(" -> ".join(seen + (name,)))
if dep.build_require: if dep.build_require:
tags.append("build-require") tags.append("build-require")
ov = edge_overrides.get((parent, name)) ov = edge_overrides.get((parent, name))

View file

@ -337,6 +337,10 @@ def test_text_and_html_render(report, tmp_path):
assert "package wanted forced to by kind" in text assert "package wanted forced to by kind" in text
assert "libfoo/1.0 libbar/1.0 libbar/1.1 your conanfile explicit (override=True)" in text assert "libfoo/1.0 libbar/1.0 libbar/1.1 your conanfile explicit (override=True)" in text
assert "overrides libbar/1.0 (explicit" in text # tree edge annotation assert "overrides libbar/1.0 (explicit" in text # tree edge annotation
# Tree lines carry the full path of their branch; direct nodes do not.
tree = text.split("Dependency hierarchy")[1]
assert re.search(r"`-- libdeep/1\.0 \[libfoo -> libbar -> libdeep\]", tree)
assert re.search(r"\|-- libfoo/1\.0\n", tree)
coloured = conandeps.render_text(report, color=True) coloured = conandeps.render_text(report, color=True)
assert "\033[1;31mMISSING\033[0m" in coloured and "\033[32mtest\033[0m" in coloured assert "\033[1;31mMISSING\033[0m" in coloured and "\033[32mtest\033[0m" in coloured