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 |