blob: c2105b8b6ddc75d1ac7f5a53a78dfa652dd50ab4 [file] [log] [blame]
[email protected]cb155a82011-11-29 17:25:341#!/usr/bin/env python
[email protected]5e93cf162012-01-28 02:16:562# Copyright (c) 2012 The Chromium Authors. All rights reserved.
[email protected]67e0bc62009-09-03 22:06:093# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Snapshot Build Bisect Tool
7
[email protected]7ad66a72009-09-04 17:52:338This script bisects a snapshot archive using binary search. It starts at
[email protected]67e0bc62009-09-03 22:06:099a bad revision (it will try to guess HEAD) and asks for a last known-good
10revision. It will then binary search across this revision range by downloading,
11unzipping, and opening Chromium for you. After testing the specific revision,
12it will ask you whether it is good or bad before continuing the search.
[email protected]67e0bc62009-09-03 22:06:0913"""
14
[email protected]4df583c2014-07-31 17:11:5515# The base URL for stored build archives.
16CHROMIUM_BASE_URL = ('http://commondatastorage.googleapis.com'
17 '/chromium-browser-snapshots')
18WEBKIT_BASE_URL = ('http://commondatastorage.googleapis.com'
19 '/chromium-webkit-snapshots')
[email protected]011886692014-08-01 21:00:2120ASAN_BASE_URL = ('http://commondatastorage.googleapis.com'
21 '/chromium-browser-asan')
[email protected]67e0bc62009-09-03 22:06:0922
[email protected]83048502014-08-21 16:48:4423# GS bucket name.
24GS_BUCKET_NAME = 'chrome-unsigned/desktop-W15K3Y'
25
26# Base URL for downloading official builds.
27GOOGLE_APIS_URL = 'commondatastorage.googleapis.com'
28
[email protected]4df583c2014-07-31 17:11:5529# The base URL for official builds.
[email protected]83048502014-08-21 16:48:4430OFFICIAL_BASE_URL = 'http://%s/%s' % (GOOGLE_APIS_URL, GS_BUCKET_NAME)
[email protected]d0149c5c2012-05-29 21:12:1131
[email protected]4df583c2014-07-31 17:11:5532# URL template for viewing changelogs between revisions.
pshenoy9ce271f2014-09-02 22:14:0533CHANGELOG_URL = ('https://chromium.googlesource.com/chromium/src/+log/%s..%s')
34
35# URL to convert SVN revision to git hash.
pshenoy13cb79e02014-09-05 01:42:5336CRREV_URL = ('https://cr-rev.appspot.com/_ah/api/crrev/v1/redirect/')
[email protected]f6a71a72009-10-08 19:55:3837
[email protected]4df583c2014-07-31 17:11:5538# URL template for viewing changelogs between official versions.
pshenoyea8fe7d42014-08-26 22:25:2639OFFICIAL_CHANGELOG_URL = ('https://chromium.googlesource.com/chromium/'
40 'src/+log/%s..%s?pretty=full')
[email protected]d0149c5c2012-05-29 21:12:1141
[email protected]b2fe7f22011-10-25 22:58:3142# DEPS file URL.
pshenoycd6bd682014-09-10 20:50:2243DEPS_FILE_OLD = ('http://src.chromium.org/viewvc/chrome/trunk/src/'
44 'DEPS?revision=%d')
45DEPS_FILE_NEW = ('https://chromium.googlesource.com/chromium/src/+/%s/DEPS')
[email protected]b2fe7f22011-10-25 22:58:3146
[email protected]4df583c2014-07-31 17:11:5547# Blink changelogs URL.
48BLINK_CHANGELOG_URL = ('http://build.chromium.org'
49 '/f/chromium/perf/dashboard/ui/changelog_blink.html'
50 '?url=/trunk&range=%d%%3A%d')
51
52DONE_MESSAGE_GOOD_MIN = ('You are probably looking for a change made after %s ('
53 'known good), but no later than %s (first known bad).')
54DONE_MESSAGE_GOOD_MAX = ('You are probably looking for a change made after %s ('
55 'known bad), but no later than %s (first known good).')
[email protected]05ff3fd2012-04-17 23:24:0656
[email protected]3e7c85322014-06-27 20:27:3657CHROMIUM_GITHASH_TO_SVN_URL = (
58 'https://chromium.googlesource.com/chromium/src/+/%s?format=json')
[email protected]4df583c2014-07-31 17:11:5559
[email protected]3e7c85322014-06-27 20:27:3660BLINK_GITHASH_TO_SVN_URL = (
61 'https://chromium.googlesource.com/chromium/blink/+/%s?format=json')
[email protected]4df583c2014-07-31 17:11:5562
63GITHASH_TO_SVN_URL = {
64 'chromium': CHROMIUM_GITHASH_TO_SVN_URL,
65 'blink': BLINK_GITHASH_TO_SVN_URL,
66}
67
68# Search pattern to be matched in the JSON output from
[email protected]3e7c85322014-06-27 20:27:3669# CHROMIUM_GITHASH_TO_SVN_URL to get the chromium revision (svn revision).
pshenoyb23a1452014-09-05 22:52:0570CHROMIUM_SEARCH_PATTERN_OLD = (
[email protected]3e7c85322014-06-27 20:27:3671 r'.*git-svn-id: svn://svn.chromium.org/chrome/trunk/src@(\d+) ')
pshenoyb23a1452014-09-05 22:52:0572CHROMIUM_SEARCH_PATTERN = (
73 r'Cr-Commit-Position: refs/heads/master@{#(\d+)}')
[email protected]4df583c2014-07-31 17:11:5574
[email protected]3e7c85322014-06-27 20:27:3675# Search pattern to be matched in the json output from
76# BLINK_GITHASH_TO_SVN_URL to get the blink revision (svn revision).
77BLINK_SEARCH_PATTERN = (
78 r'.*git-svn-id: svn://svn.chromium.org/blink/trunk@(\d+) ')
[email protected]4df583c2014-07-31 17:11:5579
80SEARCH_PATTERN = {
81 'chromium': CHROMIUM_SEARCH_PATTERN,
82 'blink': BLINK_SEARCH_PATTERN,
83}
[email protected]3e7c85322014-06-27 20:27:3684
[email protected]480369782014-08-22 20:15:5885CREDENTIAL_ERROR_MESSAGE = ('You are attempting to access protected data with '
86 'no configured credentials')
87
[email protected]67e0bc62009-09-03 22:06:0988###############################################################################
89
[email protected]83048502014-08-21 16:48:4490import httplib
[email protected]4c6fec6b2013-09-17 17:44:0891import json
[email protected]7ad66a72009-09-04 17:52:3392import optparse
[email protected]67e0bc62009-09-03 22:06:0993import os
94import re
[email protected]61ea90a2013-09-26 10:17:3495import shlex
[email protected]67e0bc62009-09-03 22:06:0996import shutil
[email protected]afe30662011-07-30 01:05:5297import subprocess
[email protected]67e0bc62009-09-03 22:06:0998import sys
[email protected]7ad66a72009-09-04 17:52:3399import tempfile
[email protected]afe30662011-07-30 01:05:52100import threading
[email protected]67e0bc62009-09-03 22:06:09101import urllib
[email protected]d0149c5c2012-05-29 21:12:11102from distutils.version import LooseVersion
[email protected]183706d92011-06-10 13:06:22103from xml.etree import ElementTree
[email protected]bd8dcb92010-03-31 01:05:24104import zipfile
105
[email protected]cb155a82011-11-29 17:25:34106
[email protected]183706d92011-06-10 13:06:22107class PathContext(object):
108 """A PathContext is used to carry the information used to construct URLs and
109 paths when dealing with the storage server and archives."""
[email protected]4c6fec6b2013-09-17 17:44:08110 def __init__(self, base_url, platform, good_revision, bad_revision,
rob724c9062015-01-22 00:26:42111 is_official, is_asan, use_local_cache, flash_path = None,
John Budorick06e5df12015-02-27 17:44:27112 pdf_path = None):
[email protected]183706d92011-06-10 13:06:22113 super(PathContext, self).__init__()
114 # Store off the input parameters.
[email protected]4c6fec6b2013-09-17 17:44:08115 self.base_url = base_url
[email protected]183706d92011-06-10 13:06:22116 self.platform = platform # What's passed in to the '-a/--archive' option.
117 self.good_revision = good_revision
118 self.bad_revision = bad_revision
[email protected]d0149c5c2012-05-29 21:12:11119 self.is_official = is_official
[email protected]011886692014-08-01 21:00:21120 self.is_asan = is_asan
121 self.build_type = 'release'
[email protected]fc3702e2013-11-09 04:23:00122 self.flash_path = flash_path
[email protected]3e7c85322014-06-27 20:27:36123 # Dictionary which stores svn revision number as key and it's
124 # corresponding git hash as value. This data is populated in
125 # _FetchAndParse and used later in GetDownloadURL while downloading
126 # the build.
127 self.githash_svn_dict = {}
[email protected]cdb77062014-07-21 18:07:15128 self.pdf_path = pdf_path
[email protected]183706d92011-06-10 13:06:22129 # The name of the ZIP file in a revision directory on the server.
130 self.archive_name = None
131
rob724c9062015-01-22 00:26:42132 # Whether to cache and use the list of known revisions in a local file to
133 # speed up the initialization of the script at the next run.
134 self.use_local_cache = use_local_cache
135
136 # Locate the local checkout to speed up the script by using locally stored
137 # metadata.
138 abs_file_path = os.path.abspath(os.path.realpath(__file__))
139 local_src_path = os.path.join(os.path.dirname(abs_file_path), '..')
140 if abs_file_path.endswith(os.path.join('tools', 'bisect-builds.py')) and\
141 os.path.exists(os.path.join(local_src_path, '.git')):
142 self.local_src_path = os.path.normpath(local_src_path)
143 else:
144 self.local_src_path = None
[email protected]6a7a5d62014-07-09 04:45:50145
[email protected]183706d92011-06-10 13:06:22146 # Set some internal members:
147 # _listing_platform_dir = Directory that holds revisions. Ends with a '/'.
148 # _archive_extract_dir = Uncompressed directory in the archive_name file.
149 # _binary_name = The name of the executable to run.
dmazzoni76e907d2015-01-22 08:14:49150 if self.platform in ('linux', 'linux64', 'linux-arm', 'chromeos'):
[email protected]183706d92011-06-10 13:06:22151 self._binary_name = 'chrome'
[email protected]480369782014-08-22 20:15:58152 elif self.platform in ('mac', 'mac64'):
[email protected]183706d92011-06-10 13:06:22153 self.archive_name = 'chrome-mac.zip'
154 self._archive_extract_dir = 'chrome-mac'
[email protected]480369782014-08-22 20:15:58155 elif self.platform in ('win', 'win64'):
[email protected]183706d92011-06-10 13:06:22156 self.archive_name = 'chrome-win32.zip'
157 self._archive_extract_dir = 'chrome-win32'
158 self._binary_name = 'chrome.exe'
159 else:
[email protected]afe30662011-07-30 01:05:52160 raise Exception('Invalid platform: %s' % self.platform)
[email protected]183706d92011-06-10 13:06:22161
[email protected]d0149c5c2012-05-29 21:12:11162 if is_official:
163 if self.platform == 'linux':
[email protected]83048502014-08-21 16:48:44164 self._listing_platform_dir = 'precise32/'
165 self.archive_name = 'chrome-precise32.zip'
166 self._archive_extract_dir = 'chrome-precise32'
[email protected]d0149c5c2012-05-29 21:12:11167 elif self.platform == 'linux64':
[email protected]83048502014-08-21 16:48:44168 self._listing_platform_dir = 'precise64/'
169 self.archive_name = 'chrome-precise64.zip'
170 self._archive_extract_dir = 'chrome-precise64'
[email protected]d0149c5c2012-05-29 21:12:11171 elif self.platform == 'mac':
172 self._listing_platform_dir = 'mac/'
173 self._binary_name = 'Google Chrome.app/Contents/MacOS/Google Chrome'
[email protected]480369782014-08-22 20:15:58174 elif self.platform == 'mac64':
175 self._listing_platform_dir = 'mac64/'
176 self._binary_name = 'Google Chrome.app/Contents/MacOS/Google Chrome'
[email protected]d0149c5c2012-05-29 21:12:11177 elif self.platform == 'win':
[email protected]965e18fc2014-08-14 20:10:18178 self._listing_platform_dir = 'win/'
[email protected]00ae3c02014-08-21 18:41:23179 self.archive_name = 'chrome-win.zip'
180 self._archive_extract_dir = 'chrome-win'
[email protected]480369782014-08-22 20:15:58181 elif self.platform == 'win64':
182 self._listing_platform_dir = 'win64/'
183 self.archive_name = 'chrome-win64.zip'
184 self._archive_extract_dir = 'chrome-win64'
[email protected]d0149c5c2012-05-29 21:12:11185 else:
dmazzoni76e907d2015-01-22 08:14:49186 if self.platform in ('linux', 'linux64', 'linux-arm', 'chromeos'):
[email protected]d0149c5c2012-05-29 21:12:11187 self.archive_name = 'chrome-linux.zip'
188 self._archive_extract_dir = 'chrome-linux'
189 if self.platform == 'linux':
190 self._listing_platform_dir = 'Linux/'
191 elif self.platform == 'linux64':
192 self._listing_platform_dir = 'Linux_x64/'
[email protected]7aec9e82013-05-09 05:09:23193 elif self.platform == 'linux-arm':
194 self._listing_platform_dir = 'Linux_ARM_Cross-Compile/'
dmazzoni76e907d2015-01-22 08:14:49195 elif self.platform == 'chromeos':
196 self._listing_platform_dir = 'Linux_ChromiumOS_Full/'
[email protected]d0149c5c2012-05-29 21:12:11197 elif self.platform == 'mac':
198 self._listing_platform_dir = 'Mac/'
199 self._binary_name = 'Chromium.app/Contents/MacOS/Chromium'
200 elif self.platform == 'win':
201 self._listing_platform_dir = 'Win/'
202
[email protected]011886692014-08-01 21:00:21203 def GetASANPlatformDir(self):
204 """ASAN builds are in directories like "linux-release", or have filenames
205 like "asan-win32-release-277079.zip". This aligns to our platform names
206 except in the case of Windows where they use "win32" instead of "win"."""
207 if self.platform == 'win':
208 return 'win32'
209 else:
210 return self.platform
211
[email protected]183706d92011-06-10 13:06:22212 def GetListingURL(self, marker=None):
213 """Returns the URL for a directory listing, with an optional marker."""
214 marker_param = ''
215 if marker:
216 marker_param = '&marker=' + str(marker)
[email protected]011886692014-08-01 21:00:21217 if self.is_asan:
218 prefix = '%s-%s' % (self.GetASANPlatformDir(), self.build_type)
219 return self.base_url + '/?delimiter=&prefix=' + prefix + marker_param
220 else:
221 return (self.base_url + '/?delimiter=/&prefix=' +
222 self._listing_platform_dir + marker_param)
[email protected]183706d92011-06-10 13:06:22223
224 def GetDownloadURL(self, revision):
225 """Gets the download URL for a build archive of a specific revision."""
[email protected]011886692014-08-01 21:00:21226 if self.is_asan:
227 return '%s/%s-%s/%s-%d.zip' % (
228 ASAN_BASE_URL, self.GetASANPlatformDir(), self.build_type,
229 self.GetASANBaseName(), revision)
[email protected]d0149c5c2012-05-29 21:12:11230 if self.is_official:
[email protected]4df583c2014-07-31 17:11:55231 return '%s/%s/%s%s' % (
John Budorick06e5df12015-02-27 17:44:27232 OFFICIAL_BASE_URL, revision, self._listing_platform_dir,
[email protected]d0149c5c2012-05-29 21:12:11233 self.archive_name)
234 else:
John Budorick06e5df12015-02-27 17:44:27235 if str(revision) in self.githash_svn_dict:
236 revision = self.githash_svn_dict[str(revision)]
237 return '%s/%s%s/%s' % (self.base_url, self._listing_platform_dir,
238 revision, self.archive_name)
[email protected]183706d92011-06-10 13:06:22239
240 def GetLastChangeURL(self):
241 """Returns a URL to the LAST_CHANGE file."""
[email protected]4c6fec6b2013-09-17 17:44:08242 return self.base_url + '/' + self._listing_platform_dir + 'LAST_CHANGE'
[email protected]183706d92011-06-10 13:06:22243
[email protected]011886692014-08-01 21:00:21244 def GetASANBaseName(self):
245 """Returns the base name of the ASAN zip file."""
246 if 'linux' in self.platform:
247 return 'asan-symbolized-%s-%s' % (self.GetASANPlatformDir(),
248 self.build_type)
249 else:
250 return 'asan-%s-%s' % (self.GetASANPlatformDir(), self.build_type)
251
252 def GetLaunchPath(self, revision):
[email protected]183706d92011-06-10 13:06:22253 """Returns a relative path (presumably from the archive extraction location)
254 that is used to run the executable."""
[email protected]011886692014-08-01 21:00:21255 if self.is_asan:
256 extract_dir = '%s-%d' % (self.GetASANBaseName(), revision)
257 else:
258 extract_dir = self._archive_extract_dir
259 return os.path.join(extract_dir, self._binary_name)
[email protected]183706d92011-06-10 13:06:22260
rob724c9062015-01-22 00:26:42261 def ParseDirectoryIndex(self, last_known_rev):
[email protected]afe30662011-07-30 01:05:52262 """Parses the Google Storage directory listing into a list of revision
[email protected]eadd95d2012-11-02 22:42:09263 numbers."""
[email protected]afe30662011-07-30 01:05:52264
rob724c9062015-01-22 00:26:42265 def _GetMarkerForRev(revision):
266 if self.is_asan:
267 return '%s-%s/%s-%d.zip' % (
268 self.GetASANPlatformDir(), self.build_type,
269 self.GetASANBaseName(), revision)
270 return '%s%d' % (self._listing_platform_dir, revision)
271
[email protected]afe30662011-07-30 01:05:52272 def _FetchAndParse(url):
273 """Fetches a URL and returns a 2-Tuple of ([revisions], next-marker). If
274 next-marker is not None, then the listing is a partial listing and another
275 fetch should be performed with next-marker being the marker= GET
276 parameter."""
277 handle = urllib.urlopen(url)
278 document = ElementTree.parse(handle)
279
280 # All nodes in the tree are namespaced. Get the root's tag name to extract
281 # the namespace. Etree does namespaces as |{namespace}tag|.
282 root_tag = document.getroot().tag
283 end_ns_pos = root_tag.find('}')
284 if end_ns_pos == -1:
[email protected]4df583c2014-07-31 17:11:55285 raise Exception('Could not locate end namespace for directory index')
[email protected]afe30662011-07-30 01:05:52286 namespace = root_tag[:end_ns_pos + 1]
287
288 # Find the prefix (_listing_platform_dir) and whether or not the list is
289 # truncated.
290 prefix_len = len(document.find(namespace + 'Prefix').text)
291 next_marker = None
292 is_truncated = document.find(namespace + 'IsTruncated')
293 if is_truncated is not None and is_truncated.text.lower() == 'true':
294 next_marker = document.find(namespace + 'NextMarker').text
[email protected]afe30662011-07-30 01:05:52295 # Get a list of all the revisions.
[email protected]afe30662011-07-30 01:05:52296 revisions = []
[email protected]3e7c85322014-06-27 20:27:36297 githash_svn_dict = {}
[email protected]011886692014-08-01 21:00:21298 if self.is_asan:
299 asan_regex = re.compile(r'.*%s-(\d+)\.zip$' % (self.GetASANBaseName()))
300 # Non ASAN builds are in a <revision> directory. The ASAN builds are
301 # flat
302 all_prefixes = document.findall(namespace + 'Contents/' +
303 namespace + 'Key')
304 for prefix in all_prefixes:
305 m = asan_regex.match(prefix.text)
306 if m:
307 try:
308 revisions.append(int(m.group(1)))
309 except ValueError:
310 pass
311 else:
312 all_prefixes = document.findall(namespace + 'CommonPrefixes/' +
313 namespace + 'Prefix')
314 # The <Prefix> nodes have content of the form of
315 # |_listing_platform_dir/revision/|. Strip off the platform dir and the
316 # trailing slash to just have a number.
317 for prefix in all_prefixes:
318 revnum = prefix.text[prefix_len:-1]
319 try:
320 if not revnum.isdigit():
rob724c9062015-01-22 00:26:42321 # During the svn-git migration, some items were stored by hash.
322 # These items may appear anywhere in the list of items.
323 # If |last_known_rev| is set, assume that the full list has been
324 # retrieved before (including the hashes), so we can safely skip
325 # all git hashes and focus on the numeric revision numbers.
326 if last_known_rev:
327 revnum = None
328 else:
329 git_hash = revnum
330 revnum = self.GetSVNRevisionFromGitHash(git_hash)
331 githash_svn_dict[revnum] = git_hash
[email protected]011886692014-08-01 21:00:21332 if revnum is not None:
333 revnum = int(revnum)
334 revisions.append(revnum)
335 except ValueError:
336 pass
[email protected]3e7c85322014-06-27 20:27:36337 return (revisions, next_marker, githash_svn_dict)
[email protected]9639b002013-08-30 14:45:52338
[email protected]afe30662011-07-30 01:05:52339 # Fetch the first list of revisions.
rob724c9062015-01-22 00:26:42340 if last_known_rev:
341 revisions = []
342 # Optimization: Start paging at the last known revision (local cache).
343 next_marker = _GetMarkerForRev(last_known_rev)
344 # Optimization: Stop paging at the last known revision (remote).
345 last_change_rev = GetChromiumRevision(self, self.GetLastChangeURL())
346 if last_known_rev == last_change_rev:
347 return []
348 else:
349 (revisions, next_marker, new_dict) = _FetchAndParse(self.GetListingURL())
350 self.githash_svn_dict.update(new_dict)
351 last_change_rev = None
352
[email protected]afe30662011-07-30 01:05:52353 # If the result list was truncated, refetch with the next marker. Do this
354 # until an entire directory listing is done.
355 while next_marker:
rob724c9062015-01-22 00:26:42356 sys.stdout.write('\rFetching revisions at marker %s' % next_marker)
357 sys.stdout.flush()
358
[email protected]afe30662011-07-30 01:05:52359 next_url = self.GetListingURL(next_marker)
[email protected]3e7c85322014-06-27 20:27:36360 (new_revisions, next_marker, new_dict) = _FetchAndParse(next_url)
[email protected]afe30662011-07-30 01:05:52361 revisions.extend(new_revisions)
[email protected]3e7c85322014-06-27 20:27:36362 self.githash_svn_dict.update(new_dict)
rob724c9062015-01-22 00:26:42363 if last_change_rev and last_change_rev in new_revisions:
364 break
365 sys.stdout.write('\r')
366 sys.stdout.flush()
[email protected]afe30662011-07-30 01:05:52367 return revisions
368
[email protected]6a7a5d62014-07-09 04:45:50369 def _GetSVNRevisionFromGitHashWithoutGitCheckout(self, git_sha1, depot):
[email protected]3e7c85322014-06-27 20:27:36370 json_url = GITHASH_TO_SVN_URL[depot] % git_sha1
[email protected]2e0f2672014-08-13 20:32:58371 response = urllib.urlopen(json_url)
372 if response.getcode() == 200:
373 try:
374 data = json.loads(response.read()[4:])
375 except ValueError:
376 print 'ValueError for JSON URL: %s' % json_url
377 raise ValueError
378 else:
379 raise ValueError
[email protected]3e7c85322014-06-27 20:27:36380 if 'message' in data:
381 message = data['message'].split('\n')
382 message = [line for line in message if line.strip()]
383 search_pattern = re.compile(SEARCH_PATTERN[depot])
384 result = search_pattern.search(message[len(message)-1])
385 if result:
386 return result.group(1)
pshenoyb23a1452014-09-05 22:52:05387 else:
388 if depot == 'chromium':
389 result = re.search(CHROMIUM_SEARCH_PATTERN_OLD,
390 message[len(message)-1])
391 if result:
392 return result.group(1)
[email protected]3e7c85322014-06-27 20:27:36393 print 'Failed to get svn revision number for %s' % git_sha1
[email protected]1f99f4d2014-07-23 16:44:14394 raise ValueError
[email protected]3e7c85322014-06-27 20:27:36395
[email protected]6a7a5d62014-07-09 04:45:50396 def _GetSVNRevisionFromGitHashFromGitCheckout(self, git_sha1, depot):
397 def _RunGit(command, path):
398 command = ['git'] + command
[email protected]6a7a5d62014-07-09 04:45:50399 shell = sys.platform.startswith('win')
400 proc = subprocess.Popen(command, shell=shell, stdout=subprocess.PIPE,
rob724c9062015-01-22 00:26:42401 stderr=subprocess.PIPE, cwd=path)
[email protected]6a7a5d62014-07-09 04:45:50402 (output, _) = proc.communicate()
[email protected]6a7a5d62014-07-09 04:45:50403 return (output, proc.returncode)
404
rob724c9062015-01-22 00:26:42405 path = self.local_src_path
[email protected]6a7a5d62014-07-09 04:45:50406 if depot == 'blink':
rob724c9062015-01-22 00:26:42407 path = os.path.join(self.local_src_path, 'third_party', 'WebKit')
408 revision = None
409 try:
[email protected]6a7a5d62014-07-09 04:45:50410 command = ['svn', 'find-rev', git_sha1]
411 (git_output, return_code) = _RunGit(command, path)
412 if not return_code:
rob724c9062015-01-22 00:26:42413 revision = git_output.strip('\n')
414 except ValueError:
415 pass
416 if not revision:
417 command = ['log', '-n1', '--format=%s', git_sha1]
418 (git_output, return_code) = _RunGit(command, path)
419 if not return_code:
420 revision = re.match('SVN changes up to revision ([0-9]+)', git_output)
421 revision = revision.group(1) if revision else None
422 if revision:
423 return revision
424 raise ValueError
[email protected]6a7a5d62014-07-09 04:45:50425
426 def GetSVNRevisionFromGitHash(self, git_sha1, depot='chromium'):
rob724c9062015-01-22 00:26:42427 if not self.local_src_path:
[email protected]6a7a5d62014-07-09 04:45:50428 return self._GetSVNRevisionFromGitHashWithoutGitCheckout(git_sha1, depot)
429 else:
430 return self._GetSVNRevisionFromGitHashFromGitCheckout(git_sha1, depot)
431
[email protected]afe30662011-07-30 01:05:52432 def GetRevList(self):
433 """Gets the list of revision numbers between self.good_revision and
434 self.bad_revision."""
rob724c9062015-01-22 00:26:42435
436 cache = {}
437 # The cache is stored in the same directory as bisect-builds.py
438 cache_filename = os.path.join(
439 os.path.abspath(os.path.dirname(__file__)),
440 '.bisect-builds-cache.json')
441 cache_dict_key = self.GetListingURL()
442
443 def _LoadBucketFromCache():
444 if self.use_local_cache:
445 try:
446 with open(cache_filename) as cache_file:
447 cache = json.load(cache_file)
448 revisions = cache.get(cache_dict_key, [])
449 githash_svn_dict = cache.get('githash_svn_dict', {})
450 if revisions:
451 print 'Loaded revisions %d-%d from %s' % (revisions[0],
452 revisions[-1], cache_filename)
453 return (revisions, githash_svn_dict)
454 except (EnvironmentError, ValueError):
455 pass
456 return ([], {})
457
458 def _SaveBucketToCache():
459 """Save the list of revisions and the git-svn mappings to a file.
460 The list of revisions is assumed to be sorted."""
461 if self.use_local_cache:
462 cache[cache_dict_key] = revlist_all
463 cache['githash_svn_dict'] = self.githash_svn_dict
464 try:
465 with open(cache_filename, 'w') as cache_file:
466 json.dump(cache, cache_file)
467 print 'Saved revisions %d-%d to %s' % (
468 revlist_all[0], revlist_all[-1], cache_filename)
469 except EnvironmentError:
470 pass
471
[email protected]afe30662011-07-30 01:05:52472 # Download the revlist and filter for just the range between good and bad.
[email protected]eadd95d2012-11-02 22:42:09473 minrev = min(self.good_revision, self.bad_revision)
474 maxrev = max(self.good_revision, self.bad_revision)
rob724c9062015-01-22 00:26:42475
476 (revlist_all, self.githash_svn_dict) = _LoadBucketFromCache()
477 last_known_rev = revlist_all[-1] if revlist_all else 0
478 if last_known_rev < maxrev:
479 revlist_all.extend(map(int, self.ParseDirectoryIndex(last_known_rev)))
480 revlist_all = list(set(revlist_all))
481 revlist_all.sort()
482 _SaveBucketToCache()
[email protected]37ed3172013-09-24 23:49:30483
484 revlist = [x for x in revlist_all if x >= int(minrev) and x <= int(maxrev)]
[email protected]37ed3172013-09-24 23:49:30485
486 # Set good and bad revisions to be legit revisions.
487 if revlist:
488 if self.good_revision < self.bad_revision:
489 self.good_revision = revlist[0]
490 self.bad_revision = revlist[-1]
491 else:
492 self.bad_revision = revlist[0]
493 self.good_revision = revlist[-1]
494
495 # Fix chromium rev so that the deps blink revision matches REVISIONS file.
496 if self.base_url == WEBKIT_BASE_URL:
497 revlist_all.sort()
498 self.good_revision = FixChromiumRevForBlink(revlist,
499 revlist_all,
500 self,
501 self.good_revision)
502 self.bad_revision = FixChromiumRevForBlink(revlist,
503 revlist_all,
504 self,
505 self.bad_revision)
[email protected]afe30662011-07-30 01:05:52506 return revlist
507
[email protected]d0149c5c2012-05-29 21:12:11508 def GetOfficialBuildsList(self):
509 """Gets the list of official build numbers between self.good_revision and
510 self.bad_revision."""
[email protected]83048502014-08-21 16:48:44511
John Budorick06e5df12015-02-27 17:44:27512 def CheckDepotToolsInPath():
513 delimiter = ';' if sys.platform.startswith('win') else ':'
514 path_list = os.environ['PATH'].split(delimiter)
515 for path in path_list:
516 if path.rstrip(os.path.sep).endswith('depot_tools'):
517 return path
518 return None
519
520 def RunGsutilCommand(args):
521 gsutil_path = CheckDepotToolsInPath()
522 if gsutil_path is None:
523 print ('Follow the instructions in this document '
524 'http://dev.chromium.org/developers/how-tos/install-depot-tools'
525 ' to install depot_tools and then try again.')
526 sys.exit(1)
527 gsutil_path = os.path.join(gsutil_path, 'third_party', 'gsutil', 'gsutil')
528 gsutil = subprocess.Popen([sys.executable, gsutil_path] + args,
529 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
530 env=None)
531 stdout, stderr = gsutil.communicate()
532 if gsutil.returncode:
533 if (re.findall(r'status[ |=]40[1|3]', stderr) or
534 stderr.startswith(CREDENTIAL_ERROR_MESSAGE)):
535 print ('Follow these steps to configure your credentials and try'
536 ' running the bisect-builds.py again.:\n'
537 ' 1. Run "python %s config" and follow its instructions.\n'
538 ' 2. If you have a @google.com account, use that account.\n'
539 ' 3. For the project-id, just enter 0.' % gsutil_path)
540 sys.exit(1)
541 else:
542 raise Exception('Error running the gsutil command: %s' % stderr)
543 return stdout
544
545 def GsutilList(bucket):
546 query = 'gs://%s/' % bucket
547 stdout = RunGsutilCommand(['ls', query])
548 return [url[len(query):].strip('/') for url in stdout.splitlines()]
549
[email protected]d0149c5c2012-05-29 21:12:11550 # Download the revlist and filter for just the range between good and bad.
[email protected]eadd95d2012-11-02 22:42:09551 minrev = min(self.good_revision, self.bad_revision)
552 maxrev = max(self.good_revision, self.bad_revision)
John Budorick06e5df12015-02-27 17:44:27553 build_numbers = GsutilList(GS_BUCKET_NAME)
[email protected]83048502014-08-21 16:48:44554 revision_re = re.compile(r'(\d\d\.\d\.\d{4}\.\d+)')
555 build_numbers = filter(lambda b: revision_re.search(b), build_numbers)
[email protected]d0149c5c2012-05-29 21:12:11556 final_list = []
[email protected]d0149c5c2012-05-29 21:12:11557 parsed_build_numbers = [LooseVersion(x) for x in build_numbers]
[email protected]83048502014-08-21 16:48:44558 connection = httplib.HTTPConnection(GOOGLE_APIS_URL)
[email protected]d0149c5c2012-05-29 21:12:11559 for build_number in sorted(parsed_build_numbers):
[email protected]83048502014-08-21 16:48:44560 if build_number > maxrev:
561 break
562 if build_number < minrev:
563 continue
John Budorick06e5df12015-02-27 17:44:27564 path = ('/' + GS_BUCKET_NAME + '/' + str(build_number) + '/' +
[email protected]4df583c2014-07-31 17:11:55565 self._listing_platform_dir + self.archive_name)
[email protected]83048502014-08-21 16:48:44566 connection.request('HEAD', path)
567 response = connection.getresponse()
568 if response.status == 200:
569 final_list.append(str(build_number))
570 response.read()
571 connection.close()
[email protected]801fb652012-07-20 20:13:50572 return final_list
[email protected]bd8dcb92010-03-31 01:05:24573
[email protected]fc3702e2013-11-09 04:23:00574def UnzipFilenameToDir(filename, directory):
575 """Unzip |filename| to |directory|."""
[email protected]afe30662011-07-30 01:05:52576 cwd = os.getcwd()
577 if not os.path.isabs(filename):
578 filename = os.path.join(cwd, filename)
[email protected]bd8dcb92010-03-31 01:05:24579 zf = zipfile.ZipFile(filename)
580 # Make base.
[email protected]fc3702e2013-11-09 04:23:00581 if not os.path.isdir(directory):
582 os.mkdir(directory)
583 os.chdir(directory)
[email protected]e29c08c2012-09-17 20:50:50584 # Extract files.
585 for info in zf.infolist():
586 name = info.filename
587 if name.endswith('/'): # dir
588 if not os.path.isdir(name):
589 os.makedirs(name)
590 else: # file
[email protected]fc3702e2013-11-09 04:23:00591 directory = os.path.dirname(name)
John Budorick06e5df12015-02-27 17:44:27592 if not os.path.isdir(directory):
[email protected]fc3702e2013-11-09 04:23:00593 os.makedirs(directory)
[email protected]e29c08c2012-09-17 20:50:50594 out = open(name, 'wb')
595 out.write(zf.read(name))
596 out.close()
597 # Set permissions. Permission info in external_attr is shifted 16 bits.
598 os.chmod(name, info.external_attr >> 16L)
599 os.chdir(cwd)
[email protected]bd8dcb92010-03-31 01:05:24600
[email protected]67e0bc62009-09-03 22:06:09601
[email protected]468a9772011-08-09 18:42:00602def FetchRevision(context, rev, filename, quit_event=None, progress_event=None):
[email protected]afe30662011-07-30 01:05:52603 """Downloads and unzips revision |rev|.
604 @param context A PathContext instance.
605 @param rev The Chromium revision number/tag to download.
606 @param filename The destination for the downloaded file.
607 @param quit_event A threading.Event which will be set by the master thread to
608 indicate that the download should be aborted.
[email protected]468a9772011-08-09 18:42:00609 @param progress_event A threading.Event which will be set by the master thread
610 to indicate that the progress of the download should be
611 displayed.
[email protected]afe30662011-07-30 01:05:52612 """
613 def ReportHook(blocknum, blocksize, totalsize):
[email protected]946be752011-10-25 23:34:21614 if quit_event and quit_event.isSet():
[email protected]4df583c2014-07-31 17:11:55615 raise RuntimeError('Aborting download of revision %s' % str(rev))
[email protected]946be752011-10-25 23:34:21616 if progress_event and progress_event.isSet():
[email protected]468a9772011-08-09 18:42:00617 size = blocknum * blocksize
618 if totalsize == -1: # Total size not known.
[email protected]4df583c2014-07-31 17:11:55619 progress = 'Received %d bytes' % size
[email protected]468a9772011-08-09 18:42:00620 else:
621 size = min(totalsize, size)
[email protected]4df583c2014-07-31 17:11:55622 progress = 'Received %d of %d bytes, %.2f%%' % (
[email protected]468a9772011-08-09 18:42:00623 size, totalsize, 100.0 * size / totalsize)
624 # Send a \r to let all progress messages use just one line of output.
[email protected]4df583c2014-07-31 17:11:55625 sys.stdout.write('\r' + progress)
[email protected]468a9772011-08-09 18:42:00626 sys.stdout.flush()
[email protected]afe30662011-07-30 01:05:52627 download_url = context.GetDownloadURL(rev)
628 try:
John Budorick06e5df12015-02-27 17:44:27629 urllib.urlretrieve(download_url, filename, ReportHook)
[email protected]946be752011-10-25 23:34:21630 if progress_event and progress_event.isSet():
[email protected]ecaba01e62011-10-26 05:33:28631 print
mikecasee2b6ce82015-02-06 18:22:39632
[email protected]4df583c2014-07-31 17:11:55633 except RuntimeError:
[email protected]afe30662011-07-30 01:05:52634 pass
[email protected]7ad66a72009-09-04 17:52:33635
[email protected]7ad66a72009-09-04 17:52:33636
[email protected]4df583c2014-07-31 17:11:55637def RunRevision(context, revision, zip_file, profile, num_runs, command, args):
[email protected]afe30662011-07-30 01:05:52638 """Given a zipped revision, unzip it and run the test."""
[email protected]4df583c2014-07-31 17:11:55639 print 'Trying revision %s...' % str(revision)
[email protected]3ff00b72011-07-20 21:34:47640
[email protected]afe30662011-07-30 01:05:52641 # Create a temp directory and unzip the revision into it.
[email protected]7ad66a72009-09-04 17:52:33642 cwd = os.getcwd()
643 tempdir = tempfile.mkdtemp(prefix='bisect_tmp')
[email protected]4df583c2014-07-31 17:11:55644 UnzipFilenameToDir(zip_file, tempdir)
dmazzoni76e907d2015-01-22 08:14:49645
646 # Hack: Chrome OS archives are missing icudtl.dat; try to copy it from
647 # the local directory.
648 if context.platform == 'chromeos':
649 icudtl_path = 'third_party/icu/source/data/in/icudtl.dat'
650 if not os.access(icudtl_path, os.F_OK):
651 print 'Couldn\'t find: ' + icudtl_path
652 sys.exit()
653 os.system('cp %s %s/chrome-linux/' % (icudtl_path, tempdir))
654
[email protected]7ad66a72009-09-04 17:52:33655 os.chdir(tempdir)
[email protected]67e0bc62009-09-03 22:06:09656
[email protected]5e93cf162012-01-28 02:16:56657 # Run the build as many times as specified.
[email protected]4646a752013-07-19 22:14:34658 testargs = ['--user-data-dir=%s' % profile] + args
[email protected]d0149c5c2012-05-29 21:12:11659 # The sandbox must be run as root on Official Chrome, so bypass it.
[email protected]cdb77062014-07-21 18:07:15660 if ((context.is_official or context.flash_path or context.pdf_path) and
[email protected]fc3702e2013-11-09 04:23:00661 context.platform.startswith('linux')):
[email protected]d0149c5c2012-05-29 21:12:11662 testargs.append('--no-sandbox')
[email protected]fc3702e2013-11-09 04:23:00663 if context.flash_path:
664 testargs.append('--ppapi-flash-path=%s' % context.flash_path)
665 # We have to pass a large enough Flash version, which currently needs not
666 # be correct. Instead of requiring the user of the script to figure out and
667 # pass the correct version we just spoof it.
668 testargs.append('--ppapi-flash-version=99.9.999.999')
[email protected]d0149c5c2012-05-29 21:12:11669
[email protected]cdb77062014-07-21 18:07:15670 # TODO(vitalybuka): Remove in the future. See crbug.com/395687.
671 if context.pdf_path:
[email protected]011886692014-08-01 21:00:21672 shutil.copy(context.pdf_path,
673 os.path.dirname(context.GetLaunchPath(revision)))
[email protected]cdb77062014-07-21 18:07:15674 testargs.append('--enable-print-preview')
675
[email protected]4646a752013-07-19 22:14:34676 runcommand = []
[email protected]61ea90a2013-09-26 10:17:34677 for token in shlex.split(command):
[email protected]4df583c2014-07-31 17:11:55678 if token == '%a':
[email protected]4646a752013-07-19 22:14:34679 runcommand.extend(testargs)
680 else:
[email protected]4df583c2014-07-31 17:11:55681 runcommand.append(
[email protected]011886692014-08-01 21:00:21682 token.replace('%p', os.path.abspath(context.GetLaunchPath(revision))).
683 replace('%s', ' '.join(testargs)))
[email protected]4646a752013-07-19 22:14:34684
[email protected]d59c8712014-02-11 21:04:57685 results = []
[email protected]4df583c2014-07-31 17:11:55686 for _ in range(num_runs):
[email protected]4646a752013-07-19 22:14:34687 subproc = subprocess.Popen(runcommand,
[email protected]5e93cf162012-01-28 02:16:56688 bufsize=-1,
689 stdout=subprocess.PIPE,
690 stderr=subprocess.PIPE)
691 (stdout, stderr) = subproc.communicate()
[email protected]d59c8712014-02-11 21:04:57692 results.append((subproc.returncode, stdout, stderr))
[email protected]7ad66a72009-09-04 17:52:33693 os.chdir(cwd)
[email protected]7ad66a72009-09-04 17:52:33694 try:
695 shutil.rmtree(tempdir, True)
[email protected]4df583c2014-07-31 17:11:55696 except Exception:
[email protected]7ad66a72009-09-04 17:52:33697 pass
[email protected]67e0bc62009-09-03 22:06:09698
[email protected]d59c8712014-02-11 21:04:57699 for (returncode, stdout, stderr) in results:
700 if returncode:
701 return (returncode, stdout, stderr)
702 return results[0]
[email protected]79f14742010-03-10 01:01:57703
[email protected]cb155a82011-11-29 17:25:34704
[email protected]4df583c2014-07-31 17:11:55705# The arguments official_builds, status, stdout and stderr are unused.
706# They are present here because this function is passed to Bisect which then
707# calls it with 5 arguments.
708# pylint: disable=W0613
[email protected]d0149c5c2012-05-29 21:12:11709def AskIsGoodBuild(rev, official_builds, status, stdout, stderr):
[email protected]4df583c2014-07-31 17:11:55710 """Asks the user whether build |rev| is good or bad."""
[email protected]79f14742010-03-10 01:01:57711 # Loop until we get a response that we can parse.
[email protected]67e0bc62009-09-03 22:06:09712 while True:
[email protected]4df583c2014-07-31 17:11:55713 response = raw_input('Revision %s is '
[email protected]1d4a06242013-08-20 22:53:12714 '[(g)ood/(b)ad/(r)etry/(u)nknown/(q)uit]: ' %
[email protected]53bb6342012-06-01 04:11:00715 str(rev))
[email protected]1d4a06242013-08-20 22:53:12716 if response and response in ('g', 'b', 'r', 'u'):
[email protected]53bb6342012-06-01 04:11:00717 return response
[email protected]afe30662011-07-30 01:05:52718 if response and response == 'q':
719 raise SystemExit()
[email protected]67e0bc62009-09-03 22:06:09720
[email protected]cb155a82011-11-29 17:25:34721
[email protected]011886692014-08-01 21:00:21722def IsGoodASANBuild(rev, official_builds, status, stdout, stderr):
723 """Determine if an ASAN build |rev| is good or bad
724
725 Will examine stderr looking for the error message emitted by ASAN. If not
726 found then will fallback to asking the user."""
727 if stderr:
728 bad_count = 0
729 for line in stderr.splitlines():
730 print line
731 if line.find('ERROR: AddressSanitizer:') != -1:
732 bad_count += 1
733 if bad_count > 0:
734 print 'Revision %d determined to be bad.' % rev
735 return 'b'
736 return AskIsGoodBuild(rev, official_builds, status, stdout, stderr)
737
[email protected]53bb6342012-06-01 04:11:00738class DownloadJob(object):
739 """DownloadJob represents a task to download a given Chromium revision."""
[email protected]4df583c2014-07-31 17:11:55740
741 def __init__(self, context, name, rev, zip_file):
[email protected]53bb6342012-06-01 04:11:00742 super(DownloadJob, self).__init__()
743 # Store off the input parameters.
744 self.context = context
745 self.name = name
746 self.rev = rev
[email protected]4df583c2014-07-31 17:11:55747 self.zip_file = zip_file
[email protected]53bb6342012-06-01 04:11:00748 self.quit_event = threading.Event()
749 self.progress_event = threading.Event()
[email protected]4df583c2014-07-31 17:11:55750 self.thread = None
[email protected]53bb6342012-06-01 04:11:00751
752 def Start(self):
753 """Starts the download."""
754 fetchargs = (self.context,
755 self.rev,
[email protected]4df583c2014-07-31 17:11:55756 self.zip_file,
[email protected]53bb6342012-06-01 04:11:00757 self.quit_event,
758 self.progress_event)
759 self.thread = threading.Thread(target=FetchRevision,
760 name=self.name,
761 args=fetchargs)
762 self.thread.start()
763
764 def Stop(self):
765 """Stops the download which must have been started previously."""
[email protected]4df583c2014-07-31 17:11:55766 assert self.thread, 'DownloadJob must be started before Stop is called.'
[email protected]53bb6342012-06-01 04:11:00767 self.quit_event.set()
768 self.thread.join()
[email protected]4df583c2014-07-31 17:11:55769 os.unlink(self.zip_file)
[email protected]53bb6342012-06-01 04:11:00770
771 def WaitFor(self):
772 """Prints a message and waits for the download to complete. The download
773 must have been started previously."""
[email protected]4df583c2014-07-31 17:11:55774 assert self.thread, 'DownloadJob must be started before WaitFor is called.'
775 print 'Downloading revision %s...' % str(self.rev)
[email protected]53bb6342012-06-01 04:11:00776 self.progress_event.set() # Display progress of download.
777 self.thread.join()
778
779
[email protected]2e0f2672014-08-13 20:32:58780def Bisect(context,
[email protected]5e93cf162012-01-28 02:16:56781 num_runs=1,
[email protected]4df583c2014-07-31 17:11:55782 command='%p %a',
[email protected]60ac66e32011-07-18 16:08:25783 try_args=(),
[email protected]afe30662011-07-30 01:05:52784 profile=None,
[email protected]d59c8712014-02-11 21:04:57785 interactive=True,
[email protected]53bb6342012-06-01 04:11:00786 evaluate=AskIsGoodBuild):
[email protected]afe30662011-07-30 01:05:52787 """Given known good and known bad revisions, run a binary search on all
788 archived revisions to determine the last known good revision.
[email protected]60ac66e32011-07-18 16:08:25789
[email protected]2e0f2672014-08-13 20:32:58790 @param context PathContext object initialized with user provided parameters.
[email protected]5e93cf162012-01-28 02:16:56791 @param num_runs Number of times to run each build for asking good/bad.
[email protected]afe30662011-07-30 01:05:52792 @param try_args A tuple of arguments to pass to the test application.
793 @param profile The name of the user profile to run with.
[email protected]d59c8712014-02-11 21:04:57794 @param interactive If it is false, use command exit code for good or bad
795 judgment of the argument build.
[email protected]53bb6342012-06-01 04:11:00796 @param evaluate A function which returns 'g' if the argument build is good,
797 'b' if it's bad or 'u' if unknown.
[email protected]afe30662011-07-30 01:05:52798
799 Threading is used to fetch Chromium revisions in the background, speeding up
800 the user's experience. For example, suppose the bounds of the search are
801 good_rev=0, bad_rev=100. The first revision to be checked is 50. Depending on
802 whether revision 50 is good or bad, the next revision to check will be either
803 25 or 75. So, while revision 50 is being checked, the script will download
804 revisions 25 and 75 in the background. Once the good/bad verdict on rev 50 is
805 known:
806
807 - If rev 50 is good, the download of rev 25 is cancelled, and the next test
808 is run on rev 75.
809
810 - If rev 50 is bad, the download of rev 75 is cancelled, and the next test
811 is run on rev 25.
[email protected]60ac66e32011-07-18 16:08:25812 """
813
[email protected]afe30662011-07-30 01:05:52814 if not profile:
815 profile = 'profile'
816
[email protected]2e0f2672014-08-13 20:32:58817 good_rev = context.good_revision
818 bad_rev = context.bad_revision
[email protected]afe30662011-07-30 01:05:52819 cwd = os.getcwd()
820
[email protected]28a3c122014-08-09 11:04:51821 print 'Downloading list of known revisions...',
rob724c9062015-01-22 00:26:42822 if not context.use_local_cache and not context.is_official:
823 print '(use --use-local-cache to cache and re-use the list of revisions)'
[email protected]28a3c122014-08-09 11:04:51824 else:
825 print
[email protected]d0149c5c2012-05-29 21:12:11826 _GetDownloadPath = lambda rev: os.path.join(cwd,
827 '%s-%s' % (str(rev), context.archive_name))
[email protected]2e0f2672014-08-13 20:32:58828 if context.is_official:
[email protected]d0149c5c2012-05-29 21:12:11829 revlist = context.GetOfficialBuildsList()
830 else:
831 revlist = context.GetRevList()
[email protected]afe30662011-07-30 01:05:52832
833 # Get a list of revisions to bisect across.
834 if len(revlist) < 2: # Don't have enough builds to bisect.
835 msg = 'We don\'t have enough builds to bisect. revlist: %s' % revlist
836 raise RuntimeError(msg)
837
838 # Figure out our bookends and first pivot point; fetch the pivot revision.
[email protected]eadd95d2012-11-02 22:42:09839 minrev = 0
840 maxrev = len(revlist) - 1
841 pivot = maxrev / 2
[email protected]afe30662011-07-30 01:05:52842 rev = revlist[pivot]
[email protected]4df583c2014-07-31 17:11:55843 zip_file = _GetDownloadPath(rev)
844 fetch = DownloadJob(context, 'initial_fetch', rev, zip_file)
[email protected]eadd95d2012-11-02 22:42:09845 fetch.Start()
846 fetch.WaitFor()
[email protected]60ac66e32011-07-18 16:08:25847
848 # Binary search time!
[email protected]4df583c2014-07-31 17:11:55849 while fetch and fetch.zip_file and maxrev - minrev > 1:
[email protected]eadd95d2012-11-02 22:42:09850 if bad_rev < good_rev:
[email protected]4df583c2014-07-31 17:11:55851 min_str, max_str = 'bad', 'good'
[email protected]eadd95d2012-11-02 22:42:09852 else:
[email protected]4df583c2014-07-31 17:11:55853 min_str, max_str = 'good', 'bad'
854 print 'Bisecting range [%s (%s), %s (%s)].' % (revlist[minrev], min_str,
[email protected]eadd95d2012-11-02 22:42:09855 revlist[maxrev], max_str)
856
[email protected]afe30662011-07-30 01:05:52857 # Pre-fetch next two possible pivots
858 # - down_pivot is the next revision to check if the current revision turns
859 # out to be bad.
860 # - up_pivot is the next revision to check if the current revision turns
861 # out to be good.
[email protected]eadd95d2012-11-02 22:42:09862 down_pivot = int((pivot - minrev) / 2) + minrev
[email protected]53bb6342012-06-01 04:11:00863 down_fetch = None
[email protected]eadd95d2012-11-02 22:42:09864 if down_pivot != pivot and down_pivot != minrev:
[email protected]afe30662011-07-30 01:05:52865 down_rev = revlist[down_pivot]
[email protected]53bb6342012-06-01 04:11:00866 down_fetch = DownloadJob(context, 'down_fetch', down_rev,
867 _GetDownloadPath(down_rev))
868 down_fetch.Start()
[email protected]60ac66e32011-07-18 16:08:25869
[email protected]eadd95d2012-11-02 22:42:09870 up_pivot = int((maxrev - pivot) / 2) + pivot
[email protected]53bb6342012-06-01 04:11:00871 up_fetch = None
[email protected]eadd95d2012-11-02 22:42:09872 if up_pivot != pivot and up_pivot != maxrev:
[email protected]afe30662011-07-30 01:05:52873 up_rev = revlist[up_pivot]
[email protected]53bb6342012-06-01 04:11:00874 up_fetch = DownloadJob(context, 'up_fetch', up_rev,
875 _GetDownloadPath(up_rev))
876 up_fetch.Start()
[email protected]60ac66e32011-07-18 16:08:25877
[email protected]afe30662011-07-30 01:05:52878 # Run test on the pivot revision.
[email protected]e29c08c2012-09-17 20:50:50879 status = None
880 stdout = None
881 stderr = None
882 try:
883 (status, stdout, stderr) = RunRevision(context,
884 rev,
[email protected]4df583c2014-07-31 17:11:55885 fetch.zip_file,
[email protected]e29c08c2012-09-17 20:50:50886 profile,
887 num_runs,
[email protected]4646a752013-07-19 22:14:34888 command,
[email protected]e29c08c2012-09-17 20:50:50889 try_args)
890 except Exception, e:
[email protected]fc3702e2013-11-09 04:23:00891 print >> sys.stderr, e
[email protected]60ac66e32011-07-18 16:08:25892
[email protected]53bb6342012-06-01 04:11:00893 # Call the evaluate function to see if the current revision is good or bad.
[email protected]afe30662011-07-30 01:05:52894 # On that basis, kill one of the background downloads and complete the
895 # other, as described in the comments above.
896 try:
[email protected]d59c8712014-02-11 21:04:57897 if not interactive:
898 if status:
899 answer = 'b'
900 print 'Bad revision: %s' % rev
901 else:
902 answer = 'g'
903 print 'Good revision: %s' % rev
904 else:
[email protected]2e0f2672014-08-13 20:32:58905 answer = evaluate(rev, context.is_official, status, stdout, stderr)
[email protected]4df583c2014-07-31 17:11:55906 if ((answer == 'g' and good_rev < bad_rev)
907 or (answer == 'b' and bad_rev < good_rev)):
[email protected]1d4a06242013-08-20 22:53:12908 fetch.Stop()
[email protected]eadd95d2012-11-02 22:42:09909 minrev = pivot
[email protected]53bb6342012-06-01 04:11:00910 if down_fetch:
911 down_fetch.Stop() # Kill the download of the older revision.
[email protected]1d4a06242013-08-20 22:53:12912 fetch = None
[email protected]53bb6342012-06-01 04:11:00913 if up_fetch:
914 up_fetch.WaitFor()
[email protected]afe30662011-07-30 01:05:52915 pivot = up_pivot
[email protected]eadd95d2012-11-02 22:42:09916 fetch = up_fetch
[email protected]4df583c2014-07-31 17:11:55917 elif ((answer == 'b' and good_rev < bad_rev)
918 or (answer == 'g' and bad_rev < good_rev)):
[email protected]1d4a06242013-08-20 22:53:12919 fetch.Stop()
[email protected]eadd95d2012-11-02 22:42:09920 maxrev = pivot
[email protected]53bb6342012-06-01 04:11:00921 if up_fetch:
922 up_fetch.Stop() # Kill the download of the newer revision.
[email protected]1d4a06242013-08-20 22:53:12923 fetch = None
[email protected]53bb6342012-06-01 04:11:00924 if down_fetch:
925 down_fetch.WaitFor()
[email protected]afe30662011-07-30 01:05:52926 pivot = down_pivot
[email protected]eadd95d2012-11-02 22:42:09927 fetch = down_fetch
[email protected]1d4a06242013-08-20 22:53:12928 elif answer == 'r':
929 pass # Retry requires no changes.
[email protected]53bb6342012-06-01 04:11:00930 elif answer == 'u':
931 # Nuke the revision from the revlist and choose a new pivot.
[email protected]1d4a06242013-08-20 22:53:12932 fetch.Stop()
[email protected]53bb6342012-06-01 04:11:00933 revlist.pop(pivot)
[email protected]eadd95d2012-11-02 22:42:09934 maxrev -= 1 # Assumes maxrev >= pivot.
[email protected]53bb6342012-06-01 04:11:00935
[email protected]eadd95d2012-11-02 22:42:09936 if maxrev - minrev > 1:
[email protected]53bb6342012-06-01 04:11:00937 # Alternate between using down_pivot or up_pivot for the new pivot
938 # point, without affecting the range. Do this instead of setting the
939 # pivot to the midpoint of the new range because adjacent revisions
940 # are likely affected by the same issue that caused the (u)nknown
941 # response.
942 if up_fetch and down_fetch:
943 fetch = [up_fetch, down_fetch][len(revlist) % 2]
944 elif up_fetch:
945 fetch = up_fetch
946 else:
947 fetch = down_fetch
948 fetch.WaitFor()
949 if fetch == up_fetch:
950 pivot = up_pivot - 1 # Subtracts 1 because revlist was resized.
951 else:
952 pivot = down_pivot
[email protected]4df583c2014-07-31 17:11:55953 zip_file = fetch.zip_file
[email protected]53bb6342012-06-01 04:11:00954
955 if down_fetch and fetch != down_fetch:
956 down_fetch.Stop()
957 if up_fetch and fetch != up_fetch:
958 up_fetch.Stop()
959 else:
[email protected]4df583c2014-07-31 17:11:55960 assert False, 'Unexpected return value from evaluate(): ' + answer
[email protected]afe30662011-07-30 01:05:52961 except SystemExit:
[email protected]4df583c2014-07-31 17:11:55962 print 'Cleaning up...'
[email protected]5e93cf162012-01-28 02:16:56963 for f in [_GetDownloadPath(revlist[down_pivot]),
964 _GetDownloadPath(revlist[up_pivot])]:
[email protected]afe30662011-07-30 01:05:52965 try:
966 os.unlink(f)
967 except OSError:
968 pass
969 sys.exit(0)
970
971 rev = revlist[pivot]
972
[email protected]2e0f2672014-08-13 20:32:58973 return (revlist[minrev], revlist[maxrev], context)
[email protected]60ac66e32011-07-18 16:08:25974
975
pshenoycd6bd682014-09-10 20:50:22976def GetBlinkDEPSRevisionForChromiumRevision(self, rev):
[email protected]4c6fec6b2013-09-17 17:44:08977 """Returns the blink revision that was in REVISIONS file at
[email protected]b2fe7f22011-10-25 22:58:31978 chromium revision |rev|."""
pshenoycd6bd682014-09-10 20:50:22979
980 def _GetBlinkRev(url, blink_re):
981 m = blink_re.search(url.read())
982 url.close()
983 if m:
984 return m.group(1)
985
986 url = urllib.urlopen(DEPS_FILE_OLD % rev)
987 if url.getcode() == 200:
988 # . doesn't match newlines without re.DOTALL, so this is safe.
989 blink_re = re.compile(r'webkit_revision\D*(\d+)')
990 return int(_GetBlinkRev(url, blink_re))
[email protected]37ed3172013-09-24 23:49:30991 else:
pshenoycd6bd682014-09-10 20:50:22992 url = urllib.urlopen(DEPS_FILE_NEW % GetGitHashFromSVNRevision(rev))
993 if url.getcode() == 200:
994 blink_re = re.compile(r'webkit_revision\D*\d+;\D*\d+;(\w+)')
995 blink_git_sha = _GetBlinkRev(url, blink_re)
996 return self.GetSVNRevisionFromGitHash(blink_git_sha, 'blink')
997 raise Exception('Could not get Blink revision for Chromium rev %d' % rev)
[email protected]37ed3172013-09-24 23:49:30998
999
[email protected]2e0f2672014-08-13 20:32:581000def GetBlinkRevisionForChromiumRevision(context, rev):
[email protected]37ed3172013-09-24 23:49:301001 """Returns the blink revision that was in REVISIONS file at
1002 chromium revision |rev|."""
[email protected]3e7c85322014-06-27 20:27:361003 def _IsRevisionNumber(revision):
1004 if isinstance(revision, int):
1005 return True
1006 else:
1007 return revision.isdigit()
[email protected]2e0f2672014-08-13 20:32:581008 if str(rev) in context.githash_svn_dict:
1009 rev = context.githash_svn_dict[str(rev)]
1010 file_url = '%s/%s%s/REVISIONS' % (context.base_url,
1011 context._listing_platform_dir, rev)
[email protected]4c6fec6b2013-09-17 17:44:081012 url = urllib.urlopen(file_url)
[email protected]2e0f2672014-08-13 20:32:581013 if url.getcode() == 200:
1014 try:
1015 data = json.loads(url.read())
1016 except ValueError:
1017 print 'ValueError for JSON URL: %s' % file_url
1018 raise ValueError
1019 else:
1020 raise ValueError
[email protected]b2fe7f22011-10-25 22:58:311021 url.close()
[email protected]4c6fec6b2013-09-17 17:44:081022 if 'webkit_revision' in data:
[email protected]3e7c85322014-06-27 20:27:361023 blink_rev = data['webkit_revision']
1024 if not _IsRevisionNumber(blink_rev):
[email protected]2e0f2672014-08-13 20:32:581025 blink_rev = int(context.GetSVNRevisionFromGitHash(blink_rev, 'blink'))
[email protected]3e7c85322014-06-27 20:27:361026 return blink_rev
[email protected]b2fe7f22011-10-25 22:58:311027 else:
[email protected]ff50d1c2013-04-17 18:49:361028 raise Exception('Could not get blink revision for cr rev %d' % rev)
[email protected]b2fe7f22011-10-25 22:58:311029
[email protected]4df583c2014-07-31 17:11:551030
[email protected]37ed3172013-09-24 23:49:301031def FixChromiumRevForBlink(revisions_final, revisions, self, rev):
1032 """Returns the chromium revision that has the correct blink revision
1033 for blink bisect, DEPS and REVISIONS file might not match since
1034 blink snapshots point to tip of tree blink.
1035 Note: The revisions_final variable might get modified to include
1036 additional revisions."""
pshenoycd6bd682014-09-10 20:50:221037 blink_deps_rev = GetBlinkDEPSRevisionForChromiumRevision(self, rev)
[email protected]37ed3172013-09-24 23:49:301038
1039 while (GetBlinkRevisionForChromiumRevision(self, rev) > blink_deps_rev):
1040 idx = revisions.index(rev)
1041 if idx > 0:
1042 rev = revisions[idx-1]
1043 if rev not in revisions_final:
1044 revisions_final.insert(0, rev)
1045
1046 revisions_final.sort()
1047 return rev
[email protected]b2fe7f22011-10-25 22:58:311048
[email protected]4df583c2014-07-31 17:11:551049
[email protected]5980b752014-07-02 00:34:401050def GetChromiumRevision(context, url):
[email protected]801fb652012-07-20 20:13:501051 """Returns the chromium revision read from given URL."""
1052 try:
1053 # Location of the latest build revision number
[email protected]5980b752014-07-02 00:34:401054 latest_revision = urllib.urlopen(url).read()
1055 if latest_revision.isdigit():
1056 return int(latest_revision)
1057 return context.GetSVNRevisionFromGitHash(latest_revision)
[email protected]4df583c2014-07-31 17:11:551058 except Exception:
1059 print 'Could not determine latest revision. This could be bad...'
[email protected]801fb652012-07-20 20:13:501060 return 999999999
1061
pshenoycd6bd682014-09-10 20:50:221062def GetGitHashFromSVNRevision(svn_revision):
1063 crrev_url = CRREV_URL + str(svn_revision)
1064 url = urllib.urlopen(crrev_url)
1065 if url.getcode() == 200:
1066 data = json.loads(url.read())
1067 if 'git_sha' in data:
1068 return data['git_sha']
1069
pshenoy9ce271f2014-09-02 22:14:051070def PrintChangeLog(min_chromium_rev, max_chromium_rev):
1071 """Prints the changelog URL."""
1072
pshenoycd6bd682014-09-10 20:50:221073 print (' ' + CHANGELOG_URL % (GetGitHashFromSVNRevision(min_chromium_rev),
1074 GetGitHashFromSVNRevision(max_chromium_rev)))
pshenoy9ce271f2014-09-02 22:14:051075
[email protected]801fb652012-07-20 20:13:501076
[email protected]67e0bc62009-09-03 22:06:091077def main():
[email protected]2c1d2732009-10-29 19:52:171078 usage = ('%prog [options] [-- chromium-options]\n'
[email protected]887c9182013-02-12 20:30:311079 'Perform binary search on the snapshot builds to find a minimal\n'
1080 'range of revisions where a behavior change happened. The\n'
1081 'behaviors are described as "good" and "bad".\n'
1082 'It is NOT assumed that the behavior of the later revision is\n'
[email protected]09c58da2013-01-07 21:30:171083 'the bad one.\n'
[email protected]178aab72010-10-08 17:21:381084 '\n'
[email protected]887c9182013-02-12 20:30:311085 'Revision numbers should use\n'
1086 ' Official versions (e.g. 1.0.1000.0) for official builds. (-o)\n'
1087 ' SVN revisions (e.g. 123456) for chromium builds, from trunk.\n'
1088 ' Use base_trunk_revision from http://omahaproxy.appspot.com/\n'
1089 ' for earlier revs.\n'
1090 ' Chrome\'s about: build number and omahaproxy branch_revision\n'
1091 ' are incorrect, they are from branches.\n'
1092 '\n'
[email protected]178aab72010-10-08 17:21:381093 'Tip: add "-- --no-first-run" to bypass the first run prompts.')
[email protected]7ad66a72009-09-04 17:52:331094 parser = optparse.OptionParser(usage=usage)
[email protected]1a45d222009-09-19 01:58:571095 # Strangely, the default help output doesn't include the choice list.
mikecasea8cd284c2014-12-02 21:30:581096 choices = ['mac', 'mac64', 'win', 'win64', 'linux', 'linux64', 'linux-arm',
dmazzoni76e907d2015-01-22 08:14:491097 'chromeos']
[email protected]7ad66a72009-09-04 17:52:331098 parser.add_option('-a', '--archive',
[email protected]4df583c2014-07-31 17:11:551099 choices=choices,
1100 help='The buildbot archive to bisect [%s].' %
1101 '|'.join(choices))
1102 parser.add_option('-o',
1103 action='store_true',
1104 dest='official_builds',
1105 help='Bisect across official Chrome builds (internal '
1106 'only) instead of Chromium archives.')
1107 parser.add_option('-b', '--bad',
1108 type='str',
1109 help='A bad revision to start bisection. '
1110 'May be earlier or later than the good revision. '
1111 'Default is HEAD.')
1112 parser.add_option('-f', '--flash_path',
1113 type='str',
1114 help='Absolute path to a recent Adobe Pepper Flash '
1115 'binary to be used in this bisection (e.g. '
1116 'on Windows C:\...\pepflashplayer.dll and on Linux '
1117 '/opt/google/chrome/PepperFlash/'
1118 'libpepflashplayer.so).')
1119 parser.add_option('-d', '--pdf_path',
1120 type='str',
1121 help='Absolute path to a recent PDF plugin '
1122 'binary to be used in this bisection (e.g. '
1123 'on Windows C:\...\pdf.dll and on Linux '
1124 '/opt/google/chrome/libpdf.so). Option also enables '
1125 'print preview.')
1126 parser.add_option('-g', '--good',
1127 type='str',
1128 help='A good revision to start bisection. ' +
1129 'May be earlier or later than the bad revision. ' +
1130 'Default is 0.')
1131 parser.add_option('-p', '--profile', '--user-data-dir',
1132 type='str',
1133 default='profile',
1134 help='Profile to use; this will not reset every run. '
1135 'Defaults to a clean profile.')
1136 parser.add_option('-t', '--times',
1137 type='int',
1138 default=1,
1139 help='Number of times to run each build before asking '
1140 'if it\'s good or bad. Temporary profiles are reused.')
1141 parser.add_option('-c', '--command',
1142 type='str',
1143 default='%p %a',
1144 help='Command to execute. %p and %a refer to Chrome '
1145 'executable and specified extra arguments '
1146 'respectively. Use %s to specify all extra arguments '
1147 'as one string. Defaults to "%p %a". Note that any '
1148 'extra paths specified should be absolute.')
1149 parser.add_option('-l', '--blink',
1150 action='store_true',
1151 help='Use Blink bisect instead of Chromium. ')
1152 parser.add_option('', '--not-interactive',
1153 action='store_true',
1154 default=False,
1155 help='Use command exit code to tell good/bad revision.')
[email protected]011886692014-08-01 21:00:211156 parser.add_option('--asan',
1157 dest='asan',
1158 action='store_true',
1159 default=False,
1160 help='Allow the script to bisect ASAN builds')
rob724c9062015-01-22 00:26:421161 parser.add_option('--use-local-cache',
1162 dest='use_local_cache',
[email protected]6a7a5d62014-07-09 04:45:501163 action='store_true',
1164 default=False,
rob724c9062015-01-22 00:26:421165 help='Use a local file in the current directory to cache '
1166 'a list of known revisions to speed up the '
1167 'initialization of this script.')
[email protected]b3b20512013-08-26 18:51:041168
[email protected]7ad66a72009-09-04 17:52:331169 (opts, args) = parser.parse_args()
1170
1171 if opts.archive is None:
[email protected]178aab72010-10-08 17:21:381172 print 'Error: missing required parameter: --archive'
1173 print
[email protected]7ad66a72009-09-04 17:52:331174 parser.print_help()
1175 return 1
1176
[email protected]011886692014-08-01 21:00:211177 if opts.asan:
1178 supported_platforms = ['linux', 'mac', 'win']
1179 if opts.archive not in supported_platforms:
1180 print 'Error: ASAN bisecting only supported on these platforms: [%s].' % (
1181 '|'.join(supported_platforms))
1182 return 1
1183 if opts.official_builds:
1184 print 'Error: Do not yet support bisecting official ASAN builds.'
1185 return 1
1186
1187 if opts.asan:
1188 base_url = ASAN_BASE_URL
1189 elif opts.blink:
[email protected]4c6fec6b2013-09-17 17:44:081190 base_url = WEBKIT_BASE_URL
1191 else:
1192 base_url = CHROMIUM_BASE_URL
1193
[email protected]183706d92011-06-10 13:06:221194 # Create the context. Initialize 0 for the revisions as they are set below.
[email protected]2e0f2672014-08-13 20:32:581195 context = PathContext(base_url, opts.archive, opts.good, opts.bad,
rob724c9062015-01-22 00:26:421196 opts.official_builds, opts.asan, opts.use_local_cache,
John Budorick06e5df12015-02-27 17:44:271197 opts.flash_path, opts.pdf_path)
mikecasea8cd284c2014-12-02 21:30:581198
[email protected]67e0bc62009-09-03 22:06:091199 # Pick a starting point, try to get HEAD for this.
[email protected]2e0f2672014-08-13 20:32:581200 if not opts.bad:
1201 context.bad_revision = '999.0.0.0'
1202 context.bad_revision = GetChromiumRevision(
1203 context, context.GetLastChangeURL())
[email protected]67e0bc62009-09-03 22:06:091204
1205 # Find out when we were good.
[email protected]2e0f2672014-08-13 20:32:581206 if not opts.good:
1207 context.good_revision = '0.0.0.0' if opts.official_builds else 0
[email protected]801fb652012-07-20 20:13:501208
[email protected]fc3702e2013-11-09 04:23:001209 if opts.flash_path:
[email protected]2e0f2672014-08-13 20:32:581210 msg = 'Could not find Flash binary at %s' % opts.flash_path
1211 assert os.path.exists(opts.flash_path), msg
[email protected]fc3702e2013-11-09 04:23:001212
[email protected]cdb77062014-07-21 18:07:151213 if opts.pdf_path:
[email protected]2e0f2672014-08-13 20:32:581214 msg = 'Could not find PDF binary at %s' % opts.pdf_path
1215 assert os.path.exists(opts.pdf_path), msg
[email protected]cdb77062014-07-21 18:07:151216
[email protected]801fb652012-07-20 20:13:501217 if opts.official_builds:
[email protected]2e0f2672014-08-13 20:32:581218 context.good_revision = LooseVersion(context.good_revision)
1219 context.bad_revision = LooseVersion(context.bad_revision)
[email protected]801fb652012-07-20 20:13:501220 else:
[email protected]2e0f2672014-08-13 20:32:581221 context.good_revision = int(context.good_revision)
1222 context.bad_revision = int(context.bad_revision)
[email protected]801fb652012-07-20 20:13:501223
[email protected]5e93cf162012-01-28 02:16:561224 if opts.times < 1:
1225 print('Number of times to run (%d) must be greater than or equal to 1.' %
1226 opts.times)
1227 parser.print_help()
1228 return 1
1229
[email protected]011886692014-08-01 21:00:211230 if opts.asan:
1231 evaluator = IsGoodASANBuild
1232 else:
1233 evaluator = AskIsGoodBuild
1234
[email protected]2e0f2672014-08-13 20:32:581235 # Save these revision numbers to compare when showing the changelog URL
1236 # after the bisect.
1237 good_rev = context.good_revision
1238 bad_rev = context.bad_revision
1239
1240 (min_chromium_rev, max_chromium_rev, context) = Bisect(
1241 context, opts.times, opts.command, args, opts.profile,
[email protected]011886692014-08-01 21:00:211242 not opts.not_interactive, evaluator)
[email protected]67e0bc62009-09-03 22:06:091243
[email protected]ff50d1c2013-04-17 18:49:361244 # Get corresponding blink revisions.
[email protected]b2fe7f22011-10-25 22:58:311245 try:
[email protected]4c6fec6b2013-09-17 17:44:081246 min_blink_rev = GetBlinkRevisionForChromiumRevision(context,
1247 min_chromium_rev)
1248 max_blink_rev = GetBlinkRevisionForChromiumRevision(context,
1249 max_chromium_rev)
[email protected]4df583c2014-07-31 17:11:551250 except Exception:
[email protected]b2fe7f22011-10-25 22:58:311251 # Silently ignore the failure.
[email protected]ff50d1c2013-04-17 18:49:361252 min_blink_rev, max_blink_rev = 0, 0
[email protected]b2fe7f22011-10-25 22:58:311253
[email protected]3bdaa4752013-09-30 20:13:361254 if opts.blink:
1255 # We're done. Let the user know the results in an official manner.
1256 if good_rev > bad_rev:
1257 print DONE_MESSAGE_GOOD_MAX % (str(min_blink_rev), str(max_blink_rev))
1258 else:
1259 print DONE_MESSAGE_GOOD_MIN % (str(min_blink_rev), str(max_blink_rev))
[email protected]eadd95d2012-11-02 22:42:091260
[email protected]ff50d1c2013-04-17 18:49:361261 print 'BLINK CHANGELOG URL:'
1262 print ' ' + BLINK_CHANGELOG_URL % (max_blink_rev, min_blink_rev)
[email protected]3bdaa4752013-09-30 20:13:361263
[email protected]d0149c5c2012-05-29 21:12:111264 else:
[email protected]3bdaa4752013-09-30 20:13:361265 # We're done. Let the user know the results in an official manner.
1266 if good_rev > bad_rev:
1267 print DONE_MESSAGE_GOOD_MAX % (str(min_chromium_rev),
1268 str(max_chromium_rev))
1269 else:
1270 print DONE_MESSAGE_GOOD_MIN % (str(min_chromium_rev),
1271 str(max_chromium_rev))
1272 if min_blink_rev != max_blink_rev:
[email protected]4df583c2014-07-31 17:11:551273 print ('NOTE: There is a Blink roll in the range, '
1274 'you might also want to do a Blink bisect.')
[email protected]3bdaa4752013-09-30 20:13:361275
1276 print 'CHANGELOG URL:'
1277 if opts.official_builds:
1278 print OFFICIAL_CHANGELOG_URL % (min_chromium_rev, max_chromium_rev)
1279 else:
pshenoy9ce271f2014-09-02 22:14:051280 PrintChangeLog(min_chromium_rev, max_chromium_rev)
[email protected]cb155a82011-11-29 17:25:341281
[email protected]4df583c2014-07-31 17:11:551282
[email protected]67e0bc62009-09-03 22:06:091283if __name__ == '__main__':
[email protected]7ad66a72009-09-04 17:52:331284 sys.exit(main())