Home | History | Annotate | Download | only in android
      1 #!/usr/bin/env python
      2 # Copyright (c) 2017 The Chromium Authors. All rights reserved.
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 
      6 """Script that uploads the specified Skia Gerrit change to Android.
      7 
      8 This script does the following:
      9 * Downloads the repo tool.
     10 * Inits and checks out the bare-minimum required Android checkout.
     11 * Sets the required git config options in external/skia.
     12 * Cherry-picks the specified Skia patch.
     13 * Modifies the change subject to append a "Test:" line required for presubmits.
     14 * Uploads the Skia change to Android's Gerrit instance.
     15 
     16 After the change is uploaded to Android, developers can trigger TH and download
     17 binaries (if required) after runs complete.
     18 
     19 The script re-uses the workdir when it is run again. To start from a clean slate
     20 delete the workdir.
     21 
     22 Timings:
     23 * ~1m15s when using an empty/non-existent workdir for the first time.
     24 * ~15s when using a workdir previously populated by the script.
     25 
     26 Example usage:
     27   $ python upload_to_android.py -w /repos/testing -c 44200
     28 """
     29 
     30 import argparse
     31 import getpass
     32 import json
     33 import os
     34 import subprocess
     35 import stat
     36 import urllib2
     37 
     38 
     39 REPO_TOOL_URL = 'https://storage.googleapis.com/git-repo-downloads/repo'
     40 SKIA_PATH_IN_ANDROID = os.path.join('external', 'skia')
     41 ANDROID_REPO_URL = 'https://googleplex-android.googlesource.com'
     42 REPO_BRANCH_NAME = 'experiment'
     43 SKIA_GERRIT_INSTANCE = 'https://skia-review.googlesource.com'
     44 SK_USER_CONFIG_PATH = os.path.join('include', 'config', 'SkUserConfig.h')
     45 
     46 
     47 def get_change_details(change_num):
     48   response = urllib2.urlopen('%s/changes/%s/detail?o=ALL_REVISIONS' % (
     49                                  SKIA_GERRIT_INSTANCE, change_num), timeout=5)
     50   content = response.read()
     51   # Remove the first line which contains ")]}'\n".
     52   return json.loads(content[5:])
     53 
     54 
     55 def init_work_dir(work_dir):
     56   if not os.path.isdir(work_dir):
     57     print 'Creating %s' % work_dir
     58     os.makedirs(work_dir)
     59 
     60   # Ensure the repo tool exists in the work_dir.
     61   repo_dir = os.path.join(work_dir, 'bin')
     62   repo_binary = os.path.join(repo_dir, 'repo')
     63   if not os.path.isdir(repo_dir):
     64     print 'Creating %s' % repo_dir
     65     os.makedirs(repo_dir)
     66   if not os.path.exists(repo_binary):
     67     print 'Downloading %s from %s' % (repo_binary, REPO_TOOL_URL)
     68     response = urllib2.urlopen(REPO_TOOL_URL, timeout=5)
     69     content = response.read()
     70     with open(repo_binary, 'w') as f:
     71       f.write(content)
     72     # Set executable bit.
     73     st = os.stat(repo_binary)
     74     os.chmod(repo_binary, st.st_mode | stat.S_IEXEC)
     75 
     76   # Create android-repo directory in the work_dir.
     77   android_dir = os.path.join(work_dir, 'android-repo')
     78   if not os.path.isdir(android_dir):
     79     print 'Creating %s' % android_dir
     80     os.makedirs(android_dir)
     81 
     82   print """
     83 
     84 About to run repo init. If it hangs asking you to run glogin then please:
     85 * Exit the script (ctrl-c).
     86 * Run 'glogin'.
     87 * Re-run the script.
     88 
     89 """
     90   os.chdir(android_dir)
     91   subprocess.check_call(
     92       '%s init -u %s/a/platform/manifest -g "all,-notdefault,-darwin" '
     93       '-b master --depth=1'
     94           % (repo_binary, ANDROID_REPO_URL), shell=True)
     95 
     96   print 'Syncing the Android checkout at %s' % android_dir
     97   subprocess.check_call('%s sync %s tools/repohooks -j 32 -c' % (
     98                             repo_binary, SKIA_PATH_IN_ANDROID), shell=True)
     99 
    100   # Set the necessary git config options.
    101   os.chdir(SKIA_PATH_IN_ANDROID)
    102   subprocess.check_call(
    103       'git config remote.goog.review %s/' % ANDROID_REPO_URL, shell=True)
    104   subprocess.check_call(
    105       'git config review.%s/.autoupload true' % ANDROID_REPO_URL, shell=True)
    106   subprocess.check_call(
    107       'git config user.email %s (at] google.com' % getpass.getuser(), shell=True)
    108 
    109   return repo_binary
    110 
    111 
    112 class Modifier:
    113   def modify(self):
    114     raise NotImplementedError
    115   def get_user_msg(self):
    116     raise NotImplementedError
    117 
    118 
    119 class FetchModifier(Modifier):
    120   def __init__(self, change_num, debug):
    121     self.change_num = change_num
    122     self.debug = debug
    123 
    124   def modify(self):
    125     # Download and cherry-pick the patch.
    126     change_details = get_change_details(self.change_num)
    127     latest_patchset = len(change_details['revisions'])
    128     mod = int(self.change_num) % 100
    129     download_ref = 'refs/changes/%s/%s/%s' % (
    130                        str(mod).zfill(2), self.change_num, latest_patchset)
    131     subprocess.check_call(
    132         'git fetch https://skia.googlesource.com/skia %s' % download_ref,
    133         shell=True)
    134     subprocess.check_call('git cherry-pick FETCH_HEAD', shell=True)
    135 
    136     if self.debug:
    137       # Add SK_DEBUG to SkUserConfig.h.
    138       with open(SK_USER_CONFIG_PATH, 'a') as f:
    139         f.write('#ifndef SK_DEBUG\n')
    140         f.write('#define SK_DEBUG\n')
    141         f.write('#endif//SK_DEBUG\n')
    142       subprocess.check_call('git add %s' % SK_USER_CONFIG_PATH, shell=True)
    143 
    144     # Amend the commit message to add a prefix that makes it clear that the
    145     # change should not be submitted and a "Test:" line which is required by
    146     # Android presubmit checks.
    147     original_commit_message = change_details['subject']
    148     new_commit_message = (
    149         # Intentionally breaking up the below string because some presubmits
    150         # complain about it.
    151         '[DO ' + 'NOT ' + 'SUBMIT] %s\n\n'
    152         'Test: Presubmit checks will test this change.' % (
    153             original_commit_message))
    154 
    155     subprocess.check_call('git commit --amend -m "%s"' % new_commit_message,
    156                           shell=True)
    157 
    158   def get_user_msg(self):
    159     return """
    160 
    161 Open the above URL and trigger TH by checking 'Presubmit-Ready'.
    162 You can download binaries (if required) from the TH link after it completes.
    163 """
    164 
    165 
    166 # Add a legacy flag if it doesn't exist, or remove it if it exists.
    167 class AndroidLegacyFlagModifier(Modifier):
    168   def __init__(self, flag):
    169     self.flag = flag
    170     self.verb = "Unknown"
    171 
    172   def modify(self):
    173     flag_line = "  #define %s\n" % self.flag
    174 
    175     config_file = os.path.join('include', 'config', 'SkUserConfigManual.h')
    176 
    177     with open(config_file) as f:
    178       lines = f.readlines()
    179 
    180     if flag_line not in lines:
    181       lines.insert(
    182           lines.index("#endif // SkUserConfigManual_DEFINED\n"), flag_line)
    183       verb = "Add"
    184     else:
    185       lines.remove(flag_line)
    186       verb = "Remove"
    187 
    188     with open(config_file, 'w') as f:
    189       for line in lines:
    190         f.write(line)
    191 
    192     subprocess.check_call('git add %s' % config_file, shell=True)
    193     message = '%s %s\n\nTest: Presubmit checks will test this change.' % (
    194         verb, self.flag)
    195 
    196     subprocess.check_call('git commit -m "%s"' % message, shell=True)
    197 
    198   def get_user_msg(self):
    199       return """
    200 
    201   Please open the above URL to review and land the change.
    202 """
    203 
    204 
    205 def upload_to_android(work_dir, modifier):
    206   repo_binary = init_work_dir(work_dir)
    207 
    208   # Create repo branch.
    209   subprocess.check_call('%s start %s .' % (repo_binary, REPO_BRANCH_NAME),
    210                         shell=True)
    211   try:
    212     modifier.modify()
    213 
    214     # Upload to Android Gerrit.
    215     subprocess.check_call('%s upload --verify' % repo_binary, shell=True)
    216 
    217     print modifier.get_user_msg()
    218   finally:
    219     # Abandon repo branch.
    220     subprocess.call('%s abandon %s' % (repo_binary, REPO_BRANCH_NAME),
    221                     shell=True)
    222 
    223 
    224 def main():
    225   parser = argparse.ArgumentParser()
    226   parser.add_argument(
    227       '--work-dir', '-w', required=True,
    228       help='Directory where an Android checkout will be created (if it does '
    229            'not already exist). Note: ~1GB space will be used.')
    230   parser.add_argument(
    231       '--change-num', '-c', required=True,
    232       help='The skia-rev Gerrit change number that should be patched into '
    233            'Android.')
    234   parser.add_argument(
    235       '--debug', '-d', action='store_true', default=False,
    236       help='Adds SK_DEBUG to SkUserConfig.h.')
    237   args = parser.parse_args()
    238   upload_to_android(args.work_dir, FetchModifier(args.change_num, args.debug))
    239 
    240 
    241 if __name__ == '__main__':
    242   main()
    243