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, |
| 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 |