Ravi Mistry | e1e47b7 | 2017-09-08 13:54:41 -0400 | [diff] [blame] | 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 |
Mike Klein | 11cd464 | 2017-09-08 17:55:31 -0400 | [diff] [blame] | 31 | import getpass |
Ravi Mistry | e1e47b7 | 2017-09-08 13:54:41 -0400 | [diff] [blame] | 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' |
Ravi Mistry | ff72a08 | 2017-09-12 09:40:46 -0400 | [diff] [blame] | 44 | SK_USER_CONFIG_PATH = os.path.join('include', 'config', 'SkUserConfig.h') |
Ravi Mistry | e1e47b7 | 2017-09-08 13:54:41 -0400 | [diff] [blame] | 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 | |
Yuqian Li | 33e2fad | 2017-10-24 14:42:57 -0400 | [diff] [blame] | 55 | def init_work_dir(work_dir): |
Ravi Mistry | e1e47b7 | 2017-09-08 13:54:41 -0400 | [diff] [blame] | 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( |
Ravi Mistry | b816322 | 2017-09-11 10:47:34 -0400 | [diff] [blame] | 92 | '%s init -u %s/a/platform/manifest -g "all,-notdefault,-darwin" ' |
| 93 | '-b master --depth=1' |
Ravi Mistry | e1e47b7 | 2017-09-08 13:54:41 -0400 | [diff] [blame] | 94 | % (repo_binary, ANDROID_REPO_URL), shell=True) |
| 95 | |
| 96 | print 'Syncing the Android checkout at %s' % android_dir |
Ravi Mistry | b816322 | 2017-09-11 10:47:34 -0400 | [diff] [blame] | 97 | subprocess.check_call('%s sync %s tools/repohooks -j 32 -c' % ( |
Ravi Mistry | e1e47b7 | 2017-09-08 13:54:41 -0400 | [diff] [blame] | 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) |
Mike Klein | 11cd464 | 2017-09-08 17:55:31 -0400 | [diff] [blame] | 106 | subprocess.check_call( |
| 107 | 'git config user.email %s@google.com' % getpass.getuser(), shell=True) |
Ravi Mistry | e1e47b7 | 2017-09-08 13:54:41 -0400 | [diff] [blame] | 108 | |
Yuqian Li | 33e2fad | 2017-10-24 14:42:57 -0400 | [diff] [blame] | 109 | return repo_binary |
| 110 | |
Yuqian Li | 33e2fad | 2017-10-24 14:42:57 -0400 | [diff] [blame] | 111 | |
Yuqian Li | ee6784d | 2017-10-31 10:14:25 -0400 | [diff] [blame] | 112 | class Modifier: |
| 113 | def modify(self): |
| 114 | raise NotImplementedError |
| 115 | def get_user_msg(self): |
| 116 | raise NotImplementedError |
Ravi Mistry | e1e47b7 | 2017-09-08 13:54:41 -0400 | [diff] [blame] | 117 | |
Yuqian Li | ee6784d | 2017-10-31 10:14:25 -0400 | [diff] [blame] | 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): |
Ravi Mistry | e1e47b7 | 2017-09-08 13:54:41 -0400 | [diff] [blame] | 125 | # Download and cherry-pick the patch. |
Yuqian Li | ee6784d | 2017-10-31 10:14:25 -0400 | [diff] [blame] | 126 | change_details = get_change_details(self.change_num) |
Ravi Mistry | e1e47b7 | 2017-09-08 13:54:41 -0400 | [diff] [blame] | 127 | latest_patchset = len(change_details['revisions']) |
Yuqian Li | ee6784d | 2017-10-31 10:14:25 -0400 | [diff] [blame] | 128 | mod = int(self.change_num) % 100 |
Ravi Mistry | e1e47b7 | 2017-09-08 13:54:41 -0400 | [diff] [blame] | 129 | download_ref = 'refs/changes/%s/%s/%s' % ( |
Yuqian Li | ee6784d | 2017-10-31 10:14:25 -0400 | [diff] [blame] | 130 | str(mod).zfill(2), self.change_num, latest_patchset) |
Ravi Mistry | e1e47b7 | 2017-09-08 13:54:41 -0400 | [diff] [blame] | 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 | |
Yuqian Li | ee6784d | 2017-10-31 10:14:25 -0400 | [diff] [blame] | 136 | if self.debug: |
Ravi Mistry | ff72a08 | 2017-09-12 09:40:46 -0400 | [diff] [blame] | 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 | |
Ravi Mistry | a6b6469 | 2017-09-12 10:56:42 -0400 | [diff] [blame] | 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. |
Ravi Mistry | e1e47b7 | 2017-09-08 13:54:41 -0400 | [diff] [blame] | 147 | original_commit_message = change_details['subject'] |
| 148 | new_commit_message = ( |
Ravi Mistry | a6b6469 | 2017-09-12 10:56:42 -0400 | [diff] [blame] | 149 | # Intentionally breaking up the below string because some presubmits |
| 150 | # complain about it. |
| 151 | '[DO ' + 'NOT ' + 'SUBMIT] %s\n\n' |
Ravi Mistry | a2bdf00 | 2017-09-11 11:00:34 -0400 | [diff] [blame] | 152 | 'Test: Presubmit checks will test this change.' % ( |
Ravi Mistry | e1e47b7 | 2017-09-08 13:54:41 -0400 | [diff] [blame] | 153 | original_commit_message)) |
Yuqian Li | ee6784d | 2017-10-31 10:14:25 -0400 | [diff] [blame] | 154 | |
Ravi Mistry | e1e47b7 | 2017-09-08 13:54:41 -0400 | [diff] [blame] | 155 | subprocess.check_call('git commit --amend -m "%s"' % new_commit_message, |
| 156 | shell=True) |
| 157 | |
Yuqian Li | ee6784d | 2017-10-31 10:14:25 -0400 | [diff] [blame] | 158 | def get_user_msg(self): |
| 159 | return """ |
Ravi Mistry | e1e47b7 | 2017-09-08 13:54:41 -0400 | [diff] [blame] | 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 | """ |
Yuqian Li | ee6784d | 2017-10-31 10:14:25 -0400 | [diff] [blame] | 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() |
Ravi Mistry | e1e47b7 | 2017-09-08 13:54:41 -0400 | [diff] [blame] | 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.') |
Ravi Mistry | ff72a08 | 2017-09-12 09:40:46 -0400 | [diff] [blame] | 234 | parser.add_argument( |
| 235 | '--debug', '-d', action='store_true', default=False, |
| 236 | help='Adds SK_DEBUG to SkUserConfig.h.') |
Ravi Mistry | e1e47b7 | 2017-09-08 13:54:41 -0400 | [diff] [blame] | 237 | args = parser.parse_args() |
Yuqian Li | ee6784d | 2017-10-31 10:14:25 -0400 | [diff] [blame] | 238 | upload_to_android(args.work_dir, FetchModifier(args.change_num, args.debug)) |
Ravi Mistry | e1e47b7 | 2017-09-08 13:54:41 -0400 | [diff] [blame] | 239 | |
| 240 | |
| 241 | if __name__ == '__main__': |
| 242 | main() |