conandeps: list every parent path in the via column
A package required by several others now gets one line per direct parent (shortest path to that parent, shortest first) instead of a "(+N)" count. The text table helper supports multi-line cells; HTML uses <br>. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EYNdaGDrpaqDyT8QtrTQbM
This commit is contained in:
parent
a64386df1b
commit
4b084f8bae
4 changed files with 70 additions and 30 deletions
|
|
@ -56,10 +56,11 @@ Users install/upgrade on their own machine with
|
|||
- **Dependency table shows the shortest path, not a level number.**
|
||||
`_paths()` does a breadth-first walk from the root; `Dep.path` is the list
|
||||
of package names on the *shortest* path (empty = direct, so a package that
|
||||
is both direct and transitive is direct). The `via` column renders it as
|
||||
`A -> B` (`→` in HTML) with `(+N)` for further direct parents from
|
||||
`Dep.parents`. Rows sort on (depth, path, host before build, name). The
|
||||
tree section keeps graph order and shows every path.
|
||||
is both direct and transitive is direct). `Dep.paths` holds one path per
|
||||
direct parent (shortest path to that parent + parent) – bounded, unlike
|
||||
"all paths". The `via` column renders them one per line as `A -> B`
|
||||
(`→`/`<br>` in HTML); `_table()` supports multi-line cells. Rows sort on
|
||||
(depth, path, host before build, name). The tree section keeps graph order.
|
||||
- **Only the remote counts, and the source is always shown.** Conan says
|
||||
`Cache` for a locally cached binary without asking the remote, so
|
||||
`_RemoteIndex` verifies cache hits with `search_packages` on the remote.
|
||||
|
|
|
|||
24
README.md
24
README.md
|
|
@ -92,12 +92,13 @@ profile: arch=x86_64, compiler=gcc, compiler.libcxx=libstdc++11, compiler.versio
|
|||
|
||||
Dependencies (4) and binary availability
|
||||
|
||||
package via ctx Release Debug RelWithDebInfo
|
||||
-----------------------------------------------------------------
|
||||
MyDepA/1.0 - host myremote myremote myremote
|
||||
MyDepB/1.0 - host myremote myremote myremote
|
||||
boost/1.8 MyDepA (+1) host myremote myremote myremote
|
||||
Zigma/1.0 MyDepB host myremote myremote MISSING
|
||||
package via ctx Release Debug RelWithDebInfo
|
||||
---------------------------------------------------------------------
|
||||
MyDepA/1.0 - host myremote myremote myremote
|
||||
MyDepB/1.0 - host myremote myremote myremote
|
||||
boost/1.8 MyDepA host myremote myremote myremote
|
||||
MyDepB -> Zigma
|
||||
Zigma/1.0 MyDepB host myremote myremote MISSING
|
||||
|
||||
Missing binaries (1)
|
||||
|
||||
|
|
@ -129,11 +130,12 @@ the same colour coding.
|
|||
|
||||
Reading the table: the `via` column is the shortest path from your conanfile
|
||||
to the package – `-` for a direct dependency, `MyDepB` for something MyDepB
|
||||
requires, `MyDepB -> Zigma` for something Zigma requires, and so on. `(+N)`
|
||||
means N other packages also require it directly (boost above is pulled in by
|
||||
both MyDepA and Zigma); the dependency hierarchy section shows every path.
|
||||
Rows are sorted direct-first, then by path, so a branch's transitive
|
||||
dependencies stay together.
|
||||
requires, `MyDepB -> Zigma` for something Zigma requires, and so on. A
|
||||
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
|
||||
direct-first, then by path, so a branch's transitive dependencies stay
|
||||
together; the dependency hierarchy section at the end shows the same
|
||||
information as a tree.
|
||||
|
||||
|
||||
* a remote name (e.g. `myremote`) – the remote has a binary for the package_id your
|
||||
|
|
|
|||
45
conandeps.py
45
conandeps.py
|
|
@ -89,6 +89,9 @@ class Dep:
|
|||
path: List[str] = field(default_factory=list)
|
||||
# Names of every package that directly requires this one (all paths).
|
||||
parents: List[str] = field(default_factory=list)
|
||||
# One path per direct parent: shortest path to that parent plus the
|
||||
# parent itself. Bounded by the number of parents, unlike "all paths".
|
||||
paths: List[List[str]] = field(default_factory=list)
|
||||
build_require: bool = False
|
||||
requires: List[str] = field(default_factory=list) # names of its own requirements
|
||||
settings: Dict[str, str] = field(default_factory=dict)
|
||||
|
|
@ -303,9 +306,14 @@ def _collect(
|
|||
if not dep.depth or depth < dep.depth:
|
||||
dep.depth, dep.path = depth, path
|
||||
for edge in node.dependants:
|
||||
parent = "your conanfile" if edge.src is root else edge.src.ref.name
|
||||
if edge.src is root:
|
||||
parent, via = "your conanfile", []
|
||||
else:
|
||||
parent = edge.src.ref.name
|
||||
via = paths.get(_node_key(edge.src), []) + [parent]
|
||||
if parent not in dep.parents:
|
||||
dep.parents.append(parent)
|
||||
dep.paths.append(via)
|
||||
dep.build_require = dep.build_require or any(e.build_require for e in node.dependants)
|
||||
for edge in node.dependencies:
|
||||
name = edge.dst.ref.name
|
||||
|
|
@ -508,18 +516,27 @@ def _visible_len(text: str) -> int:
|
|||
|
||||
|
||||
def _table(headers: List[str], rows: List[List[str]], pal: _Palette) -> List[str]:
|
||||
"""Render a column-aligned text table; cells may contain ANSI colour codes."""
|
||||
"""Render a column-aligned text table.
|
||||
|
||||
Cells may contain ANSI colour codes (alignment uses the visible width) and
|
||||
newlines: a multi-line cell spreads the row over several lines, with the
|
||||
other columns left blank on the continuation lines.
|
||||
"""
|
||||
split_rows = [[c.split("\n") for c in row] for row in rows]
|
||||
widths = [len(h) for h in headers]
|
||||
for row in rows:
|
||||
for i, cell in enumerate(row):
|
||||
widths[i] = max(widths[i], _visible_len(cell))
|
||||
for row in split_rows:
|
||||
for i, cell_lines in enumerate(row):
|
||||
widths[i] = max([widths[i]] + [_visible_len(line) for line in cell_lines])
|
||||
|
||||
def fmt(cells: List[str]) -> str:
|
||||
padded = [c + " " * (widths[i] - _visible_len(c)) for i, c in enumerate(cells)]
|
||||
return " ".join(padded).rstrip()
|
||||
|
||||
lines = [pal.bold(fmt(headers)), "-" * (sum(widths) + 2 * (len(widths) - 1))]
|
||||
lines.extend(fmt(r) for r in rows)
|
||||
for row in split_rows:
|
||||
height = max(len(cell_lines) for cell_lines in row)
|
||||
for n in range(height):
|
||||
lines.append(fmt([cell[n] if n < len(cell) else "" for cell in row]))
|
||||
return lines
|
||||
|
||||
|
||||
|
|
@ -539,17 +556,17 @@ def _override_kind(o: Override) -> str:
|
|||
return "explicit (override=True)" if o.explicit else "implicit (newer requirement downstream)"
|
||||
|
||||
|
||||
def _via(dep: Dep, arrow: str = " -> ") -> str:
|
||||
"""Shortest path to the package: '-' for direct, 'A -> B' for consumer -> A -> B -> pkg.
|
||||
def _via(dep: Dep, arrow: str = " -> ", newline: str = "\n") -> str:
|
||||
"""How the package is reached: '-' for direct, otherwise one line per path.
|
||||
|
||||
``(+N)`` marks N further packages that also require it directly; the
|
||||
dependency hierarchy section shows every path in full.
|
||||
``A -> B`` means consumer -> A -> B -> pkg. A package required by several
|
||||
others gets one line per parent (shortest path to that parent), shortest
|
||||
first.
|
||||
"""
|
||||
if dep.direct:
|
||||
return "-"
|
||||
via = arrow.join(dep.path)
|
||||
extra = len(dep.parents) - 1
|
||||
return via + (" (+%d)" % extra if extra > 0 else "")
|
||||
paths = sorted(dep.paths, key=lambda p: (len(p), [x.lower() for x in p]))
|
||||
return newline.join(arrow.join(p) for p in paths)
|
||||
|
||||
|
||||
def render_text(report: Report, color: bool = False) -> str:
|
||||
|
|
@ -668,7 +685,7 @@ def render_html(report: Report) -> str:
|
|||
w("<h2>Dependencies (%d) and binary availability</h2>" % len(report.deps))
|
||||
rows = []
|
||||
for dep in report.deps.values():
|
||||
row = [e(dep.ref), e(_via(dep, " \u2192 ")), e(dep.context)]
|
||||
row = [e(dep.ref), e(_via(dep, " \u2192 ")).replace("\n", "<br>"), e(dep.context)]
|
||||
for bt in report.build_types:
|
||||
status, pid, remote = dep.binaries[bt]
|
||||
cls = {"Remote": "ok", "Missing": "missing", "CacheOnly": "cacheonly"}.get(status, "na")
|
||||
|
|
|
|||
|
|
@ -242,8 +242,10 @@ def test_sorted_by_path(report):
|
|||
("libbar/1.1", ["libfoo"]),
|
||||
("libdeep/1.0", ["libfoo", "libbar"]),
|
||||
]
|
||||
# libqux is required by both the consumer and libbaz -> two parents.
|
||||
# libqux is required by both the consumer and libbaz -> two parents, but
|
||||
# it is direct, so the via column just says "-".
|
||||
assert sorted(report.deps["libqux#host"].parents) == ["libbaz", "your conanfile"]
|
||||
assert conandeps._via(report.deps["libqux#host"]) == "-"
|
||||
text = conandeps.render_text(report)
|
||||
assert re.search(r"hdr/1\.0\s+-\s+host", text)
|
||||
assert re.search(r"libbar/1\.1\s+libfoo\s+host", text)
|
||||
|
|
@ -253,6 +255,24 @@ def test_sorted_by_path(report):
|
|||
assert "<td>libdeep/1.0</td><td>libfoo \u2192 libbar</td><td>host</td>" in page
|
||||
|
||||
|
||||
def test_via_lists_every_parent_path():
|
||||
boost = conandeps.Dep(ref="boost/1.8", context="host", depth=2, path=["MyDepA"])
|
||||
boost.parents = ["MyDepA", "Zigma"]
|
||||
boost.paths = [["MyDepA"], ["MyDepB", "Zigma"]]
|
||||
assert conandeps._via(boost) == "MyDepA\nMyDepB -> Zigma"
|
||||
assert conandeps._via(boost, " > ", "; ") == "MyDepA; MyDepB > Zigma"
|
||||
# Multi-line cells spread the row; other columns stay aligned and blank.
|
||||
lines = conandeps._table(
|
||||
["package", "via", "ctx"],
|
||||
[["boost/1.8", conandeps._via(boost), "host"]],
|
||||
conandeps._Palette(False),
|
||||
)
|
||||
assert lines[2:] == [
|
||||
"boost/1.8 MyDepA host",
|
||||
" MyDepB -> Zigma",
|
||||
]
|
||||
|
||||
|
||||
def test_missing_binaries(report):
|
||||
missing = {d.ref: d.missing() for d in report.deps.values() if d.missing()}
|
||||
assert missing == {"libbar/1.1": ["Debug", "RelWithDebInfo"]}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue