From e18633410cc7e6c6e89a85324016fd2c3cbe0bea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9rome=20Perrin?= Date: Mon, 18 Jun 2018 11:28:01 +0200 Subject: [PATCH] New experimental "graph" command Produce a graphviz compatible graph of the parts dependencies. fdp layout option seems to produce more readable graphs in this case. Example invocation: buildout graph | fdp -t svg -o buildout.svg --- src/zc/buildout/buildout.py | 72 ++++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/src/zc/buildout/buildout.py b/src/zc/buildout/buildout.py index 84f9928e..8e1a3d20 100644 --- a/src/zc/buildout/buildout.py +++ b/src/zc/buildout/buildout.py @@ -1271,6 +1271,69 @@ class Buildout(DictMixin): def annotate(self, args=None): _print_annotate(self._annotated) + def graph(self, args=None): + nodes = set([]) + edges = set([]) + + def getRecipe(part): + recipe = self._raw[part].get('recipe') + if recipe: + return recipe + macro = self._raw[part].get('<') + if macro: + return getRecipe(macro) + + _template_split = re.compile('([$]{[^}]*})').split + + print_("digraph {") + for part in self._raw['buildout']['parts'].splitlines(): + if part: + nodes.add(part) + print_(' buildout -> "%s"' % part) + for option, value in self._raw[part].items(): + if value == '<': # XXX macro + nodes.add(option) + edges.add((part, option)) + + if "${" in value: + for sub in _template_split(value): + if sub.startswith("${"): + dependency = sub[2:].split(":")[0] + if dependency and dependency != 'buildout': + nodes.add(dependency) + edges.add((part, dependency)) + + for (part, dependency) in edges: + print_( + ' "%s" -> "%s"' % ( + part, + dependency)) + + recipe_colors = { + 'slapos.recipe.cmmi': '#00AA00', + 'slapos.recipe.build': '#FF0000', + 'slapos.recipe.build:gitclone': '#996666', + 'slapos.recipe.build:download-unpacked': '#FF00FF', + 'zc.recipe.egg': '#333399', + 'zc.recipe.egg:custom': '#444499', + 'zc.recipe.egg:scripts': '#666699', + } + + for node in nodes: + recipe = getRecipe(node) or '' + print_( +'''"{part_name}" [ + shape=record + label="{{{part_name}|{recipe}}}" + style=filled + fillcolor="{color}" +] +'''.format(part_name=node, + recipe=recipe, + color=recipe_colors.get(recipe, "#FFFFFF"))) + + print_("}") + def print_options(self): for section in sorted(self._data): if section == 'buildout' or section == self['buildout']['versions']: @@ -2122,6 +2185,13 @@ Commands: The script can be given either as a script path or a path to a directory containing a setup.py script. + graph + + Produce a graphviz compatible graph of the parts dependencies. + fdp layout option seems to produce more readable graphs in this case. + Example invocation: + buildout graph | fdp -t svg -o buildout.svg + annotate Display annotated sections. All sections are displayed, sorted @@ -2229,7 +2299,7 @@ def main(args=None): command = args.pop(0) if command not in ( 'install', 'bootstrap', 'runsetup', 'setup', 'init', - 'annotate', + 'annotate', 'graph' ): _error('invalid command:', command) else: -- 2.30.9