Add tools to build AOSP and to run the emulator

This makes it easier to work with an AOSP checkout inside a R8 checkout
without having to do all the AOSP setup and build magic manually.

Based on the code already in tools/test_android_cts.py.

Change-Id: I65f43e178afe1ca2db4d407a777107b785229c80
diff --git a/tools/build_aosp.py b/tools/build_aosp.py
new file mode 100755
index 0000000..8bb9e11
--- /dev/null
+++ b/tools/build_aosp.py
@@ -0,0 +1,97 @@
+#!/usr/bin/env python
+# Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+from os.path import join
+from glob import glob
+from itertools import chain
+from subprocess import check_call
+import argparse
+import multiprocessing
+import os
+import sys
+
+import gradle
+import utils
+
+AOSP_HELPER_SH = join(utils.REPO_ROOT, 'scripts', 'aosp_helper.sh')
+
+DEFAULT_LUNCH = 'aosp_x86-eng'
+
+J_DEFAULT = multiprocessing.cpu_count() - 2
+
+EXIT_FAILURE = 1
+
+def parse_arguments():
+  parser = argparse.ArgumentParser(
+      description = 'Checkout the AOSP source tree.')
+  parser.add_argument('--aosp-root',
+                      help='Root of the AOSP checkout. ' +
+                           'Defaults to current working directory.',
+                      default=os.getcwd())
+  parser.add_argument('--lunch',
+                      help='Build menu. ' +
+                           'Defaults to ' + DEFAULT_LUNCH + '.',
+                      default=DEFAULT_LUNCH)
+  parser.add_argument('--tool',
+                      choices = ['jack', 'd8', 'default'],
+                      default = 'd8',
+                      help='Compiler tool to use. Defaults to d8.')
+  parser.add_argument('--clean-dex',
+      action = 'store_true',
+      help = 'Remove all dex files before the build. By default they'
+      " are removed only if '--tool=d8' and if they are older than the D8 tool")
+  parser.add_argument('-j',
+                      help='Projects to fetch simultaneously. ' +
+                           'Defaults to ' + str(J_DEFAULT) + '.',
+                      type=int,
+                      default=J_DEFAULT)
+  return parser.parse_args()
+
+def setup_and_clean_dex(aosp_root, tool, clean_dex):
+  out = join(aosp_root, 'out')
+  utils.makedirs_if_needed(out)
+
+  # remove dex files older than the current d8 tool
+  counter = 0
+  if tool == 'd8' or clean_dex:
+    if not clean_dex:
+      d8jar_mtime = os.path.getmtime(utils.D8_JAR)
+    dex_files = (chain.from_iterable(glob(join(x[0], '*.dex'))
+      for x in os.walk(out)))
+    for f in dex_files:
+      if clean_dex or os.path.getmtime(f) <= d8jar_mtime:
+        os.remove(f)
+        counter += 1
+  if counter > 0:
+    print('Removed {} dex files.'.format(counter))
+
+def build_aosp(aosp_root, lunch, tool, concurrency):
+  jack_option = 'ANDROID_COMPILE_WITH_JACK=' \
+      + ('true' if tool == 'jack' else 'false')
+
+  # DX_ALT_JAR need to be cleared if not set, for 'make' to work properly
+  alt_jar_option = 'DX_ALT_JAR='
+  if tool == 'd8':
+    alt_jar_option += utils.COMPATDX_JAR
+
+  j_option = '-j' + str(concurrency);
+  print("-- Building Android image with 'make {} {} {}'." \
+    .format(j_option, jack_option, alt_jar_option))
+  check_call([AOSP_HELPER_SH, lunch, 'make', j_option,
+              jack_option, alt_jar_option], cwd = aosp_root)
+
+def Main():
+  args = parse_arguments()
+
+  # Build the required tools.
+  if args.tool == 'd8':
+    gradle.RunGradle(['d8', 'd8logger', 'compatdx'])
+
+  setup_and_clean_dex(args.aosp_root, args.tool, args.clean_dex)
+
+  build_aosp(args.aosp_root, args.lunch, args.tool, args.j)
+
+if __name__ == '__main__':
+  sys.exit(Main())