• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
8This 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
16After the change is uploaded to Android, developers can trigger TH and download
17binaries (if required) after runs complete.
18
19The script re-uses the workdir when it is run again. To start from a clean slate
20delete the workdir.
21
22Timings:
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
26Example usage:
27  $ python upload_to_android.py -w /repos/testing -c 44200
28"""
29
30import argparse
31import getpass
32import json
33import os
34import subprocess
35import stat
36import urllib2
37
38
39REPO_TOOL_URL = 'https://storage.googleapis.com/git-repo-downloads/repo'
40SKIA_PATH_IN_ANDROID = os.path.join('external', 'skia')
41ANDROID_REPO_URL = 'https://googleplex-android.googlesource.com'
42REPO_BRANCH_NAME = 'experiment'
43SKIA_GERRIT_INSTANCE = 'https://skia-review.googlesource.com'
44SK_USER_CONFIG_PATH = os.path.join('include', 'config', 'SkUserConfig.h')
45
46
47def 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
55def 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
84About 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@google.com' % getpass.getuser(), shell=True)
108
109  return repo_binary
110
111
112class Modifier:
113  def modify(self):
114    raise NotImplementedError
115  def get_user_msg(self):
116    raise NotImplementedError
117
118
119class 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
161Open the above URL and trigger TH by checking 'Presubmit-Ready'.
162You 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.
167class 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
205def 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
224def 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
241if __name__ == '__main__':
242  main()
243