Add support for generating a compile_commands.json file for clang-tidy

Change-Id: I3db1a69030f6ba6617ccaf3a1ebc319111145fe1
diff --git a/tools/actions/generate_compile_commands_json.py b/tools/actions/generate_compile_commands_json.py
new file mode 100755
index 0000000..d867698
--- /dev/null
+++ b/tools/actions/generate_compile_commands_json.py
@@ -0,0 +1,68 @@
+#!/usr/bin/python3
+
+# This reads the _compile_command files :generate_compile_commands_action
+# generates a outputs a compile_commands.json file at the top of the source
+# tree for things like clang-tidy to read.
+
+# Overall usage directions: run bazel with
+# --experimental_action_listener=//tools/actions:generate_compile_commands_listener
+# for all the files you want to use clang-tidy with and then run this script.
+# Afer that, `clang-tidy build_tests/gflags.cc` should work.
+
+import sys
+import pathlib
+import os.path
+import subprocess
+
+'''
+Args:
+  path: The pathlib.Path to _compile_command file.
+  command_directory: The directory commands are run from.
+
+Returns a string to stick in compile_commands.json.
+'''
+def _get_command(path, command_directory):
+  with path.open('r') as f:
+    contents = f.read().split('\0')
+    if len(contents) != 2:
+      # Old/incomplete file or something; silently ignore it.
+      return None
+    return '''{
+        "directory": "%s",
+        "command": "%s",
+        "file": "%s",
+      },''' % (command_directory, contents[0].replace('"', '\\"'), contents[1])
+
+'''
+Args:
+  path: A directory pathlib.Path to look for _compile_command files under.
+  command_directory: The directory commands are run from.
+
+Yields strings to stick in compile_commands.json.
+'''
+def _get_compile_commands(path, command_directory):
+  for f in path.iterdir():
+    if f.is_dir():
+      yield from _get_compile_commands(f, command_directory)
+    elif f.name.endswith('_compile_command'):
+      command = _get_command(f, command_directory)
+      if command:
+        yield command
+
+def main(argv):
+  source_path = os.path.join(os.path.dirname(__file__), '../..')
+  action_outs = os.path.join(source_path,
+                             'bazel-bin/../extra_actions',
+                             'tools/actions/generate_compile_commands_action')
+  command_directory = subprocess.check_output(
+      ('bazel', 'info', 'execution_root'),
+      cwd=source_path).decode('utf-8').rstrip()
+  commands = _get_compile_commands(pathlib.Path(action_outs), command_directory)
+  with open(os.path.join(source_path, 'compile_commands.json'), 'w') as f:
+    f.write('[')
+    for command in commands:
+      f.write(command)
+    f.write(']')
+
+if __name__ == '__main__':
+  sys.exit(main(sys.argv))