Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2012 The Native Client Authors. All rights reserved. | 2 # Copyright (c) 2012 The Native Client Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """ | 6 """ |
| 7 This tool helps with updating nacl_revision in Chromium's DEPS file to | 7 This tool helps with updating nacl_revision in Chromium's DEPS file to |
| 8 the latest revision of NaCl. It creates a Rietveld code review for | 8 the latest revision of NaCl. It creates a Rietveld code review for |
| 9 the update, listing the new NaCl commits. It kicks off a try job, | 9 the update, listing the new NaCl commits. It kicks off a try job, |
| 10 using relevant trybots. | 10 using relevant trybots. |
| 11 | 11 |
| 12 This tool should be run from a Git checkout of Chromium. | 12 This tool should be run from a Git checkout of Chromium. |
| 13 """ | 13 """ |
| 14 | 14 |
| 15 import re | 15 import re |
| 16 import optparse | 16 import optparse |
| 17 import os | 17 import os |
| 18 import subprocess | 18 import subprocess |
| 19 import sys | 19 import sys |
| 20 import time | 20 import time |
| 21 | 21 |
| 22 # This dependency can be installed with: | 22 |
| 23 # apt-get install python-svn | 23 NACL_GIT_ROOT = 'native_client' |
| 24 import pysvn | |
| 25 | 24 |
| 26 | 25 |
| 27 def ReadFile(filename): | 26 def ReadFile(filename): |
| 28 fh = open(filename, "r") | 27 fh = open(filename, "r") |
| 29 try: | 28 try: |
| 30 return fh.read() | 29 return fh.read() |
| 31 finally: | 30 finally: |
| 32 fh.close() | 31 fh.close() |
| 33 | 32 |
| 34 | 33 |
| 35 def WriteFile(filename, data): | 34 def WriteFile(filename, data): |
| 36 fh = open(filename, "w") | 35 fh = open(filename, "w") |
| 37 try: | 36 try: |
| 38 fh.write(data) | 37 fh.write(data) |
| 39 finally: | 38 finally: |
| 40 fh.close() | 39 fh.close() |
| 41 | 40 |
| 42 | 41 |
| 43 # The 'svn:' URL is faster than the 'http:' URL but only works if you | |
| 44 # have SVN committer credentials set up. | |
| 45 # NACL_SVN = 'http://src.chromium.org/native_client/trunk/src/native_client' | |
| 46 NACL_SVN = 'svn://svn.chromium.org/native_client/trunk/src/native_client' | |
| 47 # When querying for the latest revision, use the root URL. Otherwise, | |
| 48 # if the most recent change touched a branch and not trunk, the query | |
| 49 # will return an empty list. | |
| 50 NACL_SVN_ROOT = 'svn://svn.chromium.org/native_client' | |
| 51 | |
| 52 | |
| 53 def MatchKey(data, key): | 42 def MatchKey(data, key): |
| 54 if key == 'nacl_revision': | 43 if key == 'nacl_revision': |
| 55 # Pattern for fields in Chromium's DEPS file. | 44 # Pattern for fields in Chromium's DEPS file. |
| 56 match = re.search("^\s*'%s':\s*'(\S+)',\s*(#.*)?$" % key, data, re.M) | 45 match = re.search("^\s*'%s':\s*'(\S+)',\s*(#.*)?$" % key, data, re.M) |
| 57 else: | 46 else: |
| 58 # Pattern for fields in NaCl's DEPS file. | 47 # Pattern for fields in NaCl's DEPS file. |
| 59 match = re.search('^\s*"%s":\s*"(\S+)",\s*(#.*)?$' % key, data, re.M) | 48 match = re.search('^\s*"%s":\s*"(\S+)",\s*(#.*)?$' % key, data, re.M) |
| 60 if match is None: | 49 if match is None: |
| 61 raise Exception('Key %r not found' % key) | 50 raise Exception('Key %r not found' % key) |
| 62 return match | 51 return match |
| 63 | 52 |
| 64 | 53 |
| 65 def GetDepsField(data, key): | 54 def GetDepsField(data, key): |
| 66 match = MatchKey(data, key) | 55 match = MatchKey(data, key) |
| 67 return match.group(1) | 56 return match.group(1) |
| 68 | 57 |
| 69 | 58 |
| 70 def SetDepsField(data, key, value): | 59 def SetDepsField(data, key, value): |
| 71 match = MatchKey(data, key) | 60 match = MatchKey(data, key) |
| 72 return ''.join([data[:match.start(1)], | 61 return ''.join([data[:match.start(1)], |
| 73 value, | 62 value, |
| 74 data[match.end(1):]]) | 63 data[match.end(1):]]) |
| 75 | 64 |
| 76 | 65 |
| 77 def SetDepsFieldWithComment(data, key, value, comment): | 66 def GetNaClRev(git_dir): |
| 78 assert key == 'nacl_revision', key | 67 return subprocess.check_output( |
| 79 match = MatchKey(data, key) | 68 ['git', 'rev-parse', 'origin/master'], cwd=git_dir).strip() |
| 80 return data[:match.start(1)] + value + "', # " + comment + data[match.end():] | |
| 81 | 69 |
| 82 | 70 |
| 83 def GetLatestRootRev(): | 71 def GetLog(git_dir, rev1, rev2): |
| 84 rev = pysvn.Revision(pysvn.opt_revision_kind.head) | 72 stdout = subprocess.check_output( |
| 85 lst = pysvn.Client().log(NACL_SVN_ROOT, revision_start=rev, revision_end=rev, | 73 ['git', 'log', '--pretty=format:%h %ae %s', rev1 + '..' + rev2], |
|
Mark Seaborn
2015/01/28 16:34:21
Can you add "--reverse", so that the oldest change
| |
| 86 discover_changed_paths=True) | 74 cwd=git_dir) |
| 87 assert len(lst) == 1, lst | |
| 88 return lst[0].revision.number | |
| 89 | |
| 90 | |
| 91 def GetNaClRev(): | |
| 92 # Find the revision number for the most recent commit to the subdir | |
| 93 # specified by NACL_SVN. Unfortunately, SVN does not make it easy | |
| 94 # to query this directly. If the HEAD commit did not change the | |
| 95 # subdir (e.g. because it changed other branches), "svn log | |
| 96 # -rHEAD:HEAD <subdir>" yields an empty list. This means we must | |
| 97 # start with the latest root revision and search backwards until we | |
| 98 # hit a change to the subdir. | |
| 99 now = time.time() | |
| 100 rev_num = GetLatestRootRev() | |
| 101 while True: | |
| 102 rev = pysvn.Revision(pysvn.opt_revision_kind.number, rev_num) | |
| 103 lst = pysvn.Client().log(NACL_SVN, revision_start=rev, revision_end=rev) | |
| 104 assert len(lst) in (0, 1), lst | |
| 105 if len(lst) == 1: | |
| 106 age_mins = (now - lst[0].date) / 60 | |
| 107 print 'r%i committed %.1f minutes ago' % ( | |
| 108 lst[0].revision.number, age_mins) | |
| 109 return lst[0].revision.number | |
| 110 rev_num -= 1 | |
| 111 | |
| 112 | |
| 113 def GetLog(rev1, rev2): | |
| 114 # Get info on commits from rev1 (older, exclusive) to rev2 (newer, | |
| 115 # inclusive). Returns commit info formatted as a string, and a list | |
| 116 # of author e-mail addresses. | |
| 117 items = pysvn.Client().log( | |
| 118 NACL_SVN, | |
| 119 revision_start=pysvn.Revision(pysvn.opt_revision_kind.number, rev1 + 1), | |
| 120 revision_end=pysvn.Revision(pysvn.opt_revision_kind.number, rev2)) | |
| 121 got = [] | 75 got = [] |
| 122 authors = [] | 76 authors = [] |
| 123 for item in items: | 77 for line in stdout.splitlines(): |
| 124 line1 = item.message.split('\n')[0] | 78 h, author, message = line.split(' ', 2) |
| 125 author = item.author.split('@', 1)[0] | 79 authors.append(author) |
| 126 if line1 == 'Update .DEPS.git' and author == 'chrome-admin': | 80 got.append('%s (%s) %s\n' % (h, author, message)) |
|
Mark Seaborn
2015/01/28 16:44:15
Can you add a colon, i.e.
'%s: (%s) %s\n'
to mat
| |
| 127 # Skip these automated commits. | |
| 128 continue | |
| 129 authors.append(item.author) | |
| 130 got.append('r%i: (%s) %s\n' % (item.revision.number, author, line1)) | |
| 131 return ''.join(got), authors | 81 return ''.join(got), authors |
| 132 | 82 |
| 133 | 83 |
| 134 def AssertIsGitRev(git_rev): | |
| 135 assert re.match('[0-9a-f]{40}$', git_rev) is not None, git_rev | |
| 136 | |
| 137 | |
| 138 # Returns the result of "git svn find-rev", which converts Git commit IDs | |
| 139 # to SVN revision numbers and vice versa. | |
| 140 def GitSvnFindRev(git_dir, arg): | |
| 141 stdout = subprocess.check_output(['git', 'svn', 'find-rev', arg], | |
| 142 cwd=git_dir) | |
| 143 # git-svn annoyingly sends "Partial-building" log output to stdout rather | |
| 144 # than stderr, so we have to ignore everything except the result on the | |
| 145 # last line. | |
| 146 lines = stdout.strip().split('\n') | |
| 147 return lines[-1].strip() | |
| 148 | |
| 149 | |
| 150 def GitToSvnRev(git_dir, git_rev): | |
| 151 AssertIsGitRev(git_rev) | |
| 152 rev = GitSvnFindRev(git_dir, git_rev) | |
| 153 return int(rev) | |
| 154 | |
| 155 | |
| 156 def SvnToGitRev(git_dir, svn_rev): | |
| 157 git_rev = GitSvnFindRev(git_dir, 'r%i' % int(svn_rev)) | |
| 158 AssertIsGitRev(git_rev) | |
| 159 return git_rev | |
| 160 | |
| 161 | |
| 162 def RunTrybots(): | 84 def RunTrybots(): |
| 163 try_cmd = ['git', 'cl', 'try'] | 85 try_cmd = ['git', 'cl', 'try'] |
| 164 # Run the default trybots. | 86 # Run the default trybots. |
| 165 subprocess.check_call(try_cmd) | 87 subprocess.check_call(try_cmd) |
| 166 # Run some non-default trybots. | 88 # Run some non-default trybots. |
| 167 subprocess.check_call( | 89 subprocess.check_call( |
| 168 try_cmd + ['-m', 'tryserver.chromium.linux', | 90 try_cmd + ['-m', 'tryserver.chromium.linux', |
| 169 '-b', 'linux_arm', | 91 '-b', 'linux_arm', |
| 170 '-b', 'linux_arm_compile', | 92 '-b', 'linux_arm_compile', |
| 171 '-b', 'linux_rel_precise32', | 93 '-b', 'linux_rel_precise32', |
| 172 '-b', 'linux_nacl_sdk_build', | 94 '-b', 'linux_nacl_sdk_build', |
| 173 ]) | 95 ]) |
| 174 subprocess.check_call( | 96 subprocess.check_call( |
| 175 try_cmd + ['-m', 'tryserver.chromium.mac', | 97 try_cmd + ['-m', 'tryserver.chromium.mac', |
| 176 '-b', 'mac_nacl_sdk_build', | 98 '-b', 'mac_nacl_sdk_build', |
| 177 ]) | 99 ]) |
| 178 subprocess.check_call( | 100 subprocess.check_call( |
| 179 try_cmd + ['-m', 'tryserver.chromium.win', | 101 try_cmd + ['-m', 'tryserver.chromium.win', |
| 180 '-b', 'win_nacl_sdk_build', | 102 '-b', 'win_nacl_sdk_build', |
| 181 ]) | 103 ]) |
| 182 | 104 |
| 183 | 105 |
| 184 def Main(args): | 106 def Main(args): |
| 185 parser = optparse.OptionParser('%prog [options]\n\n' + __doc__.strip()) | 107 parser = optparse.OptionParser('%prog [options]\n\n' + __doc__.strip()) |
| 186 parser.add_option('-r', '--revision', default=None, type='int', | 108 parser.add_option('-r', '--revision', default=None, |
| 187 help='NaCl SVN revision to use (default is HEAD)') | 109 help='NaCl GIT revision to use (default is HEAD)') |
|
Mark Seaborn
2015/01/28 16:34:21
Nit: capitalise as "Git"
| |
| 188 parser.add_option('--no-fetch', action='store_true', default=False, | 110 parser.add_option('--no-fetch', action='store_true', default=False, |
| 189 help='Do not run "git fetch". This is useful to speed' | 111 help='Do not run "git fetch". This is useful to speed' |
| 190 ' up rerunning the script if you know that the local Git' | 112 ' up rerunning the script if you know that the local Git' |
| 191 ' repos are already reasonably up-to-date.') | 113 ' repos are already reasonably up-to-date.') |
| 192 parser.add_option('--force-branch', action='store_true', default=False, | 114 parser.add_option('--force-branch', action='store_true', default=False, |
| 193 help='Force overwriting the Git branch. This is useful' | 115 help='Force overwriting the Git branch. This is useful' |
| 194 ' if a previous run of the script failed after creating' | 116 ' if a previous run of the script failed after creating' |
| 195 ' a nacl-deps-rN branch.') | 117 ' a nacl-deps-rN branch.') |
| 196 parser.add_option('-n', '--no-commit', action='store_true', default=False, | 118 parser.add_option('-n', '--no-commit', action='store_true', default=False, |
| 197 help='Do not run "git commit" (implies --no-upload)') | 119 help='Do not run "git commit" (implies --no-upload)') |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 208 # those should be retrievable via the reflog. This can also lose | 130 # those should be retrievable via the reflog. This can also lose |
| 209 # changes that have been staged to the index but then undone in the | 131 # changes that have been staged to the index but then undone in the |
| 210 # working files. | 132 # working files. |
| 211 proc = subprocess.Popen(['git', 'diff', '--name-only', 'HEAD'], | 133 proc = subprocess.Popen(['git', 'diff', '--name-only', 'HEAD'], |
| 212 stdout=subprocess.PIPE) | 134 stdout=subprocess.PIPE) |
| 213 changes = proc.communicate()[0] | 135 changes = proc.communicate()[0] |
| 214 assert proc.wait() == 0, proc.wait() | 136 assert proc.wait() == 0, proc.wait() |
| 215 if len(changes) != 0: | 137 if len(changes) != 0: |
| 216 raise AssertionError('You have uncommitted changes:\n%s' % changes) | 138 raise AssertionError('You have uncommitted changes:\n%s' % changes) |
| 217 | 139 |
| 218 svn_rev = options.revision | 140 nacl_git_dir = NACL_GIT_ROOT |
| 219 if svn_rev is None: | |
| 220 svn_rev = GetNaClRev() | |
| 221 | |
| 222 nacl_git_dir = 'native_client' | |
| 223 if not options.no_fetch: | 141 if not options.no_fetch: |
| 224 subprocess.check_call(['git', 'fetch']) | 142 subprocess.check_call(['git', 'fetch']) |
| 225 subprocess.check_call(['git', 'fetch'], cwd=nacl_git_dir) | 143 subprocess.check_call(['git', 'fetch'], cwd=nacl_git_dir) |
|
JF
2015/01/28 07:12:17
I'm not sure I understand what the NaCl cwd is exp
Mark Seaborn
2015/01/28 16:34:21
This just assumes it's run from a Chromium checkou
bradn
2015/01/28 17:37:32
Correct.
bradn
2015/01/28 17:37:32
What mark said.
| |
| 226 | 144 |
| 227 branch_name = 'nacl-deps-r%s' % svn_rev | 145 new_rev = options.revision |
| 146 if new_rev is None: | |
| 147 new_rev = GetNaClRev(nacl_git_dir) | |
| 148 | |
| 149 branch_name = 'nacl-deps-%s' % new_rev | |
| 228 if options.force_branch: | 150 if options.force_branch: |
| 229 checkout_opt = '-B' | 151 checkout_opt = '-B' |
| 230 else: | 152 else: |
| 231 checkout_opt = '-b' | 153 checkout_opt = '-b' |
| 232 subprocess.check_call(['git', 'checkout', checkout_opt, branch_name, | 154 subprocess.check_call(['git', 'checkout', checkout_opt, branch_name, |
| 233 'origin/master']) | 155 'origin/master']) |
| 234 | 156 |
| 235 deps_data = ReadFile('DEPS') | 157 deps_data = ReadFile('DEPS') |
| 236 old_rev_git = GetDepsField(deps_data, 'nacl_revision') | 158 old_rev = GetDepsField(deps_data, 'nacl_revision') |
| 237 | 159 |
| 238 new_rev_comment = 'from svn revision r%s' % svn_rev | 160 deps_data = SetDepsField(deps_data, 'nacl_revision', new_rev) |
| 239 new_rev_git = SvnToGitRev(nacl_git_dir, svn_rev) | |
| 240 deps_data = SetDepsFieldWithComment(deps_data, 'nacl_revision', new_rev_git, | |
| 241 new_rev_comment) | |
| 242 | 161 |
| 243 old_rev = GitToSvnRev(nacl_git_dir, old_rev_git) | 162 msg_logs, authors = GetLog(nacl_git_dir, old_rev, new_rev) |
| 244 | 163 msg = 'NaCl: Update revision in DEPS, %s -> %s' % (old_rev[:9], new_rev[:9]) |
|
JF
2015/01/28 07:12:17
Isn't the default short hash 7?
Mark Seaborn
2015/01/28 16:34:21
Yes, JF is right. Let's use 7.
bradn
2015/01/28 17:37:32
Done.
bradn
2015/01/28 17:37:32
Done.
| |
| 245 msg_logs, authors = GetLog(old_rev, svn_rev) | |
| 246 msg = 'NaCl: Update revision in DEPS, r%i -> r%i' % (old_rev, svn_rev) | |
| 247 msg += '\n\nThis pulls in the following Native Client changes:\n\n' | 164 msg += '\n\nThis pulls in the following Native Client changes:\n\n' |
| 248 msg += msg_logs | 165 msg += msg_logs |
| 249 msg += '\nBUG=none\nTEST=browser_tests and nacl_integration\n' | 166 msg += '\nBUG=none\nTEST=browser_tests and nacl_integration\n' |
| 250 msg += 'CQ_EXTRA_TRYBOTS=tryserver.chromium.linux:linux_rel_precise32,linux_ar m_compile,linux_nacl_sdk_build\n' | 167 msg += 'CQ_EXTRA_TRYBOTS=tryserver.chromium.linux:linux_rel_precise32,linux_ar m_compile,linux_nacl_sdk_build\n' |
| 251 print msg | 168 print msg |
| 252 cc_list = ', '.join(['native-client-reviews@googlegroups.com'] + | 169 cc_list = ', '.join(['native-client-reviews@googlegroups.com'] + |
| 253 sorted(set(authors))) | 170 sorted(set(authors))) |
| 254 print 'CC:', cc_list | 171 print 'CC:', cc_list |
| 255 | 172 |
| 256 # Copy revision numbers across from native_client/DEPS. | |
| 257 # We do this because 'From()' is not supported in Chrome's DEPS. | |
| 258 proc = subprocess.Popen(['svn', 'cat', '%s/DEPS@%s' % (NACL_SVN, svn_rev)], | |
| 259 stdout=subprocess.PIPE) | |
| 260 nacl_deps = proc.communicate()[0] | |
| 261 assert proc.wait() == 0, proc.wait() | |
| 262 tools_rev = GetDepsField(nacl_deps, 'tools_rev') | |
| 263 # Chromium's DEPS file used to contain a single "nacl_tools_revision" | |
| 264 # field that we would update to match NaCl's "tools_rev". Since Chromium | |
| 265 # switched to using Git revisions in DEPS, "nacl_tools_revision" has been | |
| 266 # replaced by multiple fields. We don't currently support updating those | |
| 267 # fields automatically. Instead, just assert that NaCl's "tools_rev" | |
| 268 # hasn't changed. | |
| 269 # TODO(mseaborn): Fix this automatic syncing. Maybe this should wait | |
|
Mark Seaborn
2015/01/28 16:34:21
I guess we can add back this automatic syncing if
bradn
2015/01/28 17:37:32
Yeah I figure its problematic for the moment.
| |
| 270 # until NaCl has also switched to using Git revisions in its DEPS file. | |
| 271 assert tools_rev in ('13077', '13800'), tools_rev | |
| 272 | |
| 273 WriteFile('DEPS', deps_data) | 173 WriteFile('DEPS', deps_data) |
| 274 | 174 |
| 275 if options.no_commit: | 175 if options.no_commit: |
| 276 return | 176 return |
| 277 subprocess.check_call(['git', 'commit', '-a', '-m', msg]) | 177 subprocess.check_call(['git', 'commit', '-a', '-m', msg]) |
| 278 | 178 |
| 279 if options.no_upload: | 179 if options.no_upload: |
| 280 return | 180 return |
| 281 # Override EDITOR so that "git cl upload" will run non-interactively. | 181 # Override EDITOR so that "git cl upload" will run non-interactively. |
| 282 environ = os.environ.copy() | 182 environ = os.environ.copy() |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 294 # This CC does not happen by default for DEPS. | 194 # This CC does not happen by default for DEPS. |
| 295 '--cc', cc_list, | 195 '--cc', cc_list, |
| 296 ], env=environ) | 196 ], env=environ) |
| 297 if options.no_try: | 197 if options.no_try: |
| 298 return | 198 return |
| 299 RunTrybots() | 199 RunTrybots() |
| 300 | 200 |
| 301 | 201 |
| 302 if __name__ == '__main__': | 202 if __name__ == '__main__': |
| 303 Main(sys.argv[1:]) | 203 Main(sys.argv[1:]) |
| OLD | NEW |