| #!/usr/bin/env python |
| # Copyright (c) 2017 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Script that uploads the specified Skia Gerrit change to Android. |
| |
| This script does the following: |
| * Downloads the repo tool. |
| * Inits and checks out the bare-minimum required Android checkout. |
| * Sets the required git config options in external/skia. |
| * Cherry-picks the specified Skia patch. |
| * Modifies the change subject to append a "Test:" line required for presubmits. |
| * Uploads the Skia change to Android's Gerrit instance. |
| |
| After the change is uploaded to Android, developers can trigger TH and download |
| binaries (if required) after runs complete. |
| |
| The script re-uses the workdir when it is run again. To start from a clean slate |
| delete the workdir. |
| |
| Timings: |
| * ~1m15s when using an empty/non-existent workdir for the first time. |
| * ~15s when using a workdir previously populated by the script. |
| |
| Example usage: |
| $ python upload_to_android.py -w /repos/testing -c 44200 |
| """ |
| |
| import argparse |
| import getpass |
| import json |
| import os |
| import subprocess |
| import stat |
| import urllib2 |
| |
| |
| REPO_TOOL_URL = 'https://storage.googleapis.com/git-repo-downloads/repo' |
| SKIA_PATH_IN_ANDROID = os.path.join('external', 'skia') |
| ANDROID_REPO_URL = 'https://googleplex-android.googlesource.com' |
| REPO_BRANCH_NAME = 'experiment' |
| SKIA_GERRIT_INSTANCE = 'https://skia-review.googlesource.com' |
| SK_USER_CONFIG_PATH = os.path.join('include', 'config', 'SkUserConfig.h') |
| |
| |
| def get_change_details(change_num): |
| response = urllib2.urlopen('%s/changes/%s/detail?o=ALL_REVISIONS' % ( |
| SKIA_GERRIT_INSTANCE, change_num), timeout=5) |
| content = response.read() |
| # Remove the first line which contains ")]}'\n". |
| return json.loads(content[5:]) |
| |
| |
| def init_work_dir(work_dir): |
| if not os.path.isdir(work_dir): |
| print 'Creating %s' % work_dir |
| os.makedirs(work_dir) |
| |
| # Ensure the repo tool exists in the work_dir. |
| repo_dir = os.path.join(work_dir, 'bin') |
| repo_binary = os.path.join(repo_dir, 'repo') |
| if not os.path.isdir(repo_dir): |
| print 'Creating %s' % repo_dir |
| os.makedirs(repo_dir) |
| if not os.path.exists(repo_binary): |
| print 'Downloading %s from %s' % (repo_binary, REPO_TOOL_URL) |
| response = urllib2.urlopen(REPO_TOOL_URL, timeout=5) |
| content = response.read() |
| with open(repo_binary, 'w') as f: |
| f.write(content) |
| # Set executable bit. |
| st = os.stat(repo_binary) |
| os.chmod(repo_binary, st.st_mode | stat.S_IEXEC) |
| |
| # Create android-repo directory in the work_dir. |
| android_dir = os.path.join(work_dir, 'android-repo') |
| if not os.path.isdir(android_dir): |
| print 'Creating %s' % android_dir |
| os.makedirs(android_dir) |
| |
| print """ |
| |
| About to run repo init. If it hangs asking you to run glogin then please: |
| * Exit the script (ctrl-c). |
| * Run 'glogin'. |
| * Re-run the script. |
| |
| """ |
| os.chdir(android_dir) |
| subprocess.check_call( |
| '%s init -u %s/a/platform/manifest -g "all,-notdefault,-darwin" ' |
| '-b master --depth=1' |
| % (repo_binary, ANDROID_REPO_URL), shell=True) |
| |
| print 'Syncing the Android checkout at %s' % android_dir |
| subprocess.check_call('%s sync %s tools/repohooks -j 32 -c' % ( |
| repo_binary, SKIA_PATH_IN_ANDROID), shell=True) |
| |
| # Set the necessary git config options. |
| os.chdir(SKIA_PATH_IN_ANDROID) |
| subprocess.check_call( |
| 'git config remote.goog.review %s/' % ANDROID_REPO_URL, shell=True) |
| subprocess.check_call( |
| 'git config review.%s/.autoupload true' % ANDROID_REPO_URL, shell=True) |
| subprocess.check_call( |
| 'git config user.email %s@google.com' % getpass.getuser(), shell=True) |
| |
| return repo_binary |
| |
| |
| class Modifier: |
| def modify(self): |
| raise NotImplementedError |
| def get_user_msg(self): |
| raise NotImplementedError |
| |
| |
| class FetchModifier(Modifier): |
| def __init__(self, change_num, debug): |
| self.change_num = change_num |
| self.debug = debug |
| |
| def modify(self): |
| # Download and cherry-pick the patch. |
| change_details = get_change_details(self.change_num) |
| latest_patchset = len(change_details['revisions']) |
| mod = int(self.change_num) % 100 |
| download_ref = 'refs/changes/%s/%s/%s' % ( |
| str(mod).zfill(2), self.change_num, latest_patchset) |
| subprocess.check_call( |
| 'git fetch https://skia.googlesource.com/skia %s' % download_ref, |
| shell=True) |
| subprocess.check_call('git cherry-pick FETCH_HEAD', shell=True) |
| |
| if self.debug: |
| # Add SK_DEBUG to SkUserConfig.h. |
| with open(SK_USER_CONFIG_PATH, 'a') as f: |
| f.write('#ifndef SK_DEBUG\n') |
| f.write('#define SK_DEBUG\n') |
| f.write('#endif//SK_DEBUG\n') |
| subprocess.check_call('git add %s' % SK_USER_CONFIG_PATH, shell=True) |
| |
| # Amend the commit message to add a prefix that makes it clear that the |
| # change should not be submitted and a "Test:" line which is required by |
| # Android presubmit checks. |
| original_commit_message = change_details['subject'] |
| new_commit_message = ( |
| # Intentionally breaking up the below string because some presubmits |
| # complain about it. |
| '[DO ' + 'NOT ' + 'SUBMIT] %s\n\n' |
| 'Test: Presubmit checks will test this change.' % ( |
| original_commit_message)) |
| |
| subprocess.check_call('git commit --amend -m "%s"' % new_commit_message, |
| shell=True) |
| |
| def get_user_msg(self): |
| return """ |
| |
| Open the above URL and trigger TH by checking 'Presubmit-Ready'. |
| You can download binaries (if required) from the TH link after it completes. |
| """ |
| |
| |
| # Add a legacy flag if it doesn't exist, or remove it if it exists. |
| class AndroidLegacyFlagModifier(Modifier): |
| def __init__(self, flag): |
| self.flag = flag |
| self.verb = "Unknown" |
| |
| def modify(self): |
| flag_line = " #define %s\n" % self.flag |
| |
| config_file = os.path.join('include', 'config', 'SkUserConfigManual.h') |
| |
| with open(config_file) as f: |
| lines = f.readlines() |
| |
| if flag_line not in lines: |
| lines.insert( |
| lines.index("#endif // SkUserConfigManual_DEFINED\n"), flag_line) |
| verb = "Add" |
| else: |
| lines.remove(flag_line) |
| verb = "Remove" |
| |
| with open(config_file, 'w') as f: |
| for line in lines: |
| f.write(line) |
| |
| subprocess.check_call('git add %s' % config_file, shell=True) |
| message = '%s %s\n\nTest: Presubmit checks will test this change.' % ( |
| verb, self.flag) |
| |
| subprocess.check_call('git commit -m "%s"' % message, shell=True) |
| |
| def get_user_msg(self): |
| return """ |
| |
| Please open the above URL to review and land the change. |
| """ |
| |
| |
| def upload_to_android(work_dir, modifier): |
| repo_binary = init_work_dir(work_dir) |
| |
| # Create repo branch. |
| subprocess.check_call('%s start %s .' % (repo_binary, REPO_BRANCH_NAME), |
| shell=True) |
| try: |
| modifier.modify() |
| |
| # Upload to Android Gerrit. |
| subprocess.check_call('%s upload --verify' % repo_binary, shell=True) |
| |
| print modifier.get_user_msg() |
| finally: |
| # Abandon repo branch. |
| subprocess.call('%s abandon %s' % (repo_binary, REPO_BRANCH_NAME), |
| shell=True) |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser() |
| parser.add_argument( |
| '--work-dir', '-w', required=True, |
| help='Directory where an Android checkout will be created (if it does ' |
| 'not already exist). Note: ~1GB space will be used.') |
| parser.add_argument( |
| '--change-num', '-c', required=True, |
| help='The skia-rev Gerrit change number that should be patched into ' |
| 'Android.') |
| parser.add_argument( |
| '--debug', '-d', action='store_true', default=False, |
| help='Adds SK_DEBUG to SkUserConfig.h.') |
| args = parser.parse_args() |
| upload_to_android(args.work_dir, FetchModifier(args.change_num, args.debug)) |
| |
| |
| if __name__ == '__main__': |
| main() |