upload_to_android.py 7.7 KB

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