diff --git a/README.md b/README.md index 5b59675..a6501e0 100644 --- a/README.md +++ b/README.md @@ -92,12 +92,12 @@ profile: arch=x86_64, compiler=gcc, compiler.libcxx=libstdc++11, compiler.versio Dependencies (4) and binary availability -package kind ctx Release Debug RelWithDebInfo ----------------------------------------------------------- -MyDepA/1.0 direct host knor knor knor -boost/1.8 indirect host knor knor knor -MyDepB/1.0 direct host knor knor knor -Zigma/1.0 indirect host knor knor MISSING +package kind ctx Release Debug RelWithDebInfo +-------------------------------------------------------------- +MyDepA/1.0 direct host knor knor knor +MyDepB/1.0 direct host knor knor knor +boost/1.8 indirect (2) host knor knor knor +Zigma/1.0 indirect (2) host knor knor MISSING Missing binaries (1) @@ -127,7 +127,11 @@ environment variable turns that off, and redirecting stdout to a file never produces colour codes unless you pass `--color always`. The HTML report uses the same colour coding. -Reading the table: +Reading the table: rows are sorted with direct dependencies first +(alphabetically), then indirect ones by increasing level of indirection – +`indirect (2)` is required by a direct dependency, `indirect (3)` by one of +those, and so on (shortest path counts). + * `knor` (a remote name) – the remote has a binary for the package_id your profile produces. Hover a cell in the HTML report to see the package_id. diff --git a/conandeps.py b/conandeps.py index 30119dc..e53352d 100755 --- a/conandeps.py +++ b/conandeps.py @@ -81,6 +81,9 @@ class Dep: ref: str context: str # "host" or "build" direct: bool = False + # Shortest path length from the consumer: 1 = direct, 2 = required by a + # direct dependency, ... Used to order the table by level of indirection. + depth: int = 0 build_require: bool = False requires: List[str] = field(default_factory=list) # names of its own requirements settings: Dict[str, str] = field(default_factory=dict) @@ -170,6 +173,10 @@ def _node_key(node) -> str: return "%s#%s" % (node.ref.name, node.context) +def _key_of(dep: Dep) -> str: + return "%s#%s" % (dep.name, dep.context) + + def _explicit_overrides(graph) -> set: """Set of (declaring ref or 'your conanfile', package name) for override=True requires.""" result = set() @@ -241,6 +248,22 @@ class _RemoteIndex: return found +def _depths(root) -> Dict[str, int]: + """Breadth-first shortest distance from the root for every node key.""" + depths: Dict[str, int] = {} + frontier = [(e.dst, 1) for e in root.dependencies] + while frontier: + next_frontier = [] + for node, depth in frontier: + key = _node_key(node) + if key in depths: + continue + depths[key] = depth + next_frontier.extend((e.dst, depth + 1) for e in node.dependencies) + frontier = next_frontier + return depths + + def _collect( graph, build_type: str, @@ -255,6 +278,7 @@ def _collect( """ root = graph.root direct = {_node_key(e.dst) for e in root.dependencies} + depths = _depths(root) for node in graph.nodes: if node is root: continue @@ -264,6 +288,8 @@ def _collect( dep = Dep(ref=str(node.ref), context=node.context) deps[key] = dep dep.direct = dep.direct or key in direct + depth = depths.get(key, 0) + dep.depth = min(dep.depth, depth) if dep.depth else depth 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 @@ -347,6 +373,10 @@ def build_report( overrides = _parse_overrides(log.getvalue(), explicit) _progress(quiet, "%d overrides detected" % len(overrides)) + # Direct dependencies first, then by increasing level of indirection, + # alphabetically within a level. Build-context nodes sort after host. + ordered = sorted(deps.values(), key=lambda d: (d.depth, d.context != "host", d.name.lower())) + deps = OrderedDict((_key_of(d), d) for d in ordered) for dep in deps.values(): # keep column order stable dep.binaries = OrderedDict( (bt, dep.binaries.get(bt, ("n/a", "", None))) for bt in build_types @@ -490,6 +520,11 @@ def _override_kind(o: Override) -> str: return "explicit (override=True)" if o.explicit else "implicit (newer requirement downstream)" +def _kind(dep: Dep) -> str: + """'direct', or 'indirect (N)' where N is the number of hops from the consumer.""" + return "direct" if dep.direct else "indirect (%d)" % dep.depth + + def render_text(report: Report, color: bool = False) -> str: pal = _Palette(color) out: List[str] = [] @@ -503,7 +538,7 @@ def render_text(report: Report, color: bool = False) -> str: w(pal.bold("Dependencies (%d) and binary availability" % len(report.deps))) w("") rows = [ - [dep.ref, "direct" if dep.direct else "indirect", dep.context] + [dep.ref, _kind(dep), dep.context] + [_cell(*dep.binaries[bt], pal) for bt in report.build_types] for dep in report.deps.values() ] @@ -606,7 +641,7 @@ def render_html(report: Report) -> str: w("