Add a tool that will tell which git projects are used for a build target

See build/make/tools/whichgit -h for the options

Test: build/make/tools/whichgit --modules com.google.android.tzdata
Test: build/make/tools/whichgit --modules framework
Test: TARGET_BUILD_APPS=com.google.android.tzdata4 build/make/tools/whichgit --products mainline_modules_arm64 --variants eng --modules $TARGET_BUILD_APPS --why "*"
Change-Id: I05d91b85575a920b3ec9f5959e2ce463a45e63a3
diff --git a/tools/whichgit b/tools/whichgit
new file mode 100755
index 0000000..24d6d87
--- /dev/null
+++ b/tools/whichgit
@@ -0,0 +1,109 @@
+#!/usr/bin/env python3
+
+import argparse
+import os
+import subprocess
+import sys
+
+def get_build_var(var):
+  return subprocess.run(["build/soong/soong_ui.bash","--dumpvar-mode", var],
+                        check=True, capture_output=True, text=True).stdout.strip()
+
+
+def get_sources(modules):
+  result = subprocess.run(["./prebuilts/build-tools/linux-x86/bin/ninja", "-f",
+                           "out/combined-" + os.environ["TARGET_PRODUCT"] + ".ninja",
+                           "-t", "inputs", "-d", ] + modules,
+                          stderr=subprocess.STDOUT, stdout=subprocess.PIPE, check=False, text=True)
+  if result.returncode != 0:
+    sys.stderr.write(result.stdout)
+    sys.exit(1)
+  return set([f for f in result.stdout.split("\n") if not f.startswith("out/")])
+
+
+def m_nothing():
+  result = subprocess.run(["build/soong/soong_ui.bash", "--build-mode", "--all-modules",
+                           "--dir=" + os.getcwd(), "nothing"],
+                           check=False, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, text=True)
+  if result.returncode != 0:
+    sys.stderr.write(result.stdout)
+    sys.exit(1)
+
+
+def get_git_dirs():
+  text = subprocess.run(["repo","list"], check=True, capture_output=True, text=True).stdout
+  return [line.split(" : ")[0] + "/" for line in text.split("\n")]
+
+
+def get_referenced_projects(git_dirs, files):
+  # files must be sorted
+  referenced_dirs = set()
+  prev_dir = None
+  for f in files:
+    # Optimization is ~5x speedup for large sets of files
+    if prev_dir:
+      if f.startswith(prev_dir):
+        referenced_dirs.add(d)
+        continue
+    for d in git_dirs:
+      if f.startswith(d):
+        referenced_dirs.add(d)
+        prev_dir = d
+        break
+  return [d[0:-1] for d in referenced_dirs]
+
+
+def main(argv):
+  # Argument parsing
+  ap = argparse.ArgumentParser(description="List the required git projects for the given modules")
+  ap.add_argument("--products", nargs="*",
+                  help="The TARGET_PRODUCT to check. If not provided just uses whatever has"
+                        + " already been built")
+  ap.add_argument("--variants", nargs="*",
+                  help="The TARGET_BUILD_VARIANTS to check. If not provided just uses whatever has"
+                        + " already been built, or eng if --products is supplied")
+  ap.add_argument("--modules", nargs="*",
+                  help="The build modules to check, or droid it not supplied")
+  ap.add_argument("--why", nargs="*",
+                  help="Also print the input files used in these projects, or \"*\" for all")
+  args = ap.parse_args(argv[1:])
+
+  modules = args.modules if args.modules else ["droid"]
+
+  # Get the list of sources for all of the requested build combos
+  if not args.products and not args.variants:
+    sources = get_sources(modules)
+  else:
+    if not args.products:
+      sys.stderr.write("Error: --products must be supplied if --variants is supplied")
+      sys.exit(1)
+    sources = set()
+    build_num = 1
+    for product in args.products:
+      os.environ["TARGET_PRODUCT"] = product
+      variants = args.variants if args.variants else ["user", "userdebug", "eng"]
+      for variant in variants:
+        sys.stderr.write(f"Analyzing build {build_num} of {len(args.products)*len(variants)}\r")
+        os.environ["TARGET_BUILD_VARIANT"] = variant
+        m_nothing()
+        sources.update(get_sources(modules))
+        build_num += 1
+    sys.stderr.write("\n\n")
+
+  sources = sorted(sources)
+
+  # Print the list of git directories that has one or more of the sources in it
+  for project in sorted(get_referenced_projects(get_git_dirs(), sources)):
+    print(project)
+    if "*" in args.why or project in args.why:
+      prefix = project + "/"
+      for f in sources:
+        if f.startswith(prefix):
+          print("  " + f)
+
+
+if __name__ == "__main__":
+  sys.exit(main(sys.argv))
+
+
+# vim: set ts=2 sw=2 sts=2 expandtab nocindent tw=100: