aboutsummaryrefslogtreecommitdiffhomepage
path: root/tools/android/upload_to_android.py
blob: dba0c1692cc6a7498d092198cab0ea7d81953c16 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
#!/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()