Add --local-dist argument to specify a local dist dir.

As an alternative to --build, for use with local testing.

Cherry-picked from https://r.android.com/1729793.

Test: prebuilts/runtime/mainline/update.py
Test: prebuilts/runtime/mainline/update.py \
        --build 17 --local-dist out/dist
Test: cd aosp
      build/soong/scripts/build-mainline-modules.sh
      cd ../master-art
      prebuilts/runtime/mainline/update.py --local-dist ../aosp/out/dist
Bug: 190194345
Change-Id: Ie6fc3c607c674ce7d1542ea7f93b71304ed7cca4
Merged-In: Ie6fc3c607c674ce7d1542ea7f93b71304ed7cca4
diff --git a/common/python/update_prebuilts.py b/common/python/update_prebuilts.py
index ffe43d3..2bbdd3e 100644
--- a/common/python/update_prebuilts.py
+++ b/common/python/update_prebuilts.py
@@ -13,11 +13,14 @@
 # limitations under the License.
 #
 """Downloads prebuilt from the build server."""
+
 import argparse
 import logging
 import os
+import os.path
 import shutil
 import stat
+import sys
 import textwrap
 
 
@@ -64,6 +67,14 @@
     check_call(cmd)
 
 
+def copy_artifact(local_dist, target, name):
+    """Copies artifact from a local dist directory."""
+    source_path = (target[6:] if target.startswith('local:')
+                   else os.path.join(local_dist, name))
+    logger().info('Copying from %s', source_path)
+    shutil.copyfile(source_path, os.path.basename(name))
+
+
 def start_branch(build):
     """Creates a new branch in the project."""
     branch_name = 'update-' + (build or 'latest')
@@ -75,10 +86,16 @@
     """Commits the new prebuilts."""
     logger().info('Making commit')
     check_call(['git', 'add'] + add_paths)
-    message = textwrap.dedent("""\
-        Update {prebuilts} prebuilts to build {build}.
+    if build:
+        message = textwrap.dedent("""\
+            Update {prebuilts} prebuilts to build {build}.
 
-        Taken from branch {branch}.""").format(prebuilts=prebuilts, branch=branch, build=build)
+            Taken from branch {branch}.""").format(
+                prebuilts=prebuilts, branch=branch, build=build)
+    else:
+        message = (
+            'DO NOT SUBMIT: Update {prebuilts} prebuilts from local build.'
+            .format(prebuilts=prebuilts))
     if commit_message_note:
         message += "\n\n" + commit_message_note
     check_call(['git', 'commit', '-m', message])
@@ -107,16 +124,16 @@
     check_call(['rm', '-rf'] + old_files)
 
 
-def install_new_files(branch, build, install_list, extracted_list):
+def install_new_files(branch, build, local_dist, install_list, extracted_list):
     """Installs the new release."""
     for entry in install_list:
-        install_entry(branch, build, entry)
+        install_entry(branch, build, local_dist, entry)
     for entry in extracted_list:
         if entry.need_strip:
             check_call(['strip', entry.name])
 
 
-def install_entry(branch, build, entry):
+def install_entry(branch, build, local_dist, entry):
     """Installs one file specified by entry."""
     target = entry.target
     name = entry.name
@@ -126,7 +143,10 @@
     need_unzip = entry.need_unzip
     install_unzipped = entry.install_unzipped
 
-    fetch_artifact(branch, build, target, name)
+    if build:
+        fetch_artifact(branch, build, target, name)
+    else:
+        copy_artifact(local_dist, target, name)
     if need_strip:
         check_call(['strip', name])
     if need_exec:
@@ -152,12 +172,16 @@
 
 def get_args():
     """Parses and returns command line arguments."""
-    parser = argparse.ArgumentParser()
+    parser = argparse.ArgumentParser(
+        epilog='Either --build or --local-dist is required.')
 
     parser.add_argument(
         '-b', '--branch', default='aosp-master',
         help='Branch to pull build from.')
-    parser.add_argument('--build', required=True, help='Build number to pull.')
+    parser.add_argument('--build', help='Build number to pull.')
+    parser.add_argument('--local-dist',
+                        help='Take prebuilts from this local dist dir instead of '
+                        'using fetch_artifact')
     parser.add_argument(
         '--use-current-branch', action='store_true',
         help='Perform the update in the current branch. Do not repo start.')
@@ -165,12 +189,15 @@
         '-v', '--verbose', action='count', default=0,
         help='Increase output verbosity.')
 
-    return parser.parse_args()
+    args = parser.parse_args()
+    if ((not args.build and not args.local_dist) or
+        (args.build and args.local_dist)):
+        sys.exit(parser.format_help())
+    return args
 
 
 def main(work_dir, prebuilts, install_list, extracted_list, commit_message_note=None):
     """Program entry point."""
-    os.chdir(work_dir)
 
     args = get_args()
     verbose_map = (logging.WARNING, logging.INFO, logging.DEBUG)
@@ -179,9 +206,15 @@
         verbosity = 2
     logging.basicConfig(level=verbose_map[verbosity])
 
+    local_dist = args.local_dist
+    if local_dist:
+        local_dist = os.path.abspath(local_dist)
+
+    os.chdir(work_dir)
+
     if not args.use_current_branch:
         start_branch(args.build)
     remove_old_files(install_list, extracted_list)
-    install_new_files(args.branch, args.build, install_list, extracted_list)
+    install_new_files(args.branch, args.build, local_dist, install_list, extracted_list)
     files = list_installed_files(install_list, extracted_list)
     commit(prebuilts, args.branch, args.build, files, commit_message_note)