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