[131795] contrib/port-depgraph/port_deptree.py

snc at macports.org snc at macports.org
Sun Jan 18 11:35:49 PST 2015


Revision: 131795
          https://trac.macports.org/changeset/131795
Author:   snc at macports.org
Date:     2015-01-18 11:35:49 -0800 (Sun, 18 Jan 2015)
Log Message:
-----------
contrib: add port_deptree.py, #46602

Modified Paths:
--------------
    contrib/port-depgraph/port_deptree.py

Modified: contrib/port-depgraph/port_deptree.py
===================================================================
--- contrib/port-depgraph/port_deptree.py	2015-01-18 19:34:53 UTC (rev 131794)
+++ contrib/port-depgraph/port_deptree.py	2015-01-18 19:35:49 UTC (rev 131795)
@@ -1 +1,198 @@
-<html><body>You are being <a href="https://raw.githubusercontent.com/Synss/macports_deptree/master/port_deptree.py">redirected</a>.</body></html>
\ No newline at end of file
+#!/usr/bin/env python
+# Copyright (c) 2014, 2015 Mathias Laurin
+# BSD 3-Clause License (http://opensource.org/licenses/BSD-3-Clause)
+
+r"""Print all dependencies required to build a port as a graph.
+
+Usage:
+    port_deptree.py [--min] PORTNAME [VARIANTS ...]
+
+Example:
+    port_deptree.py irssi -perl | dot -Tpdf -oirssi.pdf
+    port_deptree.py --min $(port echo requested and outdated)\
+            | dot -Tpdf | open -fa Preview
+
+"""
+from __future__ import print_function
+import sys
+_stdout, sys.stdout = sys.stdout, sys.stderr
+import subprocess
+from itertools import product
+from altgraph import Dot, Graph
+__version__ = "0.9"
+
+
+def _(bytes):
+    return bytes.decode("utf-8")
+
+
+class NodeData(object):
+
+    __slots__ = ("type", "status")
+
+    def __init__(self, type):
+        self.type = type  # in (root, vertex, leaf)
+        self.status = "missing"  # in (installed, outdated, missing)
+
+
+class EdgeData(object):
+
+    __slots__ = ("section",)
+
+    def __init__(self, section):
+        self.section = section
+
+
+def get_deps(portname, variants):
+    """Return `section, depname` dependents of `portname` with `variants`."""
+    process = ["port", "deps", portname]
+    process.extend(variants)
+    for line in subprocess.Popen(
+            process, stdout=subprocess.PIPE).stdout.readlines():
+        section, sep, children = _(line).partition(":")
+        if not section.endswith("Dependencies"):
+            continue
+        for child in [child.strip() for child in children.split(",")]:
+            yield section.split()[0].lower(), child
+
+
+def make_graph(graph, portname, variants):
+    """Traverse dependency tree of `portname` with `variants`.
+
+    Args:
+        portname (str): The name of a port.
+        variants (list): The variants to apply to `portname`.
+
+    """
+    def call(cmd):
+        return subprocess.Popen(
+            cmd.split(), stdout=subprocess.PIPE).stdout.readlines()
+
+    installed = set(_(line.split()[0]) for line in call("port echo installed"))
+    outdated = set(_(line.split()[0]) for line in call("port echo outdated"))
+    visited = set(node for node in graph)
+
+    def traverse(parent):
+        """Recursively traverse dependencies to `parent`."""
+        if parent in visited:
+            return
+        else:
+            visited.add(parent)
+        node_data = graph.node_data(parent)
+        if parent in outdated:
+            node_data.status = "outdated"
+        elif parent in installed:
+            node_data.status = "installed"
+        for section, child in get_deps(parent.strip('"'), variants):
+            if node_data.type is not "root":
+                node_data.type = "vertex"
+            if child not in graph:
+                graph.add_node(child, NodeData("leaf"))
+            graph.add_edge(parent, child, EdgeData(section),
+                           create_nodes=False)
+            traverse(child)
+    graph.add_node(portname, NodeData("root"))
+    traverse(portname)
+
+
+def reduce_graph(graph, root):
+    """Keep only "missing" and "outdated" nodes and their parents."""
+    for node in graph.forw_bfs(root):
+        node_data = graph.node_data(node)
+        if node_data.type is "root" or node_data.status is not "installed":
+            continue
+        children = set(graph.tail(edge) for edge in graph.out_edges(node))
+        if not set(("outdated", "missing")).intersection(
+                data.status for data in (graph.node_data(child)
+                                         for child in children)):
+            parents = set(graph.head(edge) for edge in graph.inc_edges(node))
+            for parent, child in product(parents, children):
+                if not graph.edge_by_node(parent, child):
+                    graph.add_edge(parent, child, EdgeData("virtual"))
+            graph.hide_node(node)
+
+
+def make_dot(graph):
+    """Convert the graph to a dot file.
+
+    Node and edge styles is obtained from the corresponding data.
+
+    Args:
+        graph (Graph.Graph): The graph.
+
+    Returns:
+        Dot.Dot: The dot file generator.
+
+    """
+    dot = Dot.Dot(graph, graphtype="digraph")
+    dot.style(overlap=False, bgcolor="transparent")
+    for node in graph:
+        node_data = graph.node_data(node)
+        shape = ("circle" if node_data.type is "vertex" else "doublecircle")
+        color, fillcolor = dict(missing=("red", "moccasin"),
+                                outdated=("forestgreen", "lightblue")
+                                ).get(node_data.status, ("black", "white"))
+        dot.node_style(node, shape=shape,
+                       style="filled", fillcolor=fillcolor, color=color)
+    for edge, edge_data, head, tail in (graph.describe_edge(edge)
+                                        for edge in graph.edge_list()):
+        section = edge_data.section
+        color = dict(fetch="forestgreen",
+                     extract="darkgreen",
+                     build="blue",
+                     runtime="red",
+                     virtual="darkgray").get(section, "black")
+        style = dict(virtual="dashed").get(section, "solid")
+        dot.edge_style(
+            head, tail,
+            label=section if section not in ("library", "virtual") else "",
+            style=style, color=color, fontcolor=color)
+    return dot
+
+
+def make_stats(graph):
+    """Return the stats for `graph`."""
+    stats = dict(missing=0,
+                 installed=0,
+                 outdated=0,
+                 total=graph.number_of_nodes())
+    for node in graph:
+        node_data = graph.node_data(node)
+        stats[node_data.status] += 1
+    return stats
+
+
+if __name__ == '__main__':
+    graph = Graph.Graph()
+    reduce = False
+    commandline = {}
+    try:
+        if not sys.argv[1:]:
+            raise RuntimeError
+        for arg in sys.argv[1:]:
+            if arg.startswith("@"):
+                continue
+            elif arg.startswith("--min"):
+                reduce = True
+            elif not (arg.startswith("+") or arg.startswith("-")):
+                portname = arg
+                commandline[portname] = []
+            else:
+                commandline[portname].append(arg)
+    except:
+        print(__doc__, file=sys.stderr)
+        exit(1)
+    for portname, variants in commandline.items():
+        print("Calculating dependencies for", portname, *variants,
+              file=sys.stderr)
+        make_graph(graph, portname, variants)
+    stats = make_stats(graph)
+    if reduce:
+        for portname in commandline:
+            reduce_graph(graph, portname)
+    print("Total:", stats["total"],
+          "(%i" % stats["outdated"], "upgrades,", stats["missing"], "new)",
+          file=sys.stderr)
+    for line in make_dot(graph).iterdot():
+        print(line, file=_stdout)
+    _stdout.flush()
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/macports-changes/attachments/20150118/a09b1315/attachment.html>


More information about the macports-changes mailing list