tools: Add tools/shim

//tools is a bit of a mess of various naming schemes:
foo_bar
foo-bar
foo_bar.py
foo-bar.py
foobar

Even just considering the Python scripts. Everything that doesn't end
with .py doesn't get formatted aromatically (which is one problem)
but worse they can't easily share code - which leads to a lot of
duplication.

It would be better if they could live in //python/tools and get those
advantages but some people are used to the naming of the existing
scripts.

The idea is for a tool named /tools/foo-bar you:
- mv it to //python/tools/foo_bar.py
- symlink //tools/foo-bar to //tools/shim

//tools/foo-bar then continues to work as an alias for
//python/tools/foo_bar.py but you get the advantage of formatting
& code sharing.

Change-Id: If7ec4fddb0a613f02ddec1ad59ecddf880e4b428
diff --git a/tools/check_imports b/python/tools/check_imports.py
similarity index 81%
rename from tools/check_imports
rename to python/tools/check_imports.py
index 3233e47..756d99c 100755
--- a/tools/check_imports
+++ b/python/tools/check_imports.py
@@ -12,7 +12,6 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-
 """
 Enforce import rules for https://ui.perfetto.dev.
 Directory structure encodes ideas about the expected dependency graph
@@ -34,11 +33,13 @@
 import collections
 import argparse
 
-ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+ROOT_DIR = os.path.dirname(
+    os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
 UI_SRC_DIR = os.path.join(ROOT_DIR, 'ui', 'src')
 
 
 class Failure(object):
+
   def __init__(self, path, rule):
     self.path = path
     self.rule = rule
@@ -46,16 +47,17 @@
   def __str__(self):
     nice_path = ["ui/src" + name + ".ts" for name in self.path]
     return ''.join([
-      'Forbidden dependency path:\n\n ',
-      '\n    -> '.join(nice_path),
-      '\n',
-      '\n',
-      str(self.rule),
-      '\n',
+        'Forbidden dependency path:\n\n ',
+        '\n    -> '.join(nice_path),
+        '\n',
+        '\n',
+        str(self.rule),
+        '\n',
     ])
 
 
 class NoDirectDep(object):
+
   def __init__(self, src, dst, reasoning):
     self.src = src
     self.dst = dst
@@ -73,6 +75,7 @@
 
 
 class NoDep(object):
+
   def __init__(self, src, dst, reasoning):
     self.src = src
     self.dst = dst
@@ -94,38 +97,38 @@
 #   files matching regex 'b' - but they may indirectly depend on them.
 # NoDep(a, b) = as above but 'a' may not even transitively import 'b'.
 RULES = [
-  NoDirectDep(
-    r'/plugins/.*',
-    r'/core/.*',
-    'instead plugins should depend on the API exposed at ui/src/api.',
-  ),
-  NoDirectDep(
-    r'/tracks/.*',
-    r'/core/.*',
-    'instead tracks should depend on the API exposed at ui/src/api.',
-  ),
-  NoDep(
-    r'/core/.*',
-    r'/plugins/.*',
-    'otherwise the plugins are no longer optional.',
-  ),
-  # Fails at the moment due to:
-  # ui/src/base/comparison_utils.ts
-  #    -> ui/src/common/query_result.ts
-  #    -> ui/src/core/static_initializers.ts
-  #NoDep(
-  #  r'/base/.*',
-  #  r'/core/.*',
-  #  'core should depend on base not the other way round',
-  #),
+    NoDirectDep(
+        r'/plugins/.*',
+        r'/core/.*',
+        'instead plugins should depend on the API exposed at ui/src/api.',
+    ),
+    NoDirectDep(
+        r'/tracks/.*',
+        r'/core/.*',
+        'instead tracks should depend on the API exposed at ui/src/api.',
+    ),
+    NoDep(
+        r'/core/.*',
+        r'/plugins/.*',
+        'otherwise the plugins are no longer optional.',
+    ),
+    # Fails at the moment due to:
+    # ui/src/base/comparison_utils.ts
+    #    -> ui/src/common/query_result.ts
+    #    -> ui/src/core/static_initializers.ts
+    #NoDep(
+    #  r'/base/.*',
+    #  r'/core/.*',
+    #  'core should depend on base not the other way round',
+    #),
 ]
 
 
 def all_source_files():
   for root, dirs, files in os.walk(UI_SRC_DIR, followlinks=False):
-     for name in files:
-       if name.endswith('.ts') and not name.endswith('.d.ts'):
-         yield os.path.join(root, name)
+    for name in files:
+      if name.endswith('.ts') and not name.endswith('.d.ts'):
+        yield os.path.join(root, name)
 
 
 def is_dir(path, cache={}):
@@ -162,7 +165,6 @@
   return not path.startswith('/')
 
 
-
 def bfs(graph, src):
   seen = set()
   queue = [(src, [])]
@@ -210,6 +212,7 @@
 
 
 def do_dot(options, graph):
+
   def simplify(path):
     if is_external_dep(path):
       return path
@@ -244,13 +247,17 @@
   parser.set_defaults(func=do_check)
   subparsers = parser.add_subparsers()
 
-  check_command = subparsers.add_parser('check', help='Check the rules (default)')
+  check_command = subparsers.add_parser(
+      'check', help='Check the rules (default)')
   check_command.set_defaults(func=do_check)
 
   desc_command = subparsers.add_parser('desc', help='Print the rules')
   desc_command.set_defaults(func=do_desc)
 
-  dot_command = subparsers.add_parser('dot', help='Output dependency graph in dot format suitble for use in graphviz (e.g. ./tools/check_imports dot | dot -Tpng -ograph.png)')
+  dot_command = subparsers.add_parser(
+      'dot',
+      help='Output dependency graph in dot format suitble for use in graphviz (e.g. ./tools/check_imports dot | dot -Tpng -ograph.png)'
+  )
   dot_command.set_defaults(func=do_dot)
   dot_command.add_argument(
       '--simplify',
@@ -275,4 +282,3 @@
 
 if __name__ == '__main__':
   sys.exit(main())
-
diff --git a/tools/check_imports b/tools/check_imports
new file mode 120000
index 0000000..478ff5c
--- /dev/null
+++ b/tools/check_imports
@@ -0,0 +1 @@
+shim
\ No newline at end of file
diff --git a/tools/shim b/tools/shim
new file mode 100755
index 0000000..fff429e
--- /dev/null
+++ b/tools/shim
@@ -0,0 +1,14 @@
+#!/usr/bin/env python3
+# The idea is for a tool named /tools/foo-bar you mv it to
+# /python/tools/foo_bar.py then softlink /tools/shim to /tools/foo-bar.
+# /tools/foo-bar then continues to work as an alias for
+# /python/tools/foo_bar.py but you get the advantage of formatting
+# & code sharing.
+import os
+import sys
+ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+NAME = os.path.basename(sys.argv[0]).replace('-', '_')
+NAME = NAME if NAME.endswith('.py') else NAME + '.py'
+PATH = os.path.join(ROOT_DIR, 'python', 'tools', NAME)
+assert os.path.isfile(PATH), f'Shim target {PATH} does not exist.'
+os.execv(PATH, sys.argv)