Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 #!/usr/bin/python | |
|
brettw
2015/07/15 01:59:05
Needs copyright.
Dirk Pranke
2015/07/15 02:40:31
Acknowledged.
| |
| 2 | |
| 3 from __future__ import print_function | |
| 4 | |
| 5 import argparse | |
| 6 import json | |
| 7 import os | |
| 8 import re | |
| 9 import subprocess | |
| 10 import sys | |
| 11 import tempfile | |
| 12 import time | |
| 13 import urllib2 | |
| 14 | |
| 15 depot_tools_path = None | |
|
brettw
2015/07/15 01:59:05
Can this have some documentation and an example at
Dirk Pranke
2015/07/15 02:40:32
I assume "this" means the roll_gn script as a whol
brettw
2015/07/15 02:43:37
Correct, you can be assured I did not notice the s
| |
| 16 for p in os.environ['PATH'].split(os.pathsep): | |
| 17 if (p.rstrip(os.sep).endswith('depot_tools') and | |
| 18 os.path.isfile(os.path.join(p, 'gclient.py'))): | |
| 19 depot_tools_path = p | |
| 20 | |
| 21 assert depot_tools_path | |
| 22 | |
| 23 if not depot_tools_path in sys.path: | |
| 24 sys.path.append(depot_tools_path) | |
|
Nico
2015/07/15 20:24:25
nit: always prepend to sys.path, else you can end
Dirk Pranke
2015/07/15 20:35:56
true.
| |
| 25 sys.path.append(os.path.join(depot_tools_path, 'third_party')) | |
| 26 | |
| 27 import upload | |
| 28 | |
| 29 | |
| 30 CHROMIUM_REPO = 'https://chromium.googlesource.com/chromium/src.git' | |
| 31 | |
| 32 CODE_REVIEW_SERVER = 'https://codereview.chromium.org' | |
| 33 | |
| 34 | |
| 35 class GNRoller(object): | |
| 36 def __init__(self): | |
| 37 self.chromium_src_dir = None | |
| 38 self.old_gn_commitish = None | |
| 39 self.new_gn_commitish = None | |
| 40 self.old_gn_version = None | |
| 41 self.new_gn_version = None | |
| 42 self.reviewer = 'dpranke@chromium.org' | |
| 43 if os.getenv('USER') == 'dpranke': | |
| 44 self.reviewer = 'brettw@chromium.org' | |
| 45 | |
| 46 def Roll(self): | |
| 47 parser = argparse.ArgumentParser() | |
| 48 parser.add_argument('command', nargs='?', default='roll', | |
| 49 help='build|roll|roll_buildtools|roll_deps|wait' | |
| 50 ' (default is roll)') | |
| 51 | |
| 52 args = parser.parse_args() | |
| 53 command = args.command | |
| 54 ret = self.SetUp() | |
| 55 if not ret and command in ('roll', 'build'): | |
| 56 ret = self.TriggerBuild() | |
| 57 if not ret and command in ('roll', 'wait'): | |
| 58 ret = self.WaitForBuildToFinish() | |
| 59 if not ret and command in ('roll', 'roll_buildtools'): | |
| 60 ret = self.RollBuildtools() | |
| 61 if not ret and command in ('roll', 'roll_deps'): | |
| 62 ret = self.RollDEPS() | |
| 63 | |
| 64 return ret | |
| 65 | |
| 66 def SetUp(self): | |
| 67 if sys.platform != 'linux2': | |
| 68 print('roll_gn is only tested and working on Linux for now.') | |
| 69 return 1 | |
| 70 | |
| 71 ret, out, _ = call('git config --get remote.origin.url') | |
| 72 origin = out.strip() | |
| 73 if ret or origin != CHROMIUM_REPO: | |
| 74 print('Not in a Chromium repo? git config --get remote.origin.url ' | |
| 75 'returned %d: %s' % (ret, origin)) | |
| 76 return 1 | |
| 77 | |
| 78 ret, _, _ = call('git diff -q') | |
| 79 if ret: | |
| 80 print("Checkout is dirty, exiting") | |
| 81 return 1 | |
| 82 | |
| 83 _, out, _ = call('git rev-parse --show-toplevel') | |
| 84 self.chromium_src_dir = out.strip() | |
| 85 os.chdir(self.chromium_src_dir) | |
|
Nico
2015/07/15 20:24:25
i always encourage people to make their code not c
Dirk Pranke
2015/07/15 20:35:56
Yeah, I do, too. In this case I was being lazy.
| |
| 86 | |
| 87 self.new_gn_commitish, self.new_gn_version = self.GetNewVersions() | |
| 88 | |
| 89 _, out, _ = call('gn --version') | |
| 90 self.old_gn_version = out.strip() | |
| 91 | |
| 92 _, out, _ = call('git crrev-parse %s' % self.old_gn_version) | |
| 93 self.old_gn_commitish = out.strip() | |
| 94 return 0 | |
| 95 | |
| 96 def GetNewVersions(self): | |
| 97 commit_msg = call('git log -1 --grep Cr-Commit-Position')[1].splitlines() | |
| 98 first_line = commit_msg[0] | |
| 99 new_gn_commitish = first_line.split()[1] | |
| 100 | |
| 101 last_line = commit_msg[-1] | |
| 102 new_gn_version = re.sub('.*master@{#(\d+)}', '\\1', last_line) | |
| 103 | |
| 104 return new_gn_commitish, new_gn_version | |
| 105 | |
| 106 def TriggerBuild(self): | |
| 107 os.chdir(self.chromium_src_dir) | |
| 108 ret, _, _ = call('git new-branch build_gn_%s' % self.new_gn_version) | |
| 109 if ret: | |
| 110 print('Failed to create a new branch for build_gn_%s' % | |
| 111 self.new_gn_version) | |
| 112 return 1 | |
| 113 | |
| 114 self.MakeDummyDepsChange() | |
| 115 | |
| 116 ret, out, err = call('git commit -a -m "Build gn at %s"' % | |
| 117 self.new_gn_version) | |
| 118 if ret: | |
| 119 print('git commit failed: %s' % out + err) | |
| 120 return 1 | |
| 121 | |
| 122 print('Uploading CL to build GN at {#%s} - %s' % | |
| 123 (self.new_gn_version, self.new_gn_commitish)) | |
| 124 ret, out, err = call('git cl upload -f') | |
| 125 if ret: | |
| 126 print('git-cl upload failed: %s' % out + err) | |
| 127 return 1 | |
| 128 | |
| 129 print('Starting try jobs') | |
| 130 call('git-cl try -b linux_chromium_gn_upload -b mac_chromium_gn_upload ' | |
| 131 '-b win8_chromium_gn_upload -r %s' % self.new_gn_commitish) | |
| 132 | |
| 133 return 0 | |
| 134 | |
| 135 def MakeDummyDepsChange(self): | |
| 136 with open('DEPS') as fp: | |
| 137 deps_content = fp.read() | |
| 138 new_deps = deps_content.replace("'buildtools_revision':", | |
| 139 "'buildtools_revision': ") | |
| 140 | |
| 141 with open('DEPS', 'w') as fp: | |
| 142 fp.write(new_deps) | |
| 143 | |
| 144 def WaitForBuildToFinish(self): | |
| 145 print('Checking build') | |
| 146 results = self.CheckBuild() | |
| 147 while any(r['state'] == 'pending' for r in results.values()): | |
| 148 print() | |
| 149 print('Sleeping for 30 seconds') | |
| 150 time.sleep(30) | |
| 151 print('Checking build') | |
| 152 results = self.CheckBuild() | |
| 153 return 0 if all(r['state'] == 'success' for r in results.values()) else 1 | |
| 154 | |
| 155 def CheckBuild(self): | |
| 156 os.chdir(self.chromium_src_dir) | |
| 157 _, out, _ = call('git-cl issue') | |
| 158 | |
| 159 issue = int(out.split()[2]) | |
| 160 | |
| 161 _, out, _ = call('git config user.email') | |
| 162 email = '' | |
| 163 rpc_server = upload.GetRpcServer(CODE_REVIEW_SERVER, email) | |
| 164 try: | |
| 165 props = json.loads(rpc_server.Send('/api/%d' % issue)) | |
| 166 except Exception as _e: | |
| 167 raise | |
| 168 | |
| 169 patchset = int(props['patchsets'][-1]) | |
| 170 | |
| 171 try: | |
| 172 patchset_data = json.loads(rpc_server.Send('/api/%d/%d' % | |
| 173 (issue, patchset))) | |
| 174 except Exception as _e: | |
| 175 raise | |
| 176 | |
| 177 TRY_JOB_RESULT_STATES = ('success', 'warnings', 'failure', 'skipped', | |
| 178 'exception', 'retry', 'pending') | |
| 179 try_job_results = patchset_data['try_job_results'] | |
| 180 if not try_job_results: | |
| 181 print('No try jobs found on most recent patchset') | |
| 182 return 1 | |
| 183 | |
| 184 results = {} | |
| 185 for job in try_job_results: | |
| 186 builder = job['builder'] | |
| 187 if builder == 'linux_chromium_gn_upload': | |
| 188 platform = 'linux64' | |
| 189 elif builder == 'mac_chromium_gn_upload': | |
| 190 platform = 'mac' | |
| 191 elif builder == 'win8_chromium_gn_upload': | |
| 192 platform = 'win' | |
| 193 else: | |
| 194 print('Unexpected builder: %s') | |
| 195 continue | |
| 196 | |
| 197 state = TRY_JOB_RESULT_STATES[int(job['result'])] | |
| 198 url_str = ' %s' % job['url'] | |
| 199 build = url_str.split('/')[-1] | |
| 200 | |
| 201 sha1 = '-' | |
| 202 results.setdefault(platform, {'build': -1, 'sha1': '', 'url': url_str}) | |
| 203 | |
| 204 if state == 'success': | |
| 205 jsurl = url_str.replace('/builders/', '/json/builders/') | |
| 206 fp = urllib2.urlopen(jsurl) | |
| 207 js = json.loads(fp.read()) | |
| 208 fp.close() | |
| 209 for step in js['steps']: | |
| 210 if step['name'] == 'gn sha1': | |
| 211 sha1 = step['text'][1] | |
| 212 | |
| 213 if results[platform]['build'] < build: | |
| 214 results[platform]['build'] = build | |
| 215 results[platform]['sha1'] = sha1 | |
| 216 results[platform]['state'] = state | |
| 217 results[platform]['url'] = url_str | |
| 218 | |
| 219 for platform, r in results.items(): | |
| 220 print(platform) | |
| 221 print(' sha1: %s' % r['sha1']) | |
| 222 print(' state: %s' % r['state']) | |
| 223 print(' build: %s' % r['build']) | |
| 224 print(' url: %s' % r['url']) | |
| 225 print() | |
| 226 | |
| 227 return results | |
| 228 | |
| 229 def RollBuildtools(self): | |
| 230 os.chdir(self.chromium_src_dir) | |
| 231 results = self.CheckBuild() | |
| 232 if not all(r['state'] == 'success' for r in results.values()): | |
| 233 print("Roll isn't done or didn't succeed, exiting:") | |
| 234 return 1 | |
| 235 | |
| 236 desc = self.GetBuildtoolsDesc() | |
| 237 | |
| 238 os.chdir('buildtools') | |
| 239 call('git new-branch roll_buildtools_gn_%s' % self.new_gn_version) | |
| 240 | |
| 241 for platform in results: | |
| 242 fname = 'gn.exe.sha1' if platform == 'win' else 'gn.sha1' | |
|
Nico
2015/07/15 20:24:25
suddenly, 4-space indent?
Dirk Pranke
2015/07/15 20:35:56
bad indent. will fix.
| |
| 243 with open('%s/%s' % (platform, fname), 'w') as fp: | |
| 244 fp.write('%s\n' % results[platform]['sha1']) | |
| 245 | |
| 246 desc_file = tempfile.NamedTemporaryFile(delete=False) | |
| 247 try: | |
| 248 desc_file.write(desc) | |
| 249 desc_file.close() | |
| 250 call('git commit -a -F %s' % desc_file.name) | |
| 251 call('git-cl upload -f --send-mail') | |
| 252 finally: | |
| 253 os.remove(desc_file.name) | |
| 254 | |
| 255 call('git cl push') | |
| 256 call('git cl fetch') | |
| 257 return 0 | |
| 258 | |
| 259 def RollDEPS(self): | |
| 260 os.chdir(self.chromium_src_dir) | |
| 261 | |
| 262 os.chdir('buildtools') | |
| 263 _, out, _ = call('git rev-parse origin/master') | |
| 264 new_buildtools_commitish = out.strip() | |
| 265 | |
| 266 new_deps_lines = [] | |
| 267 os.chdir('..') | |
| 268 old_buildtools_commitish = '' | |
| 269 with open('DEPS') as fp: | |
| 270 for l in fp.readlines(): | |
| 271 m = re.match(".*'buildtools_revision':.*'(.+)',", l) | |
| 272 if m: | |
| 273 old_buildtools_commitish = m.group(1) | |
| 274 new_deps_lines.append(" 'buildtools_revision': '%s'," % | |
| 275 new_buildtools_commitish) | |
| 276 else: | |
| 277 new_deps_lines.append(l) | |
| 278 | |
| 279 if not old_buildtools_commitish: | |
| 280 print('Could not update DEPS properly, exiting') | |
| 281 return 1 | |
| 282 | |
| 283 with open('DEPS', 'w') as fp: | |
| 284 fp.write(''.join(new_deps_lines) + '\n') | |
| 285 | |
| 286 desc = self.GetDEPSRollDesc(old_buildtools_commitish, | |
| 287 new_buildtools_commitish) | |
| 288 desc_file = tempfile.NamedTemporaryFile(delete=False) | |
| 289 try: | |
| 290 desc_file.write(desc) | |
| 291 desc_file.close() | |
| 292 call('git commit -a -F %s' % desc_file.name) | |
| 293 call('git-cl upload -f --send-mail --commit-queue') | |
| 294 finally: | |
| 295 os.remove(desc_file.name) | |
| 296 return 0 | |
| 297 | |
| 298 def GetBuildtoolsDesc(self): | |
| 299 gn_changes = self.GetGNChanges() | |
| 300 return ( | |
| 301 'Roll gn %s..%s (r%s:%s)\n' | |
| 302 '\n' | |
| 303 '%s' | |
| 304 '\n' | |
| 305 'TBR=%s\n' % ( | |
| 306 self.old_gn_commitish, | |
| 307 self.new_gn_commitish, | |
| 308 self.old_gn_version, | |
| 309 self.new_gn_version, | |
| 310 gn_changes, | |
| 311 self.reviewer, | |
| 312 )) | |
| 313 | |
| 314 def GetDEPSRollDesc(self, old_buildtools_commitish, new_buildtools_commitish): | |
| 315 gn_changes = self.GetGNChanges() | |
| 316 | |
| 317 return ( | |
| 318 'Roll DEPS %s..%s\n' | |
| 319 '\n' | |
| 320 ' in order to roll GN %s..%s (r%s:%s)\n' | |
| 321 '\n' | |
| 322 '%s' | |
| 323 '\n' | |
| 324 'TBR=%s\n' % ( | |
| 325 old_buildtools_commitish, | |
| 326 new_buildtools_commitish, | |
| 327 self.old_gn_commitish, | |
| 328 self.new_gn_commitish, | |
| 329 self.old_gn_version, | |
| 330 self.new_gn_version, | |
| 331 gn_changes, | |
| 332 self.reviewer, | |
| 333 )) | |
| 334 | |
| 335 def GetGNChanges(self): | |
| 336 _, out, _ = call( | |
| 337 "git log --pretty=' %h %s' " + | |
| 338 "%s..%s tools/gn" % (self.old_gn_commitish, self.new_gn_commitish)) | |
| 339 return out | |
| 340 | |
| 341 def call(cmd): | |
| 342 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) | |
|
Nico
2015/07/15 20:24:25
do you need the shell bit? if not, i'd recommend n
Dirk Pranke
2015/07/15 20:35:56
In many cases, I need shell=True. It is possible t
| |
| 343 out, err = p.communicate() | |
| 344 return p.returncode, out, err | |
| 345 | |
| 346 | |
| 347 if __name__ == '__main__': | |
| 348 roller = GNRoller() | |
| 349 sys.exit(roller.Roll()) | |
| OLD | NEW |