Add --svg and --dot args to soongdbg so you don't have to run graphviz by hand

Test: soongdbg between --svg ~/Desktop/foo.svg --label '"sdk_version="+.properties.Sdk_version' MODULE_1 MODULE_2
Change-Id: Idf5ee9a3ca2dc4b1fd3aa941fe641225336438fc
diff --git a/bin/soongdbg b/bin/soongdbg
index 6531e1b..c091c97 100755
--- a/bin/soongdbg
+++ b/bin/soongdbg
@@ -3,6 +3,7 @@
 import argparse
 import fnmatch
 import html
+import io
 import json
 import os
 import pathlib
@@ -192,14 +193,41 @@
             yield m
 
 
-def print_nodes(nodes, module_formatter):
-    print("digraph {")
+def print_args(parser):
+    parser.add_argument("--label", action="append", metavar="JQ_FILTER",
+                        help="jq query for each module metadata")
+
+    group = parser.add_argument_group("output formats",
+                                      "If no format is provided, a dot file will be written to"
+                                      + " stdout.")
+    output = group.add_mutually_exclusive_group()
+    output.add_argument("--dot", type=str, metavar="FILENAME",
+                        help="Write the graph to this file as dot (graphviz format)")
+    output.add_argument("--svg", type=str, metavar="FILENAME",
+                        help="Write the graph to this file as svg")
+
+
+def print_nodes(args, nodes, module_formatter):
+    # Generate the graphviz
+    dot = io.StringIO()
+    dot.write("digraph {\n")
     for node in nodes:
-        print(f"\"{node.id}\"[label={format_node_label(node, module_formatter)}];")
+        dot.write(f"\"{node.id}\"[label={format_node_label(node, module_formatter)}];\n")
         for dep in node.deps:
             if dep in nodes:
-                print(f"\"{node.id}\" -> \"{dep.id}\";")
-    print("}")
+                dot.write(f"\"{node.id}\" -> \"{dep.id}\";\n")
+    dot.write("}\n")
+    text = dot.getvalue()
+
+    # Write it somewhere
+    if args.dot:
+        with open(args.dot, "w") as f:
+            f.write(text)
+    elif args.svg:
+        subprocess.run(["dot", "-Tsvg", "-Nshape=box", "-o", args.svg],
+                              input=text, text=True, check=True)
+    else:
+        sys.stdout.write(text)
 
 
 def get_deps(nodes, root):
@@ -240,12 +268,12 @@
     def args(self, parser):
         parser.add_argument("module", nargs=2,
                             help="the two modules")
-        parser.add_argument("--label", action="append",
-                            help="jq query for each module metadata")
+        print_args(parser)
 
     def run(self, args):
         graph = load_graph()
-        print_nodes(graph.find_paths(args.module[0], args.module[1]), new_module_formatter(args))
+        print_nodes(args, graph.find_paths(args.module[0], args.module[1]),
+                    new_module_formatter(args))
 
 
 class DepsCommand:
@@ -254,8 +282,7 @@
     def args(self, parser):
         parser.add_argument("module", nargs="+",
                             help="Module to print dependencies of")
-        parser.add_argument("--label", action="append",
-                            help="jq query for each module metadata")
+        print_args(parser)
 
     def run(self, args):
         graph = load_graph()
@@ -270,7 +297,7 @@
             get_deps(nodes, root)
         if err:
             sys.exit(1)
-        print_nodes(nodes, new_module_formatter(args))
+        print_nodes(args, nodes, new_module_formatter(args))
 
 
 class IdCommand: