Import cros_deps_diff and cros_generate_deps_graph from crosutils.

Those tools already were reliant on cros_extract_deps which lives here,
thus move them across to cleanup crosutils.

Importation is as of 5e2e27345f60c5f572e3a50a5ff97a6b3a90ba1d from
crosutils; additionally, the scripts were modified to

1) fit into chromite.scripts.wrapper framework (argv/main modification)
2) updated to be pylint clean.
3) removed a set of evals, replacing them with json.loads calls.
   While you can mostly get away with eval'ing json data (since the format,
   at least for the data it was dumping, is python compatible syntax), this
   is wrong; thus just use json.loads directly.

BUG=chromium-os:33829
TEST=None

Change-Id: Iba33660d0051b256e47b11db017a0a25c19cb361
Reviewed-on: https://gerrit.chromium.org/gerrit/31218
Reviewed-by: Mike Frysinger <vapier@chromium.org>
Commit-Ready: Brian Harring <ferringb@chromium.org>
Tested-by: Brian Harring <ferringb@chromium.org>
diff --git a/scripts/cros_generate_deps_graphs.py b/scripts/cros_generate_deps_graphs.py
new file mode 100644
index 0000000..b7dffb2
--- /dev/null
+++ b/scripts/cros_generate_deps_graphs.py
@@ -0,0 +1,149 @@
+# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Generates pretty dependency graphs for Chrome OS packages."""
+
+import json
+import optparse
+import os
+import sys
+
+from chromite.lib import dot_helper
+
+
+NORMAL_COLOR = 'black'
+TARGET_COLOR = 'red'
+SEED_COLOR = 'green'
+CHILD_COLOR = 'grey'
+
+
+def GetReverseDependencyClosure(full_name, deps_map):
+  """Gets the closure of the reverse dependencies of a node.
+
+  Walks the tree along all the reverse dependency paths to find all the nodes
+  that transitively depend on the input node."""
+  s = set()
+  def GetClosure(name):
+    s.add(name)
+    node = deps_map[name]
+    for dep in node['rev_deps']:
+      if dep in s:
+        continue
+      GetClosure(dep)
+
+  GetClosure(full_name)
+  return s
+
+
+def GetOutputBaseName(node, options):
+  """Gets the basename of the output file for a node."""
+  return '%s_%s-%s.%s' % (node['category'], node['name'], node['version'],
+                          options.format)
+
+
+def AddNodeToSubgraph(subgraph, node, options, color):
+  """Gets the dot definition for a node."""
+  name = node['full_name']
+  href = None
+  if options.link:
+    filename = GetOutputBaseName(node, options)
+    href = '%s%s' % (options.base_url, filename)
+  subgraph.AddNode(name, name, color, href)
+
+
+
+def GenerateDotGraph(package, deps_map, options):
+  """Generates the dot source for the dependency graph leading to a node.
+
+  The output is a list of lines."""
+  deps = GetReverseDependencyClosure(package, deps_map)
+  node = deps_map[package]
+
+  # Keep track of all the emitted nodes so that we don't issue multiple
+  # definitions
+  emitted = set()
+
+  graph = dot_helper.Graph(package)
+
+  # Add all the children if we want them, all of them in their own subgraph,
+  # as a sink. Keep the arcs outside of the subgraph though (it generates
+  # better layout).
+  children_subgraph = None
+  if options.children and node['deps']:
+    children_subgraph = graph.AddNewSubgraph('sink')
+    for child in node['deps']:
+      child_node = deps_map[child]
+      AddNodeToSubgraph(children_subgraph, child_node, options, CHILD_COLOR)
+      emitted.add(child)
+      graph.AddArc(package, child)
+
+  # Add the package in its own subgraph. If we didn't have children, make it
+  # a sink
+  if children_subgraph:
+    rank = 'same'
+  else:
+    rank = 'sink'
+  package_subgraph = graph.AddNewSubgraph(rank)
+  AddNodeToSubgraph(package_subgraph, node, options, TARGET_COLOR)
+  emitted.add(package)
+
+  # Add all the other nodes, as well as all the arcs.
+  for dep in deps:
+    dep_node = deps_map[dep]
+    if not dep in emitted:
+      color = NORMAL_COLOR
+      if dep_node['action'] == 'seed':
+        color = SEED_COLOR
+      AddNodeToSubgraph(graph, dep_node, options, color)
+    for j in dep_node['rev_deps']:
+      graph.AddArc(j, dep)
+
+  return graph.Gen()
+
+
+def GenerateImages(data, options):
+  """Generate the output images for all the nodes in the input."""
+  deps_map = json.loads(data)
+
+  for package in deps_map:
+    lines = GenerateDotGraph(package, deps_map, options)
+
+    filename = os.path.join(options.output_dir,
+                            GetOutputBaseName(deps_map[package], options))
+
+    save_dot_filename = None
+    if options.save_dot:
+      save_dot_filename = filename + '.dot'
+
+    dot_helper.GenerateImage(lines, filename, options.format, save_dot_filename)
+
+
+def main(argv):
+  parser = optparse.OptionParser(usage='usage: %prog [options] input')
+  parser.add_option('-f', '--format', default='svg',
+                    help='Dot output format (png, svg, etc.).')
+  parser.add_option('-o', '--output-dir', default='.',
+                    help='Output directory.')
+  parser.add_option('-c', '--children', action='store_true',
+                    help='Also add children.')
+  parser.add_option('-l', '--link', action='store_true',
+                    help='Embed links.')
+  parser.add_option('-b', '--base-url', default='',
+                    help='Base url for links.')
+  parser.add_option('-s', '--save-dot', action='store_true',
+                    help='Save dot files.')
+  (options, inputs) = parser.parse_args(argv)
+
+  try:
+    os.makedirs(options.output_dir)
+  except OSError:
+    # The directory already exists.
+    pass
+
+  if not inputs:
+    GenerateImages(sys.stdin.read(), options)
+  else:
+    for i in inputs:
+      with open(i) as handle:
+        GenerateImages(handle.read(), options)