ui: Add planned dependency viz to check_imports and /widgets rules

Change-Id: I76dc010aa7dc9f189a3684284b55346895de9f20
diff --git a/python/tools/check_imports.py b/python/tools/check_imports.py
index ef4ff29..dd096fb 100755
--- a/python/tools/check_imports.py
+++ b/python/tools/check_imports.py
@@ -37,6 +37,91 @@
     os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
 UI_SRC_DIR = os.path.join(ROOT_DIR, 'ui', 'src')
 
+# Current plan for the dependency tree of the UI code (2023-09-21)
+# black = current
+# red = planning to remove
+# green = planning to add
+PLAN_DOT = """
+digraph g {
+    mithril [shape=rectangle, label="mithril"];
+    protos [shape=rectangle, label="//protos/perfetto"];
+
+    _gen [shape=ellipse, label="/gen"];
+    _base [shape=ellipse, label="/base"];
+    _core [shape=ellipse, label="/core"];
+    _engine [shape=ellipse, label="/engine"];
+
+    _frontend [shape=ellipse, label="/frontend" color=red];
+    _common [shape=ellipse, label="/common" color=red];
+    _controller [shape=ellipse, label="/controller" color=red];
+    _tracks [shape=ellipse, label="/tracks" color=red];
+
+    _widgets [shape=ellipse, label="/widgets"];
+
+    _public [shape=ellipse, label="/public"];
+    _plugins [shape=ellipse, label="/plugins"];
+    _chrome_extension [shape=ellipse, label="/chrome_extension"];
+    _trace_processor [shape=ellipse, label="/trace_processor" color="green"];
+    _protos [shape=ellipse, label="/protos" color="green"];
+    engine_worker_bundle [shape=cds, label="Engine worker bundle"];
+    frontend_bundle [shape=cds, label="Frontend bundle"];
+
+    engine_worker_bundle -> _engine;
+    frontend_bundle -> _core [color=green];
+    frontend_bundle -> _frontend [color=red];
+
+    _core -> _public;
+    _plugins -> _public;
+
+    _widgets -> _base;
+    _core -> _base;
+    _core -> _widgets;
+
+
+    _widgets -> mithril;
+    _plugins -> mithril;
+    _core -> mithril
+
+    _plugins -> _widgets;
+
+    _core -> _chrome_extension;
+
+    _frontend -> _widgets [color=red];
+    _common -> _core [color=red];
+    _frontend -> _core [color=red];
+    _controller -> _core [color=red];
+
+    _frontend -> _controller [color=red];
+    _frontend -> _common [color=red];
+    _controller -> _frontend  [color=red];
+    _controller -> _common [color=red];
+    _common -> _controller [color=red];
+    _common -> _frontend [color=red];
+    _tracks -> _frontend  [color=red];
+    _tracks -> _controller  [color=red];
+    _common -> _chrome_extension [color=red];
+
+    _core -> _trace_processor [color=green];
+
+    _engine -> _trace_processor [color=green];
+    _engine -> _common [color=red];
+    _engine -> _base;
+
+    _gen -> protos;
+    _core -> _gen [color=red];
+
+    _core -> _protos [color=green];
+    _protos -> _gen [color=green];
+    _trace_processor -> _protos [color=green];
+
+    _trace_processor -> _public [color=green];
+
+    npm_trace_processor [shape=cds, label="npm trace_processor" color="green"];
+    npm_trace_processor -> engine_worker_bundle [color="green"];
+    npm_trace_processor -> _trace_processor [color="green"];
+}
+"""
+
 
 class Failure(object):
 
@@ -183,6 +268,23 @@
         'chrome_extension must be a leaf',
     ),
 
+    # Widgets
+    NoDep(
+        r'/widgets/.*',
+        r'/frontend/.*',
+        'widgets should only depend on base',
+    ),
+    NoDep(
+        r'/widgets/.*',
+        r'/core/.*',
+        'widgets should only depend on base',
+    ),
+    NoDep(
+        r'/widgets/.*',
+        r'/plugins/.*',
+        'widgets should only depend on base',
+    ),
+
     # Fails at the moment as we have several circular dependencies. One
     # example:
     # ui/src/frontend/cookie_consent.ts
@@ -340,6 +442,11 @@
   return 0
 
 
+def do_plan_dot(options, _):
+  print(PLAN_DOT, file=sys.stdout)
+  return 0
+
+
 def main():
   parser = argparse.ArgumentParser(description=__doc__)
   parser.set_defaults(func=do_check)
@@ -371,6 +478,12 @@
       help='Don\'t show external dependencies',
   )
 
+  plan_dot_command = subparsers.add_parser(
+      'plan-dot',
+      help='Output planned dependency graph in dot format suitble for use in graphviz (e.g. ./tools/check_imports plan-dot | dot -Tpng -ograph.png)'
+  )
+  plan_dot_command.set_defaults(func=do_plan_dot)
+
   graph = collections.defaultdict(set)
   for path in all_source_files():
     for src, target in find_imports(path):