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, |
11 unzipping, and opening Chromium for you. After testing the specific revision, | 11 unzipping, and opening Chromium for you. After testing the specific revision, |
12 it will ask you whether it is good or bad before continuing the search. | 12 it will ask you whether it is good or bad before continuing the search. |
13 """ | 13 """ |
14 | 14 |
15 # The base URL for stored build archives. | 15 # The base URL for stored build archives. |
16 CHROMIUM_BASE_URL = ('http://commondatastorage.googleapis.com' | 16 CHROMIUM_BASE_URL = ('http://commondatastorage.googleapis.com' |
17 '/chromium-browser-snapshots') | 17 '/chromium-browser-snapshots') |
18 WEBKIT_BASE_URL = ('http://commondatastorage.googleapis.com' | 18 WEBKIT_BASE_URL = ('http://commondatastorage.googleapis.com' |
19 '/chromium-webkit-snapshots') | 19 '/chromium-webkit-snapshots') |
20 ASAN_BASE_URL = ('http://commondatastorage.googleapis.com' | 20 ASAN_BASE_URL = ('http://commondatastorage.googleapis.com' |
21 '/chromium-browser-asan') | 21 '/chromium-browser-asan') |
22 | 22 |
23 # The base URL for official builds. | 23 # The base URL for official builds. |
24 OFFICIAL_BASE_URL = 'http://master.chrome.corp.google.com/official_builds' | 24 OFFICIAL_BASE_URL = ('http://commondatastorage.googleapis.com/' |
25 'chrome-unsigned/desktop-W15K3Y') | |
25 | 26 |
26 # URL template for viewing changelogs between revisions. | 27 # URL template for viewing changelogs between revisions. |
27 CHANGELOG_URL = ('http://build.chromium.org' | 28 CHANGELOG_URL = ('http://build.chromium.org' |
28 '/f/chromium/perf/dashboard/ui/changelog.html' | 29 '/f/chromium/perf/dashboard/ui/changelog.html' |
29 '?url=/trunk/src&range=%d%%3A%d') | 30 '?url=/trunk/src&range=%d%%3A%d') |
30 | 31 |
31 # URL template for viewing changelogs between official versions. | 32 # URL template for viewing changelogs between official versions. |
32 OFFICIAL_CHANGELOG_URL = ('http://omahaproxy.appspot.com/changelog' | 33 OFFICIAL_CHANGELOG_URL = ('http://omahaproxy.appspot.com/changelog' |
33 '?old_version=%s&new_version=%s') | 34 '?old_version=%s&new_version=%s') |
34 | 35 |
35 # DEPS file URL. | 36 # DEPS file URL. |
36 DEPS_FILE = 'http://src.chromium.org/viewvc/chrome/trunk/src/DEPS?revision=%d' | 37 DEPS_FILE = 'http://src.chromium.org/viewvc/chrome/trunk/src/DEPS?revision=%d' |
37 | 38 |
39 # GSUtil download URL. | |
40 GSUTIL_URL = 'http://storage.googleapis.com/pub/gsutil.tar.gz' | |
41 | |
38 # Blink changelogs URL. | 42 # Blink changelogs URL. |
39 BLINK_CHANGELOG_URL = ('http://build.chromium.org' | 43 BLINK_CHANGELOG_URL = ('http://build.chromium.org' |
40 '/f/chromium/perf/dashboard/ui/changelog_blink.html' | 44 '/f/chromium/perf/dashboard/ui/changelog_blink.html' |
41 '?url=/trunk&range=%d%%3A%d') | 45 '?url=/trunk&range=%d%%3A%d') |
42 | 46 |
43 DONE_MESSAGE_GOOD_MIN = ('You are probably looking for a change made after %s (' | 47 DONE_MESSAGE_GOOD_MIN = ('You are probably looking for a change made after %s (' |
44 'known good), but no later than %s (first known bad).') | 48 'known good), but no later than %s (first known bad).') |
45 DONE_MESSAGE_GOOD_MAX = ('You are probably looking for a change made after %s (' | 49 DONE_MESSAGE_GOOD_MAX = ('You are probably looking for a change made after %s (' |
46 'known bad), but no later than %s (first known good).') | 50 'known bad), but no later than %s (first known good).') |
47 | 51 |
(...skipping 18 matching lines...) Expand all Loading... | |
66 BLINK_SEARCH_PATTERN = ( | 70 BLINK_SEARCH_PATTERN = ( |
67 r'.*git-svn-id: svn://svn.chromium.org/blink/trunk@(\d+) ') | 71 r'.*git-svn-id: svn://svn.chromium.org/blink/trunk@(\d+) ') |
68 | 72 |
69 SEARCH_PATTERN = { | 73 SEARCH_PATTERN = { |
70 'chromium': CHROMIUM_SEARCH_PATTERN, | 74 'chromium': CHROMIUM_SEARCH_PATTERN, |
71 'blink': BLINK_SEARCH_PATTERN, | 75 'blink': BLINK_SEARCH_PATTERN, |
72 } | 76 } |
73 | 77 |
74 ############################################################################### | 78 ############################################################################### |
75 | 79 |
80 import cStringIO | |
81 import httplib | |
76 import json | 82 import json |
77 import optparse | 83 import optparse |
78 import os | 84 import os |
79 import re | 85 import re |
80 import shlex | 86 import shlex |
81 import shutil | 87 import shutil |
82 import subprocess | 88 import subprocess |
83 import sys | 89 import sys |
90 import tarfile | |
84 import tempfile | 91 import tempfile |
85 import threading | 92 import threading |
86 import urllib | 93 import urllib |
87 from distutils.version import LooseVersion | 94 from distutils.version import LooseVersion |
88 from xml.etree import ElementTree | 95 from xml.etree import ElementTree |
89 import zipfile | 96 import zipfile |
90 | 97 |
91 | 98 |
92 class PathContext(object): | 99 class PathContext(object): |
93 """A PathContext is used to carry the information used to construct URLs and | 100 """A PathContext is used to carry the information used to construct URLs and |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
132 self._archive_extract_dir = 'chrome-mac' | 139 self._archive_extract_dir = 'chrome-mac' |
133 elif self.platform == 'win': | 140 elif self.platform == 'win': |
134 self.archive_name = 'chrome-win32.zip' | 141 self.archive_name = 'chrome-win32.zip' |
135 self._archive_extract_dir = 'chrome-win32' | 142 self._archive_extract_dir = 'chrome-win32' |
136 self._binary_name = 'chrome.exe' | 143 self._binary_name = 'chrome.exe' |
137 else: | 144 else: |
138 raise Exception('Invalid platform: %s' % self.platform) | 145 raise Exception('Invalid platform: %s' % self.platform) |
139 | 146 |
140 if is_official: | 147 if is_official: |
141 if self.platform == 'linux': | 148 if self.platform == 'linux': |
142 self._listing_platform_dir = 'precise32bit/' | 149 self._listing_platform_dir = 'precise32/' |
143 self.archive_name = 'chrome-precise32bit.zip' | 150 self.archive_name = 'chrome-precise32.zip' |
144 self._archive_extract_dir = 'chrome-precise32bit' | 151 self._archive_extract_dir = 'chrome-precise32' |
145 elif self.platform == 'linux64': | 152 elif self.platform == 'linux64': |
146 self._listing_platform_dir = 'precise64bit/' | 153 self._listing_platform_dir = 'precise64/' |
147 self.archive_name = 'chrome-precise64bit.zip' | 154 self.archive_name = 'chrome-precise64.zip' |
148 self._archive_extract_dir = 'chrome-precise64bit' | 155 self._archive_extract_dir = 'chrome-precise64' |
149 elif self.platform == 'mac': | 156 elif self.platform == 'mac': |
150 self._listing_platform_dir = 'mac/' | 157 self._listing_platform_dir = 'mac/' |
151 self._binary_name = 'Google Chrome.app/Contents/MacOS/Google Chrome' | 158 self._binary_name = 'Google Chrome.app/Contents/MacOS/Google Chrome' |
152 elif self.platform == 'win': | 159 elif self.platform == 'win': |
153 self._listing_platform_dir = 'win/' | 160 self._listing_platform_dir = 'win/' |
154 else: | 161 else: |
155 if self.platform in ('linux', 'linux64', 'linux-arm'): | 162 if self.platform in ('linux', 'linux64', 'linux-arm'): |
156 self.archive_name = 'chrome-linux.zip' | 163 self.archive_name = 'chrome-linux.zip' |
157 self._archive_extract_dir = 'chrome-linux' | 164 self._archive_extract_dir = 'chrome-linux' |
158 if self.platform == 'linux': | 165 if self.platform == 'linux': |
(...skipping 227 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
386 self.good_revision) | 393 self.good_revision) |
387 self.bad_revision = FixChromiumRevForBlink(revlist, | 394 self.bad_revision = FixChromiumRevForBlink(revlist, |
388 revlist_all, | 395 revlist_all, |
389 self, | 396 self, |
390 self.bad_revision) | 397 self.bad_revision) |
391 return revlist | 398 return revlist |
392 | 399 |
393 def GetOfficialBuildsList(self): | 400 def GetOfficialBuildsList(self): |
394 """Gets the list of official build numbers between self.good_revision and | 401 """Gets the list of official build numbers between self.good_revision and |
395 self.bad_revision.""" | 402 self.bad_revision.""" |
403 | |
404 def DownloadGsutil(): | |
405 gsutil_path = os.path.join(os.getcwd(), 'gsutil') | |
406 if not os.path.exists(gsutil_path): | |
407 print 'Downloading gsutil' | |
408 response = urllib.urlopen(GSUTIL_URL) | |
409 if response.getcode() == 200: | |
410 tar_file = tarfile.open(fileobj=cStringIO.StringIO(response.read())) | |
411 tar_file.extractall(os.getcwd()) | |
412 print 'Downloaded gsutil to %s' % gsutil_path | |
413 else: | |
414 raise Exception('HTTP Error %d' % response.getcode()) | |
415 return os.path.join(gsutil_path, 'gsutil') | |
416 | |
417 def RunGsutilCommand(args): | |
418 gsutil_path = DownloadGsutil() | |
419 gsutil = subprocess.Popen([sys.executable, gsutil_path] + args, | |
420 stdout=subprocess.PIPE, stderr=subprocess.PIPE, | |
421 env=None) | |
422 stdout, stderr = gsutil.communicate() | |
423 if gsutil.returncode: | |
424 if ('status=403' in stderr or 'status 403' in stderr or | |
425 'status=401' in stderr or 'status 401' in stderr): | |
426 print ('Follow these steps to configure your credentials and try' | |
427 ' running the bisect-builds.py again.:\n' | |
428 ' 1. Run "python %s config" and follow its instructions.\n' | |
429 ' 2. If you have a @google.com account, use that account.\n' | |
430 ' 3. For the project-id, just enter 0.' % gsutil_path) | |
431 sys.exit(1) | |
432 else: | |
433 raise Exception('Error running the gsutil command') | |
434 return stdout | |
435 | |
436 def GsutilList(bucket): | |
437 query = 'gs://%s/' % bucket | |
438 stdout = RunGsutilCommand(['ls', query]) | |
439 return [url[len(query):].strip('/') for url in stdout.splitlines()] | |
440 | |
441 def IsValidBuildNumber(build): | |
442 revision_re = re.compile(r'(\d\d\.\d\.\d{4}\.\d+)') | |
DaleCurtis
2014/08/20 20:46:43
No point in using re.compile inside the function.
pshenoy
2014/08/20 22:42:43
Done.
| |
443 return revision_re.search(build) | |
444 | |
396 # Download the revlist and filter for just the range between good and bad. | 445 # Download the revlist and filter for just the range between good and bad. |
397 minrev = min(self.good_revision, self.bad_revision) | 446 minrev = min(self.good_revision, self.bad_revision) |
398 maxrev = max(self.good_revision, self.bad_revision) | 447 maxrev = max(self.good_revision, self.bad_revision) |
399 handle = urllib.urlopen(OFFICIAL_BASE_URL) | 448 build_numbers = GsutilList('chrome-unsigned/desktop-W15K3Y') |
400 dirindex = handle.read() | 449 build_numbers = filter(IsValidBuildNumber, build_numbers) |
401 handle.close() | |
402 build_numbers = re.findall(r'<a href="([0-9][0-9].*)/">', dirindex) | |
403 final_list = [] | 450 final_list = [] |
404 i = 0 | 451 i = 0 |
405 parsed_build_numbers = [LooseVersion(x) for x in build_numbers] | 452 parsed_build_numbers = [LooseVersion(x) for x in build_numbers] |
453 connection = httplib.HTTPConnection('commondatastorage.googleapis.com') | |
406 for build_number in sorted(parsed_build_numbers): | 454 for build_number in sorted(parsed_build_numbers): |
407 path = (OFFICIAL_BASE_URL + '/' + str(build_number) + '/' + | 455 if build_number > maxrev: |
456 break | |
457 if build_number < minrev: | |
458 continue | |
459 path = ('/chrome-unsigned/desktop-W15K3Y/' + str(build_number) + '/' + | |
408 self._listing_platform_dir + self.archive_name) | 460 self._listing_platform_dir + self.archive_name) |
409 i = i + 1 | 461 i = i + 1 |
410 try: | 462 connection.request('HEAD', path) |
411 connection = urllib.urlopen(path) | 463 response = connection.getresponse() |
412 connection.close() | 464 if response.status == 200: |
413 if build_number > maxrev: | 465 final_list.append(str(build_number)) |
414 break | 466 response.read() |
415 if build_number >= minrev: | 467 connection.close() |
416 final_list.append(str(build_number)) | 468 return sorted(list(set(final_list))) |
417 except urllib.HTTPError: | |
418 pass | |
419 return final_list | |
420 | 469 |
421 def UnzipFilenameToDir(filename, directory): | 470 def UnzipFilenameToDir(filename, directory): |
422 """Unzip |filename| to |directory|.""" | 471 """Unzip |filename| to |directory|.""" |
423 cwd = os.getcwd() | 472 cwd = os.getcwd() |
424 if not os.path.isabs(filename): | 473 if not os.path.isabs(filename): |
425 filename = os.path.join(cwd, filename) | 474 filename = os.path.join(cwd, filename) |
426 zf = zipfile.ZipFile(filename) | 475 zf = zipfile.ZipFile(filename) |
427 # Make base. | 476 # Make base. |
428 if not os.path.isdir(directory): | 477 if not os.path.isdir(directory): |
429 os.mkdir(directory) | 478 os.mkdir(directory) |
(...skipping 220 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
650 """ | 699 """ |
651 | 700 |
652 if not profile: | 701 if not profile: |
653 profile = 'profile' | 702 profile = 'profile' |
654 | 703 |
655 good_rev = context.good_revision | 704 good_rev = context.good_revision |
656 bad_rev = context.bad_revision | 705 bad_rev = context.bad_revision |
657 cwd = os.getcwd() | 706 cwd = os.getcwd() |
658 | 707 |
659 print 'Downloading list of known revisions...', | 708 print 'Downloading list of known revisions...', |
660 if not context.use_local_repo: | 709 if not context.use_local_repo and not context.is_official: |
661 print '(use --use-local-repo for speed if you have a local checkout)' | 710 print '(use --use-local-repo for speed if you have a local checkout)' |
662 else: | 711 else: |
663 print | 712 print |
664 _GetDownloadPath = lambda rev: os.path.join(cwd, | 713 _GetDownloadPath = lambda rev: os.path.join(cwd, |
665 '%s-%s' % (str(rev), context.archive_name)) | 714 '%s-%s' % (str(rev), context.archive_name)) |
666 if context.is_official: | 715 if context.is_official: |
667 revlist = context.GetOfficialBuildsList() | 716 revlist = context.GetOfficialBuildsList() |
668 else: | 717 else: |
669 revlist = context.GetRevList() | 718 revlist = context.GetRevList() |
670 | 719 |
(...skipping 417 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1088 | 1137 |
1089 print 'CHANGELOG URL:' | 1138 print 'CHANGELOG URL:' |
1090 if opts.official_builds: | 1139 if opts.official_builds: |
1091 print OFFICIAL_CHANGELOG_URL % (min_chromium_rev, max_chromium_rev) | 1140 print OFFICIAL_CHANGELOG_URL % (min_chromium_rev, max_chromium_rev) |
1092 else: | 1141 else: |
1093 print ' ' + CHANGELOG_URL % (min_chromium_rev, max_chromium_rev) | 1142 print ' ' + CHANGELOG_URL % (min_chromium_rev, max_chromium_rev) |
1094 | 1143 |
1095 | 1144 |
1096 if __name__ == '__main__': | 1145 if __name__ == '__main__': |
1097 sys.exit(main()) | 1146 sys.exit(main()) |
OLD | NEW |