Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium 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 """Snapshot Build Bisect Tool | 6 """Snapshot Build Bisect Tool |
| 7 | 7 |
| 8 This script bisects a snapshot archive using binary search. It starts at | 8 This script bisects a snapshot archive using binary search. It starts at |
| 9 a bad revision (it will try to guess HEAD) and asks for a last known-good | 9 a bad revision (it will try to guess HEAD) and asks for a last known-good |
| 10 revision. It will then binary search across this revision range by downloading, | 10 revision. It will then binary search across this revision range by downloading, |
| (...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 72 import urllib | 72 import urllib |
| 73 from distutils.version import LooseVersion | 73 from distutils.version import LooseVersion |
| 74 from xml.etree import ElementTree | 74 from xml.etree import ElementTree |
| 75 import zipfile | 75 import zipfile |
| 76 | 76 |
| 77 | 77 |
| 78 class PathContext(object): | 78 class PathContext(object): |
| 79 """A PathContext is used to carry the information used to construct URLs and | 79 """A PathContext is used to carry the information used to construct URLs and |
| 80 paths when dealing with the storage server and archives.""" | 80 paths when dealing with the storage server and archives.""" |
| 81 def __init__(self, base_url, platform, good_revision, bad_revision, | 81 def __init__(self, base_url, platform, good_revision, bad_revision, |
| 82 is_official, is_aura, flash_path = None): | 82 is_official, is_aura, use_local_repo, flash_path = None): |
| 83 super(PathContext, self).__init__() | 83 super(PathContext, self).__init__() |
| 84 # Store off the input parameters. | 84 # Store off the input parameters. |
| 85 self.base_url = base_url | 85 self.base_url = base_url |
| 86 self.platform = platform # What's passed in to the '-a/--archive' option. | 86 self.platform = platform # What's passed in to the '-a/--archive' option. |
| 87 self.good_revision = good_revision | 87 self.good_revision = good_revision |
| 88 self.bad_revision = bad_revision | 88 self.bad_revision = bad_revision |
| 89 self.is_official = is_official | 89 self.is_official = is_official |
| 90 self.is_aura = is_aura | 90 self.is_aura = is_aura |
| 91 self.flash_path = flash_path | 91 self.flash_path = flash_path |
| 92 # Dictionary which stores svn revision number as key and it's | 92 # Dictionary which stores svn revision number as key and it's |
| 93 # corresponding git hash as value. This data is populated in | 93 # corresponding git hash as value. This data is populated in |
| 94 # _FetchAndParse and used later in GetDownloadURL while downloading | 94 # _FetchAndParse and used later in GetDownloadURL while downloading |
| 95 # the build. | 95 # the build. |
| 96 self.githash_svn_dict = {} | 96 self.githash_svn_dict = {} |
| 97 | 97 |
| 98 # The name of the ZIP file in a revision directory on the server. | 98 # The name of the ZIP file in a revision directory on the server. |
| 99 self.archive_name = None | 99 self.archive_name = None |
| 100 self.use_local_repo = use_local_repo | |
|
Robert Sesek
2014/07/08 18:52:58
nit: blank line before and a comment about what th
pshenoy
2014/07/08 19:58:19
Done.
| |
| 100 | 101 |
| 101 # Set some internal members: | 102 # Set some internal members: |
| 102 # _listing_platform_dir = Directory that holds revisions. Ends with a '/'. | 103 # _listing_platform_dir = Directory that holds revisions. Ends with a '/'. |
| 103 # _archive_extract_dir = Uncompressed directory in the archive_name file. | 104 # _archive_extract_dir = Uncompressed directory in the archive_name file. |
| 104 # _binary_name = The name of the executable to run. | 105 # _binary_name = The name of the executable to run. |
| 105 if self.platform in ('linux', 'linux64', 'linux-arm'): | 106 if self.platform in ('linux', 'linux64', 'linux-arm'): |
| 106 self._binary_name = 'chrome' | 107 self._binary_name = 'chrome' |
| 107 elif self.platform == 'mac': | 108 elif self.platform == 'mac': |
| 108 self.archive_name = 'chrome-mac.zip' | 109 self.archive_name = 'chrome-mac.zip' |
| 109 self._archive_extract_dir = 'chrome-mac' | 110 self._archive_extract_dir = 'chrome-mac' |
| (...skipping 136 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 246 _FetchAndParse(self.GetListingURL()) | 247 _FetchAndParse(self.GetListingURL()) |
| 247 # If the result list was truncated, refetch with the next marker. Do this | 248 # If the result list was truncated, refetch with the next marker. Do this |
| 248 # until an entire directory listing is done. | 249 # until an entire directory listing is done. |
| 249 while next_marker: | 250 while next_marker: |
| 250 next_url = self.GetListingURL(next_marker) | 251 next_url = self.GetListingURL(next_marker) |
| 251 (new_revisions, next_marker, new_dict) = _FetchAndParse(next_url) | 252 (new_revisions, next_marker, new_dict) = _FetchAndParse(next_url) |
| 252 revisions.extend(new_revisions) | 253 revisions.extend(new_revisions) |
| 253 self.githash_svn_dict.update(new_dict) | 254 self.githash_svn_dict.update(new_dict) |
| 254 return revisions | 255 return revisions |
| 255 | 256 |
| 256 def GetSVNRevisionFromGitHash(self, git_sha1, depot='chromium'): | 257 def _GetSVNRevisionFromGitHashWithoutGitCheckout(self, git_sha1, depot): |
| 257 json_url = GITHASH_TO_SVN_URL[depot] % git_sha1 | 258 json_url = GITHASH_TO_SVN_URL[depot] % git_sha1 |
| 258 try: | 259 try: |
| 259 response = urllib.urlopen(json_url) | 260 response = urllib.urlopen(json_url) |
| 260 except urllib.HTTPError as error: | 261 except urllib.HTTPError as error: |
| 261 msg = 'HTTP Error %d for %s' % (error.getcode(), git_sha1) | 262 msg = 'HTTP Error %d for %s' % (error.getcode(), git_sha1) |
| 262 return None | 263 return None |
| 263 data = json.loads(response.read()[4:]) | 264 data = json.loads(response.read()[4:]) |
| 264 if 'message' in data: | 265 if 'message' in data: |
| 265 message = data['message'].split('\n') | 266 message = data['message'].split('\n') |
| 266 message = [line for line in message if line.strip()] | 267 message = [line for line in message if line.strip()] |
| 267 search_pattern = re.compile(SEARCH_PATTERN[depot]) | 268 search_pattern = re.compile(SEARCH_PATTERN[depot]) |
| 268 result = search_pattern.search(message[len(message)-1]) | 269 result = search_pattern.search(message[len(message)-1]) |
| 269 if result: | 270 if result: |
| 270 return result.group(1) | 271 return result.group(1) |
| 271 print 'Failed to get svn revision number for %s' % git_sha1 | 272 print 'Failed to get svn revision number for %s' % git_sha1 |
| 272 return None | 273 return None |
| 273 | 274 |
| 275 def _GetSVNRevisionFromGitHashFromGitCheckout(self, git_sha1, depot): | |
| 276 def _RunGit(command, path): | |
| 277 command = ['git'] + command | |
| 278 if path: | |
| 279 original_path = os.getcwd() | |
| 280 os.chdir(path) | |
| 281 shell = sys.platform.startswith('win') | |
| 282 proc = subprocess.Popen(command, shell=shell, stdout=subprocess.PIPE, | |
| 283 stderr=subprocess.PIPE) | |
| 284 (output, _) = proc.communicate() | |
| 285 | |
| 286 if path: | |
| 287 os.chdir(original_path) | |
| 288 return (output, proc.returncode) | |
| 289 | |
| 290 path = None | |
| 291 if depot == 'blink': | |
| 292 path = os.path.join(os.getcwd(), 'third_party', 'WebKit') | |
| 293 if os.path.basename(os.getcwd()) == 'src': | |
| 294 command = [] | |
| 295 command.append('svn') | |
|
Robert Sesek
2014/07/08 18:52:58
Why .append() these things? command = ['svn', 'fin
pshenoy
2014/07/08 19:58:19
Done.
| |
| 296 command.append('find-rev') | |
| 297 command.append(git_sha1) | |
| 298 (git_output, return_code) = _RunGit(command, path) | |
| 299 if not return_code: | |
| 300 return git_output.strip('\n') | |
| 301 return None | |
| 302 else: | |
| 303 print ('Script should be run from src folder. ' + | |
| 304 'Eg: python tools/bisect-builds.py -g 280588 -b 280590' + | |
| 305 '--archive linux64 --git') | |
|
Robert Sesek
2014/07/08 18:52:58
--use-local-repo
pshenoy
2014/07/08 19:58:19
Done.
| |
| 306 sys.exit(0) | |
|
Robert Sesek
2014/07/08 18:52:58
Code 1 for errors
pshenoy
2014/07/08 19:58:19
Done.
| |
| 307 | |
| 308 def GetSVNRevisionFromGitHash(self, git_sha1, depot='chromium'): | |
| 309 if not self.use_local_repo: | |
| 310 return self._GetSVNRevisionFromGitHashWithoutGitCheckout(git_sha1, depot) | |
| 311 else: | |
| 312 return self._GetSVNRevisionFromGitHashFromGitCheckout(git_sha1, depot) | |
| 313 | |
| 274 def GetRevList(self): | 314 def GetRevList(self): |
| 275 """Gets the list of revision numbers between self.good_revision and | 315 """Gets the list of revision numbers between self.good_revision and |
| 276 self.bad_revision.""" | 316 self.bad_revision.""" |
| 277 # Download the revlist and filter for just the range between good and bad. | 317 # Download the revlist and filter for just the range between good and bad. |
| 278 minrev = min(self.good_revision, self.bad_revision) | 318 minrev = min(self.good_revision, self.bad_revision) |
| 279 maxrev = max(self.good_revision, self.bad_revision) | 319 maxrev = max(self.good_revision, self.bad_revision) |
| 280 revlist_all = map(int, self.ParseDirectoryIndex()) | 320 revlist_all = map(int, self.ParseDirectoryIndex()) |
| 281 | 321 |
| 282 revlist = [x for x in revlist_all if x >= int(minrev) and x <= int(maxrev)] | 322 revlist = [x for x in revlist_all if x >= int(minrev) and x <= int(maxrev)] |
| 283 revlist.sort() | 323 revlist.sort() |
| (...skipping 222 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 506 must have been started previously.""" | 546 must have been started previously.""" |
| 507 print "Downloading revision %s..." % str(self.rev) | 547 print "Downloading revision %s..." % str(self.rev) |
| 508 self.progress_event.set() # Display progress of download. | 548 self.progress_event.set() # Display progress of download. |
| 509 self.thread.join() | 549 self.thread.join() |
| 510 | 550 |
| 511 | 551 |
| 512 def Bisect(base_url, | 552 def Bisect(base_url, |
| 513 platform, | 553 platform, |
| 514 official_builds, | 554 official_builds, |
| 515 is_aura, | 555 is_aura, |
| 556 use_local_repo, | |
| 516 good_rev=0, | 557 good_rev=0, |
| 517 bad_rev=0, | 558 bad_rev=0, |
| 518 num_runs=1, | 559 num_runs=1, |
| 519 command="%p %a", | 560 command="%p %a", |
| 520 try_args=(), | 561 try_args=(), |
| 521 profile=None, | 562 profile=None, |
| 522 flash_path=None, | 563 flash_path=None, |
| 523 interactive=True, | 564 interactive=True, |
| 524 evaluate=AskIsGoodBuild): | 565 evaluate=AskIsGoodBuild): |
| 525 """Given known good and known bad revisions, run a binary search on all | 566 """Given known good and known bad revisions, run a binary search on all |
| (...skipping 23 matching lines...) Expand all Loading... | |
| 549 is run on rev 75. | 590 is run on rev 75. |
| 550 | 591 |
| 551 - If rev 50 is bad, the download of rev 75 is cancelled, and the next test | 592 - If rev 50 is bad, the download of rev 75 is cancelled, and the next test |
| 552 is run on rev 25. | 593 is run on rev 25. |
| 553 """ | 594 """ |
| 554 | 595 |
| 555 if not profile: | 596 if not profile: |
| 556 profile = 'profile' | 597 profile = 'profile' |
| 557 | 598 |
| 558 context = PathContext(base_url, platform, good_rev, bad_rev, | 599 context = PathContext(base_url, platform, good_rev, bad_rev, |
| 559 official_builds, is_aura, flash_path) | 600 official_builds, is_aura, use_local_repo, flash_path) |
| 560 cwd = os.getcwd() | 601 cwd = os.getcwd() |
| 561 | 602 |
| 562 print "Downloading list of known revisions..." | 603 print "Downloading list of known revisions..." |
| 563 _GetDownloadPath = lambda rev: os.path.join(cwd, | 604 _GetDownloadPath = lambda rev: os.path.join(cwd, |
| 564 '%s-%s' % (str(rev), context.archive_name)) | 605 '%s-%s' % (str(rev), context.archive_name)) |
| 565 if official_builds: | 606 if official_builds: |
| 566 revlist = context.GetOfficialBuildsList() | 607 revlist = context.GetOfficialBuildsList() |
| 567 else: | 608 else: |
| 568 revlist = context.GetRevList() | 609 revlist = context.GetRevList() |
| 569 | 610 |
| (...skipping 268 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 838 parser.add_option('-l', '--blink', action='store_true', | 879 parser.add_option('-l', '--blink', action='store_true', |
| 839 help = 'Use Blink bisect instead of Chromium. ') | 880 help = 'Use Blink bisect instead of Chromium. ') |
| 840 parser.add_option('', '--not-interactive', action='store_true', | 881 parser.add_option('', '--not-interactive', action='store_true', |
| 841 help = 'Use command exit code to tell good/bad revision.', | 882 help = 'Use command exit code to tell good/bad revision.', |
| 842 default=False) | 883 default=False) |
| 843 parser.add_option('--aura', | 884 parser.add_option('--aura', |
| 844 dest='aura', | 885 dest='aura', |
| 845 action='store_true', | 886 action='store_true', |
| 846 default=False, | 887 default=False, |
| 847 help='Allow the script to bisect aura builds') | 888 help='Allow the script to bisect aura builds') |
| 889 parser.add_option('--use-local-repo', | |
| 890 dest='use_local_repo', | |
| 891 action='store_true', | |
| 892 default=False, | |
| 893 help='Allow the script to convert git SHA1 to SVN ' + | |
| 894 'revision using "git svn find-rev <SHA1>" ' + | |
| 895 'command.') | |
|
Robert Sesek
2014/07/08 18:52:58
"from a Chromium checkout."
pshenoy
2014/07/08 19:58:19
Done.
| |
| 848 | 896 |
| 849 (opts, args) = parser.parse_args() | 897 (opts, args) = parser.parse_args() |
| 850 | 898 |
| 851 if opts.archive is None: | 899 if opts.archive is None: |
| 852 print 'Error: missing required parameter: --archive' | 900 print 'Error: missing required parameter: --archive' |
| 853 print | 901 print |
| 854 parser.print_help() | 902 parser.print_help() |
| 855 return 1 | 903 return 1 |
| 856 | 904 |
| 857 if opts.aura: | 905 if opts.aura: |
| 858 if opts.archive != 'win' or not opts.official_builds: | 906 if opts.archive != 'win' or not opts.official_builds: |
| 859 print 'Error: Aura is supported only on Windows platform '\ | 907 print 'Error: Aura is supported only on Windows platform '\ |
| 860 'and official builds.' | 908 'and official builds.' |
| 861 return 1 | 909 return 1 |
| 862 | 910 |
| 863 if opts.blink: | 911 if opts.blink: |
| 864 base_url = WEBKIT_BASE_URL | 912 base_url = WEBKIT_BASE_URL |
| 865 else: | 913 else: |
| 866 base_url = CHROMIUM_BASE_URL | 914 base_url = CHROMIUM_BASE_URL |
| 867 | 915 |
| 868 # Create the context. Initialize 0 for the revisions as they are set below. | 916 # Create the context. Initialize 0 for the revisions as they are set below. |
| 869 context = PathContext(base_url, opts.archive, 0, 0, | 917 context = PathContext(base_url, opts.archive, 0, 0, |
| 870 opts.official_builds, opts.aura, None) | 918 opts.official_builds, opts.aura, opts.use_local_repo, |
| 919 None) | |
| 871 # Pick a starting point, try to get HEAD for this. | 920 # Pick a starting point, try to get HEAD for this. |
| 872 if opts.bad: | 921 if opts.bad: |
| 873 bad_rev = opts.bad | 922 bad_rev = opts.bad |
| 874 else: | 923 else: |
| 875 bad_rev = '999.0.0.0' | 924 bad_rev = '999.0.0.0' |
| 876 if not opts.official_builds: | 925 if not opts.official_builds: |
| 877 bad_rev = GetChromiumRevision(context, context.GetLastChangeURL()) | 926 bad_rev = GetChromiumRevision(context, context.GetLastChangeURL()) |
| 878 | 927 |
| 879 # Find out when we were good. | 928 # Find out when we were good. |
| 880 if opts.good: | 929 if opts.good: |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 894 good_rev = int(good_rev) | 943 good_rev = int(good_rev) |
| 895 bad_rev = int(bad_rev) | 944 bad_rev = int(bad_rev) |
| 896 | 945 |
| 897 if opts.times < 1: | 946 if opts.times < 1: |
| 898 print('Number of times to run (%d) must be greater than or equal to 1.' % | 947 print('Number of times to run (%d) must be greater than or equal to 1.' % |
| 899 opts.times) | 948 opts.times) |
| 900 parser.print_help() | 949 parser.print_help() |
| 901 return 1 | 950 return 1 |
| 902 | 951 |
| 903 (min_chromium_rev, max_chromium_rev) = Bisect( | 952 (min_chromium_rev, max_chromium_rev) = Bisect( |
| 904 base_url, opts.archive, opts.official_builds, opts.aura, good_rev, | 953 base_url, opts.archive, opts.official_builds, opts.aura, |
| 905 bad_rev, opts.times, opts.command, args, opts.profile, opts.flash_path, | 954 opts.use_local_repo, good_rev, bad_rev, opts.times, opts.command, |
| 906 not opts.not_interactive) | 955 args, opts.profile, opts.flash_path, not opts.not_interactive) |
| 907 | 956 |
| 908 # Get corresponding blink revisions. | 957 # Get corresponding blink revisions. |
| 909 try: | 958 try: |
| 910 min_blink_rev = GetBlinkRevisionForChromiumRevision(context, | 959 min_blink_rev = GetBlinkRevisionForChromiumRevision(context, |
| 911 min_chromium_rev) | 960 min_chromium_rev) |
| 912 max_blink_rev = GetBlinkRevisionForChromiumRevision(context, | 961 max_blink_rev = GetBlinkRevisionForChromiumRevision(context, |
| 913 max_chromium_rev) | 962 max_chromium_rev) |
| 914 except Exception, e: | 963 except Exception, e: |
| 915 # Silently ignore the failure. | 964 # Silently ignore the failure. |
| 916 min_blink_rev, max_blink_rev = 0, 0 | 965 min_blink_rev, max_blink_rev = 0, 0 |
| (...skipping 21 matching lines...) Expand all Loading... | |
| 938 "you might also want to do a Blink bisect.") | 987 "you might also want to do a Blink bisect.") |
| 939 | 988 |
| 940 print 'CHANGELOG URL:' | 989 print 'CHANGELOG URL:' |
| 941 if opts.official_builds: | 990 if opts.official_builds: |
| 942 print OFFICIAL_CHANGELOG_URL % (min_chromium_rev, max_chromium_rev) | 991 print OFFICIAL_CHANGELOG_URL % (min_chromium_rev, max_chromium_rev) |
| 943 else: | 992 else: |
| 944 print ' ' + CHANGELOG_URL % (min_chromium_rev, max_chromium_rev) | 993 print ' ' + CHANGELOG_URL % (min_chromium_rev, max_chromium_rev) |
| 945 | 994 |
| 946 if __name__ == '__main__': | 995 if __name__ == '__main__': |
| 947 sys.exit(main()) | 996 sys.exit(main()) |
| OLD | NEW |