blob: 7e07e3d03761dac31b0fdefa4cc611cd46f12c8d [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:
rob1c836052015-05-18 16:34:02447 for (key, value) in json.load(cache_file).items():
448 cache[key] = value
rob724c9062015-01-22 00:26:42449 revisions = cache.get(cache_dict_key, [])
450 githash_svn_dict = cache.get('githash_svn_dict', {})
451 if revisions:
452 print 'Loaded revisions %d-%d from %s' % (revisions[0],
453 revisions[-1], cache_filename)
454 return (revisions, githash_svn_dict)
455 except (EnvironmentError, ValueError):
456 pass
457 return ([], {})
458
459 def _SaveBucketToCache():
460 """Save the list of revisions and the git-svn mappings to a file.
461 The list of revisions is assumed to be sorted."""
462 if self.use_local_cache:
463 cache[cache_dict_key] = revlist_all
464 cache['githash_svn_dict'] = self.githash_svn_dict
465 try:
466 with open(cache_filename, 'w') as cache_file:
467 json.dump(cache, cache_file)
468 print 'Saved revisions %d-%d to %s' % (
469 revlist_all[0], revlist_all[-1], cache_filename)
470 except EnvironmentError:
471 pass
472
[email protected]afe30662011-07-30 01:05:52473 # Download the revlist and filter for just the range between good and bad.
[email protected]eadd95d2012-11-02 22:42:09474 minrev = min(self.good_revision, self.bad_revision)
475 maxrev = max(self.good_revision, self.bad_revision)
rob724c9062015-01-22 00:26:42476
477 (revlist_all, self.githash_svn_dict) = _LoadBucketFromCache()
478 last_known_rev = revlist_all[-1] if revlist_all else 0
479 if last_known_rev < maxrev:
480 revlist_all.extend(map(int, self.ParseDirectoryIndex(last_known_rev)))
481 revlist_all = list(set(revlist_all))
482 revlist_all.sort()
483 _SaveBucketToCache()
[email protected]37ed3172013-09-24 23:49:30484
485 revlist = [x for x in revlist_all if x >= int(minrev) and x <= int(maxrev)]
[email protected]37ed3172013-09-24 23:49:30486
487 # Set good and bad revisions to be legit revisions.
488 if revlist:
489 if self.good_revision < self.bad_revision:
490 self.good_revision = revlist[0]
491 self.bad_revision = revlist[-1]
492 else:
493 self.bad_revision = revlist[0]
494 self.good_revision = revlist[-1]
495
496 # Fix chromium rev so that the deps blink revision matches REVISIONS file.
497 if self.base_url == WEBKIT_BASE_URL:
498 revlist_all.sort()
499 self.good_revision = FixChromiumRevForBlink(revlist,
500 revlist_all,
501 self,
502 self.good_revision)
503 self.bad_revision = FixChromiumRevForBlink(revlist,
504 revlist_all,
505 self,
506 self.bad_revision)
[email protected]afe30662011-07-30 01:05:52507 return revlist
508
[email protected]d0149c5c2012-05-29 21:12:11509 def GetOfficialBuildsList(self):
510 """Gets the list of official build numbers between self.good_revision and
511 self.bad_revision."""
[email protected]83048502014-08-21 16:48:44512
John Budorick06e5df12015-02-27 17:44:27513 def CheckDepotToolsInPath():
514 delimiter = ';' if sys.platform.startswith('win') else ':'
515 path_list = os.environ['PATH'].split(delimiter)
516 for path in path_list:
517 if path.rstrip(os.path.sep).endswith('depot_tools'):
518 return path
519 return None
520
521 def RunGsutilCommand(args):
522 gsutil_path = CheckDepotToolsInPath()
523 if gsutil_path is None:
524 print ('Follow the instructions in this document '
525 'http://dev.chromium.org/developers/how-tos/install-depot-tools'
526 ' to install depot_tools and then try again.')
527 sys.exit(1)
528 gsutil_path = os.path.join(gsutil_path, 'third_party', 'gsutil', 'gsutil')
529 gsutil = subprocess.Popen([sys.executable, gsutil_path] + args,
530 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
531 env=None)
532 stdout, stderr = gsutil.communicate()
533 if gsutil.returncode:
534 if (re.findall(r'status[ |=]40[1|3]', stderr) or
535 stderr.startswith(CREDENTIAL_ERROR_MESSAGE)):
536 print ('Follow these steps to configure your credentials and try'
537 ' running the bisect-builds.py again.:\n'
538 ' 1. Run "python %s config" and follow its instructions.\n'
539 ' 2. If you have a @google.com account, use that account.\n'
540 ' 3. For the project-id, just enter 0.' % gsutil_path)
541 sys.exit(1)
542 else:
543 raise Exception('Error running the gsutil command: %s' % stderr)
544 return stdout
545
546 def GsutilList(bucket):
547 query = 'gs://%s/' % bucket
548 stdout = RunGsutilCommand(['ls', query])
549 return [url[len(query):].strip('/') for url in stdout.splitlines()]
550
[email protected]d0149c5c2012-05-29 21:12:11551 # Download the revlist and filter for just the range between good and bad.
[email protected]eadd95d2012-11-02 22:42:09552 minrev = min(self.good_revision, self.bad_revision)
553 maxrev = max(self.good_revision, self.bad_revision)
John Budorick06e5df12015-02-27 17:44:27554 build_numbers = GsutilList(GS_BUCKET_NAME)
[email protected]83048502014-08-21 16:48:44555 revision_re = re.compile(r'(\d\d\.\d\.\d{4}\.\d+)')
556 build_numbers = filter(lambda b: revision_re.search(b), build_numbers)
[email protected]d0149c5c2012-05-29 21:12:11557 final_list = []
[email protected]d0149c5c2012-05-29 21:12:11558 parsed_build_numbers = [LooseVersion(x) for x in build_numbers]
[email protected]83048502014-08-21 16:48:44559 connection = httplib.HTTPConnection(GOOGLE_APIS_URL)
[email protected]d0149c5c2012-05-29 21:12:11560 for build_number in sorted(parsed_build_numbers):
[email protected]83048502014-08-21 16:48:44561 if build_number > maxrev:
562 break
563 if build_number < minrev:
564 continue
John Budorick06e5df12015-02-27 17:44:27565 path = ('/' + GS_BUCKET_NAME + '/' + str(build_number) + '/' +
[email protected]4df583c2014-07-31 17:11:55566 self._listing_platform_dir + self.archive_name)
[email protected]83048502014-08-21 16:48:44567 connection.request('HEAD', path)
568 response = connection.getresponse()
569 if response.status == 200:
570 final_list.append(str(build_number))
571 response.read()
572 connection.close()
[email protected]801fb652012-07-20 20:13:50573 return final_list
[email protected]bd8dcb92010-03-31 01:05:24574
[email protected]fc3702e2013-11-09 04:23:00575def UnzipFilenameToDir(filename, directory):
576 """Unzip |filename| to |directory|."""
[email protected]afe30662011-07-30 01:05:52577 cwd = os.getcwd()
578 if not os.path.isabs(filename):
579 filename = os.path.join(cwd, filename)
[email protected]bd8dcb92010-03-31 01:05:24580 zf = zipfile.ZipFile(filename)
581 # Make base.
[email protected]fc3702e2013-11-09 04:23:00582 if not os.path.isdir(directory):
583 os.mkdir(directory)
584 os.chdir(directory)
[email protected]e29c08c2012-09-17 20:50:50585 # Extract files.
586 for info in zf.infolist():
587 name = info.filename
588 if name.endswith('/'): # dir
589 if not os.path.isdir(name):
590 os.makedirs(name)
591 else: # file
[email protected]fc3702e2013-11-09 04:23:00592 directory = os.path.dirname(name)
John Budorick06e5df12015-02-27 17:44:27593 if not os.path.isdir(directory):
[email protected]fc3702e2013-11-09 04:23:00594 os.makedirs(directory)
[email protected]e29c08c2012-09-17 20:50:50595 out = open(name, 'wb')
596 out.write(zf.read(name))
597 out.close()
598 # Set permissions. Permission info in external_attr is shifted 16 bits.
599 os.chmod(name, info.external_attr >> 16L)
600 os.chdir(cwd)
[email protected]bd8dcb92010-03-31 01:05:24601
[email protected]67e0bc62009-09-03 22:06:09602
[email protected]468a9772011-08-09 18:42:00603def FetchRevision(context, rev, filename, quit_event=None, progress_event=None):
[email protected]afe30662011-07-30 01:05:52604 """Downloads and unzips revision |rev|.
605 @param context A PathContext instance.
606 @param rev The Chromium revision number/tag to download.
607 @param filename The destination for the downloaded file.
608 @param quit_event A threading.Event which will be set by the master thread to
609 indicate that the download should be aborted.
[email protected]468a9772011-08-09 18:42:00610 @param progress_event A threading.Event which will be set by the master thread
611 to indicate that the progress of the download should be
612 displayed.
[email protected]afe30662011-07-30 01:05:52613 """
614 def ReportHook(blocknum, blocksize, totalsize):
[email protected]946be752011-10-25 23:34:21615 if quit_event and quit_event.isSet():
[email protected]4df583c2014-07-31 17:11:55616 raise RuntimeError('Aborting download of revision %s' % str(rev))
[email protected]946be752011-10-25 23:34:21617 if progress_event and progress_event.isSet():
[email protected]468a9772011-08-09 18:42:00618 size = blocknum * blocksize
619 if totalsize == -1: # Total size not known.
[email protected]4df583c2014-07-31 17:11:55620 progress = 'Received %d bytes' % size
[email protected]468a9772011-08-09 18:42:00621 else:
622 size = min(totalsize, size)
[email protected]4df583c2014-07-31 17:11:55623 progress = 'Received %d of %d bytes, %.2f%%' % (
[email protected]468a9772011-08-09 18:42:00624 size, totalsize, 100.0 * size / totalsize)
625 # Send a \r to let all progress messages use just one line of output.
[email protected]4df583c2014-07-31 17:11:55626 sys.stdout.write('\r' + progress)
[email protected]468a9772011-08-09 18:42:00627 sys.stdout.flush()
[email protected]afe30662011-07-30 01:05:52628 download_url = context.GetDownloadURL(rev)
629 try:
John Budorick06e5df12015-02-27 17:44:27630 urllib.urlretrieve(download_url, filename, ReportHook)
[email protected]946be752011-10-25 23:34:21631 if progress_event and progress_event.isSet():
[email protected]ecaba01e62011-10-26 05:33:28632 print
mikecasee2b6ce82015-02-06 18:22:39633
[email protected]4df583c2014-07-31 17:11:55634 except RuntimeError:
[email protected]afe30662011-07-30 01:05:52635 pass
[email protected]7ad66a72009-09-04 17:52:33636
[email protected]7ad66a72009-09-04 17:52:33637
[email protected]4df583c2014-07-31 17:11:55638def RunRevision(context, revision, zip_file, profile, num_runs, command, args):
[email protected]afe30662011-07-30 01:05:52639 """Given a zipped revision, unzip it and run the test."""
[email protected]4df583c2014-07-31 17:11:55640 print 'Trying revision %s...' % str(revision)
[email protected]3ff00b72011-07-20 21:34:47641
[email protected]afe30662011-07-30 01:05:52642 # Create a temp directory and unzip the revision into it.
[email protected]7ad66a72009-09-04 17:52:33643 cwd = os.getcwd()
644 tempdir = tempfile.mkdtemp(prefix='bisect_tmp')
[email protected]4df583c2014-07-31 17:11:55645 UnzipFilenameToDir(zip_file, tempdir)
dmazzoni76e907d2015-01-22 08:14:49646
647 # Hack: Chrome OS archives are missing icudtl.dat; try to copy it from
648 # the local directory.
649 if context.platform == 'chromeos':
650 icudtl_path = 'third_party/icu/source/data/in/icudtl.dat'
651 if not os.access(icudtl_path, os.F_OK):
652 print 'Couldn\'t find: ' + icudtl_path
653 sys.exit()
654 os.system('cp %s %s/chrome-linux/' % (icudtl_path, tempdir))
655
[email protected]7ad66a72009-09-04 17:52:33656 os.chdir(tempdir)
[email protected]67e0bc62009-09-03 22:06:09657
[email protected]5e93cf162012-01-28 02:16:56658 # Run the build as many times as specified.
[email protected]4646a752013-07-19 22:14:34659 testargs = ['--user-data-dir=%s' % profile] + args
[email protected]d0149c5c2012-05-29 21:12:11660 # The sandbox must be run as root on Official Chrome, so bypass it.
[email protected]cdb77062014-07-21 18:07:15661 if ((context.is_official or context.flash_path or context.pdf_path) and
[email protected]fc3702e2013-11-09 04:23:00662 context.platform.startswith('linux')):
[email protected]d0149c5c2012-05-29 21:12:11663 testargs.append('--no-sandbox')
[email protected]fc3702e2013-11-09 04:23:00664 if context.flash_path:
665 testargs.append('--ppapi-flash-path=%s' % context.flash_path)
666 # We have to pass a large enough Flash version, which currently needs not
667 # be correct. Instead of requiring the user of the script to figure out and
668 # pass the correct version we just spoof it.
669 testargs.append('--ppapi-flash-version=99.9.999.999')
[email protected]d0149c5c2012-05-29 21:12:11670
[email protected]cdb77062014-07-21 18:07:15671 # TODO(vitalybuka): Remove in the future. See crbug.com/395687.
672 if context.pdf_path:
[email protected]011886692014-08-01 21:00:21673 shutil.copy(context.pdf_path,
674 os.path.dirname(context.GetLaunchPath(revision)))
[email protected]cdb77062014-07-21 18:07:15675 testargs.append('--enable-print-preview')
676
[email protected]4646a752013-07-19 22:14:34677 runcommand = []
[email protected]61ea90a2013-09-26 10:17:34678 for token in shlex.split(command):
[email protected]4df583c2014-07-31 17:11:55679 if token == '%a':
[email protected]4646a752013-07-19 22:14:34680 runcommand.extend(testargs)
681 else:
[email protected]4df583c2014-07-31 17:11:55682 runcommand.append(
[email protected]011886692014-08-01 21:00:21683 token.replace('%p', os.path.abspath(context.GetLaunchPath(revision))).
684 replace('%s', ' '.join(testargs)))
[email protected]4646a752013-07-19 22:14:34685
[email protected]d59c8712014-02-11 21:04:57686 results = []
[email protected]4df583c2014-07-31 17:11:55687 for _ in range(num_runs):
[email protected]4646a752013-07-19 22:14:34688 subproc = subprocess.Popen(runcommand,
[email protected]5e93cf162012-01-28 02:16:56689 bufsize=-1,
690 stdout=subprocess.PIPE,
691 stderr=subprocess.PIPE)
692 (stdout, stderr) = subproc.communicate()
[email protected]d59c8712014-02-11 21:04:57693 results.append((subproc.returncode, stdout, stderr))
[email protected]7ad66a72009-09-04 17:52:33694 os.chdir(cwd)
[email protected]7ad66a72009-09-04 17:52:33695 try:
696 shutil.rmtree(tempdir, True)
[email protected]4df583c2014-07-31 17:11:55697 except Exception:
[email protected]7ad66a72009-09-04 17:52:33698 pass
[email protected]67e0bc62009-09-03 22:06:09699
[email protected]d59c8712014-02-11 21:04:57700 for (returncode, stdout, stderr) in results:
701 if returncode:
702 return (returncode, stdout, stderr)
703 return results[0]
[email protected]79f14742010-03-10 01:01:57704
[email protected]cb155a82011-11-29 17:25:34705
[email protected]4df583c2014-07-31 17:11:55706# The arguments official_builds, status, stdout and stderr are unused.
707# They are present here because this function is passed to Bisect which then
708# calls it with 5 arguments.
709# pylint: disable=W0613
[email protected]d0149c5c2012-05-29 21:12:11710def AskIsGoodBuild(rev, official_builds, status, stdout, stderr):
[email protected]4df583c2014-07-31 17:11:55711 """Asks the user whether build |rev| is good or bad."""
[email protected]79f14742010-03-10 01:01:57712 # Loop until we get a response that we can parse.
[email protected]67e0bc62009-09-03 22:06:09713 while True:
[email protected]4df583c2014-07-31 17:11:55714 response = raw_input('Revision %s is '
[email protected]1d4a06242013-08-20 22:53:12715 '[(g)ood/(b)ad/(r)etry/(u)nknown/(q)uit]: ' %
[email protected]53bb6342012-06-01 04:11:00716 str(rev))
[email protected]1d4a06242013-08-20 22:53:12717 if response and response in ('g', 'b', 'r', 'u'):
[email protected]53bb6342012-06-01 04:11:00718 return response
[email protected]afe30662011-07-30 01:05:52719 if response and response == 'q':
720 raise SystemExit()
[email protected]67e0bc62009-09-03 22:06:09721
[email protected]cb155a82011-11-29 17:25:34722
[email protected]011886692014-08-01 21:00:21723def IsGoodASANBuild(rev, official_builds, status, stdout, stderr):
724 """Determine if an ASAN build |rev| is good or bad
725
726 Will examine stderr looking for the error message emitted by ASAN. If not
727 found then will fallback to asking the user."""
728 if stderr:
729 bad_count = 0
730 for line in stderr.splitlines():
731 print line
732 if line.find('ERROR: AddressSanitizer:') != -1:
733 bad_count += 1
734 if bad_count > 0:
735 print 'Revision %d determined to be bad.' % rev
736 return 'b'
737 return AskIsGoodBuild(rev, official_builds, status, stdout, stderr)
738
[email protected]53bb6342012-06-01 04:11:00739class DownloadJob(object):
740 """DownloadJob represents a task to download a given Chromium revision."""
[email protected]4df583c2014-07-31 17:11:55741
742 def __init__(self, context, name, rev, zip_file):
[email protected]53bb6342012-06-01 04:11:00743 super(DownloadJob, self).__init__()
744 # Store off the input parameters.
745 self.context = context
746 self.name = name
747 self.rev = rev
[email protected]4df583c2014-07-31 17:11:55748 self.zip_file = zip_file
[email protected]53bb6342012-06-01 04:11:00749 self.quit_event = threading.Event()
750 self.progress_event = threading.Event()
[email protected]4df583c2014-07-31 17:11:55751 self.thread = None
[email protected]53bb6342012-06-01 04:11:00752
753 def Start(self):
754 """Starts the download."""
755 fetchargs = (self.context,
756 self.rev,
[email protected]4df583c2014-07-31 17:11:55757 self.zip_file,
[email protected]53bb6342012-06-01 04:11:00758 self.quit_event,
759 self.progress_event)
760 self.thread = threading.Thread(target=FetchRevision,
761 name=self.name,
762 args=fetchargs)
763 self.thread.start()
764
765 def Stop(self):
766 """Stops the download which must have been started previously."""
[email protected]4df583c2014-07-31 17:11:55767 assert self.thread, 'DownloadJob must be started before Stop is called.'
[email protected]53bb6342012-06-01 04:11:00768 self.quit_event.set()
769 self.thread.join()
[email protected]4df583c2014-07-31 17:11:55770 os.unlink(self.zip_file)
[email protected]53bb6342012-06-01 04:11:00771
772 def WaitFor(self):
773 """Prints a message and waits for the download to complete. The download
774 must have been started previously."""
[email protected]4df583c2014-07-31 17:11:55775 assert self.thread, 'DownloadJob must be started before WaitFor is called.'
776 print 'Downloading revision %s...' % str(self.rev)
[email protected]53bb6342012-06-01 04:11:00777 self.progress_event.set() # Display progress of download.
778 self.thread.join()
779
780
[email protected]2e0f2672014-08-13 20:32:58781def Bisect(context,
[email protected]5e93cf162012-01-28 02:16:56782 num_runs=1,
[email protected]4df583c2014-07-31 17:11:55783 command='%p %a',
[email protected]60ac66e32011-07-18 16:08:25784 try_args=(),
[email protected]afe30662011-07-30 01:05:52785 profile=None,
[email protected]d59c8712014-02-11 21:04:57786 interactive=True,
[email protected]53bb6342012-06-01 04:11:00787 evaluate=AskIsGoodBuild):
[email protected]afe30662011-07-30 01:05:52788 """Given known good and known bad revisions, run a binary search on all
789 archived revisions to determine the last known good revision.
[email protected]60ac66e32011-07-18 16:08:25790
[email protected]2e0f2672014-08-13 20:32:58791 @param context PathContext object initialized with user provided parameters.
[email protected]5e93cf162012-01-28 02:16:56792 @param num_runs Number of times to run each build for asking good/bad.
[email protected]afe30662011-07-30 01:05:52793 @param try_args A tuple of arguments to pass to the test application.
794 @param profile The name of the user profile to run with.
[email protected]d59c8712014-02-11 21:04:57795 @param interactive If it is false, use command exit code for good or bad
796 judgment of the argument build.
[email protected]53bb6342012-06-01 04:11:00797 @param evaluate A function which returns 'g' if the argument build is good,
798 'b' if it's bad or 'u' if unknown.
[email protected]afe30662011-07-30 01:05:52799
800 Threading is used to fetch Chromium revisions in the background, speeding up
801 the user's experience. For example, suppose the bounds of the search are
802 good_rev=0, bad_rev=100. The first revision to be checked is 50. Depending on
803 whether revision 50 is good or bad, the next revision to check will be either
804 25 or 75. So, while revision 50 is being checked, the script will download
805 revisions 25 and 75 in the background. Once the good/bad verdict on rev 50 is
806 known:
807
808 - If rev 50 is good, the download of rev 25 is cancelled, and the next test
809 is run on rev 75.
810
811 - If rev 50 is bad, the download of rev 75 is cancelled, and the next test
812 is run on rev 25.
[email protected]60ac66e32011-07-18 16:08:25813 """
814
[email protected]afe30662011-07-30 01:05:52815 if not profile:
816 profile = 'profile'
817
[email protected]2e0f2672014-08-13 20:32:58818 good_rev = context.good_revision
819 bad_rev = context.bad_revision
[email protected]afe30662011-07-30 01:05:52820 cwd = os.getcwd()
821
[email protected]28a3c122014-08-09 11:04:51822 print 'Downloading list of known revisions...',
rob724c9062015-01-22 00:26:42823 if not context.use_local_cache and not context.is_official:
824 print '(use --use-local-cache to cache and re-use the list of revisions)'
[email protected]28a3c122014-08-09 11:04:51825 else:
826 print
[email protected]d0149c5c2012-05-29 21:12:11827 _GetDownloadPath = lambda rev: os.path.join(cwd,
828 '%s-%s' % (str(rev), context.archive_name))
[email protected]2e0f2672014-08-13 20:32:58829 if context.is_official:
[email protected]d0149c5c2012-05-29 21:12:11830 revlist = context.GetOfficialBuildsList()
831 else:
832 revlist = context.GetRevList()
[email protected]afe30662011-07-30 01:05:52833
834 # Get a list of revisions to bisect across.
835 if len(revlist) < 2: # Don't have enough builds to bisect.
836 msg = 'We don\'t have enough builds to bisect. revlist: %s' % revlist
837 raise RuntimeError(msg)
838
839 # Figure out our bookends and first pivot point; fetch the pivot revision.
[email protected]eadd95d2012-11-02 22:42:09840 minrev = 0
841 maxrev = len(revlist) - 1
842 pivot = maxrev / 2
[email protected]afe30662011-07-30 01:05:52843 rev = revlist[pivot]
[email protected]4df583c2014-07-31 17:11:55844 zip_file = _GetDownloadPath(rev)
845 fetch = DownloadJob(context, 'initial_fetch', rev, zip_file)
[email protected]eadd95d2012-11-02 22:42:09846 fetch.Start()
847 fetch.WaitFor()
[email protected]60ac66e32011-07-18 16:08:25848
849 # Binary search time!
[email protected]4df583c2014-07-31 17:11:55850 while fetch and fetch.zip_file and maxrev - minrev > 1:
[email protected]eadd95d2012-11-02 22:42:09851 if bad_rev < good_rev:
[email protected]4df583c2014-07-31 17:11:55852 min_str, max_str = 'bad', 'good'
[email protected]eadd95d2012-11-02 22:42:09853 else:
[email protected]4df583c2014-07-31 17:11:55854 min_str, max_str = 'good', 'bad'
855 print 'Bisecting range [%s (%s), %s (%s)].' % (revlist[minrev], min_str,
[email protected]eadd95d2012-11-02 22:42:09856 revlist[maxrev], max_str)
857
[email protected]afe30662011-07-30 01:05:52858 # Pre-fetch next two possible pivots
859 # - down_pivot is the next revision to check if the current revision turns
860 # out to be bad.
861 # - up_pivot is the next revision to check if the current revision turns
862 # out to be good.
[email protected]eadd95d2012-11-02 22:42:09863 down_pivot = int((pivot - minrev) / 2) + minrev
[email protected]53bb6342012-06-01 04:11:00864 down_fetch = None
[email protected]eadd95d2012-11-02 22:42:09865 if down_pivot != pivot and down_pivot != minrev:
[email protected]afe30662011-07-30 01:05:52866 down_rev = revlist[down_pivot]
[email protected]53bb6342012-06-01 04:11:00867 down_fetch = DownloadJob(context, 'down_fetch', down_rev,
868 _GetDownloadPath(down_rev))
869 down_fetch.Start()
[email protected]60ac66e32011-07-18 16:08:25870
[email protected]eadd95d2012-11-02 22:42:09871 up_pivot = int((maxrev - pivot) / 2) + pivot
[email protected]53bb6342012-06-01 04:11:00872 up_fetch = None
[email protected]eadd95d2012-11-02 22:42:09873 if up_pivot != pivot and up_pivot != maxrev:
[email protected]afe30662011-07-30 01:05:52874 up_rev = revlist[up_pivot]
[email protected]53bb6342012-06-01 04:11:00875 up_fetch = DownloadJob(context, 'up_fetch', up_rev,
876 _GetDownloadPath(up_rev))
877 up_fetch.Start()
[email protected]60ac66e32011-07-18 16:08:25878
[email protected]afe30662011-07-30 01:05:52879 # Run test on the pivot revision.
[email protected]e29c08c2012-09-17 20:50:50880 status = None
881 stdout = None
882 stderr = None
883 try:
884 (status, stdout, stderr) = RunRevision(context,
885 rev,
[email protected]4df583c2014-07-31 17:11:55886 fetch.zip_file,
[email protected]e29c08c2012-09-17 20:50:50887 profile,
888 num_runs,
[email protected]4646a752013-07-19 22:14:34889 command,
[email protected]e29c08c2012-09-17 20:50:50890 try_args)
891 except Exception, e:
[email protected]fc3702e2013-11-09 04:23:00892 print >> sys.stderr, e
[email protected]60ac66e32011-07-18 16:08:25893
[email protected]53bb6342012-06-01 04:11:00894 # Call the evaluate function to see if the current revision is good or bad.
[email protected]afe30662011-07-30 01:05:52895 # On that basis, kill one of the background downloads and complete the
896 # other, as described in the comments above.
897 try:
[email protected]d59c8712014-02-11 21:04:57898 if not interactive:
899 if status:
900 answer = 'b'
901 print 'Bad revision: %s' % rev
902 else:
903 answer = 'g'
904 print 'Good revision: %s' % rev
905 else:
[email protected]2e0f2672014-08-13 20:32:58906 answer = evaluate(rev, context.is_official, status, stdout, stderr)
[email protected]4df583c2014-07-31 17:11:55907 if ((answer == 'g' and good_rev < bad_rev)
908 or (answer == 'b' and bad_rev < good_rev)):
[email protected]1d4a06242013-08-20 22:53:12909 fetch.Stop()
[email protected]eadd95d2012-11-02 22:42:09910 minrev = pivot
[email protected]53bb6342012-06-01 04:11:00911 if down_fetch:
912 down_fetch.Stop() # Kill the download of the older revision.
[email protected]1d4a06242013-08-20 22:53:12913 fetch = None
[email protected]53bb6342012-06-01 04:11:00914 if up_fetch:
915 up_fetch.WaitFor()
[email protected]afe30662011-07-30 01:05:52916 pivot = up_pivot
[email protected]eadd95d2012-11-02 22:42:09917 fetch = up_fetch
[email protected]4df583c2014-07-31 17:11:55918 elif ((answer == 'b' and good_rev < bad_rev)
919 or (answer == 'g' and bad_rev < good_rev)):
[email protected]1d4a06242013-08-20 22:53:12920 fetch.Stop()
[email protected]eadd95d2012-11-02 22:42:09921 maxrev = pivot
[email protected]53bb6342012-06-01 04:11:00922 if up_fetch:
923 up_fetch.Stop() # Kill the download of the newer revision.
[email protected]1d4a06242013-08-20 22:53:12924 fetch = None
[email protected]53bb6342012-06-01 04:11:00925 if down_fetch:
926 down_fetch.WaitFor()
[email protected]afe30662011-07-30 01:05:52927 pivot = down_pivot
[email protected]eadd95d2012-11-02 22:42:09928 fetch = down_fetch
[email protected]1d4a06242013-08-20 22:53:12929 elif answer == 'r':
930 pass # Retry requires no changes.
[email protected]53bb6342012-06-01 04:11:00931 elif answer == 'u':
932 # Nuke the revision from the revlist and choose a new pivot.
[email protected]1d4a06242013-08-20 22:53:12933 fetch.Stop()
[email protected]53bb6342012-06-01 04:11:00934 revlist.pop(pivot)
[email protected]eadd95d2012-11-02 22:42:09935 maxrev -= 1 # Assumes maxrev >= pivot.
[email protected]53bb6342012-06-01 04:11:00936
[email protected]eadd95d2012-11-02 22:42:09937 if maxrev - minrev > 1:
[email protected]53bb6342012-06-01 04:11:00938 # Alternate between using down_pivot or up_pivot for the new pivot
939 # point, without affecting the range. Do this instead of setting the
940 # pivot to the midpoint of the new range because adjacent revisions
941 # are likely affected by the same issue that caused the (u)nknown
942 # response.
943 if up_fetch and down_fetch:
944 fetch = [up_fetch, down_fetch][len(revlist) % 2]
945 elif up_fetch:
946 fetch = up_fetch
947 else:
948 fetch = down_fetch
949 fetch.WaitFor()
950 if fetch == up_fetch:
951 pivot = up_pivot - 1 # Subtracts 1 because revlist was resized.
952 else:
953 pivot = down_pivot
[email protected]4df583c2014-07-31 17:11:55954 zip_file = fetch.zip_file
[email protected]53bb6342012-06-01 04:11:00955
956 if down_fetch and fetch != down_fetch:
957 down_fetch.Stop()
958 if up_fetch and fetch != up_fetch:
959 up_fetch.Stop()
960 else:
[email protected]4df583c2014-07-31 17:11:55961 assert False, 'Unexpected return value from evaluate(): ' + answer
[email protected]afe30662011-07-30 01:05:52962 except SystemExit:
[email protected]4df583c2014-07-31 17:11:55963 print 'Cleaning up...'
[email protected]5e93cf162012-01-28 02:16:56964 for f in [_GetDownloadPath(revlist[down_pivot]),
965 _GetDownloadPath(revlist[up_pivot])]:
[email protected]afe30662011-07-30 01:05:52966 try:
967 os.unlink(f)
968 except OSError:
969 pass
970 sys.exit(0)
971
972 rev = revlist[pivot]
973
[email protected]2e0f2672014-08-13 20:32:58974 return (revlist[minrev], revlist[maxrev], context)
[email protected]60ac66e32011-07-18 16:08:25975
976
pshenoycd6bd682014-09-10 20:50:22977def GetBlinkDEPSRevisionForChromiumRevision(self, rev):
[email protected]4c6fec6b2013-09-17 17:44:08978 """Returns the blink revision that was in REVISIONS file at
[email protected]b2fe7f22011-10-25 22:58:31979 chromium revision |rev|."""
pshenoycd6bd682014-09-10 20:50:22980
981 def _GetBlinkRev(url, blink_re):
982 m = blink_re.search(url.read())
983 url.close()
984 if m:
985 return m.group(1)
986
987 url = urllib.urlopen(DEPS_FILE_OLD % rev)
988 if url.getcode() == 200:
989 # . doesn't match newlines without re.DOTALL, so this is safe.
990 blink_re = re.compile(r'webkit_revision\D*(\d+)')
991 return int(_GetBlinkRev(url, blink_re))
[email protected]37ed3172013-09-24 23:49:30992 else:
pshenoycd6bd682014-09-10 20:50:22993 url = urllib.urlopen(DEPS_FILE_NEW % GetGitHashFromSVNRevision(rev))
994 if url.getcode() == 200:
995 blink_re = re.compile(r'webkit_revision\D*\d+;\D*\d+;(\w+)')
996 blink_git_sha = _GetBlinkRev(url, blink_re)
997 return self.GetSVNRevisionFromGitHash(blink_git_sha, 'blink')
998 raise Exception('Could not get Blink revision for Chromium rev %d' % rev)
[email protected]37ed3172013-09-24 23:49:30999
1000
[email protected]2e0f2672014-08-13 20:32:581001def GetBlinkRevisionForChromiumRevision(context, rev):
[email protected]37ed3172013-09-24 23:49:301002 """Returns the blink revision that was in REVISIONS file at
1003 chromium revision |rev|."""
[email protected]3e7c85322014-06-27 20:27:361004 def _IsRevisionNumber(revision):
1005 if isinstance(revision, int):
1006 return True
1007 else:
1008 return revision.isdigit()
[email protected]2e0f2672014-08-13 20:32:581009 if str(rev) in context.githash_svn_dict:
1010 rev = context.githash_svn_dict[str(rev)]
1011 file_url = '%s/%s%s/REVISIONS' % (context.base_url,
1012 context._listing_platform_dir, rev)
[email protected]4c6fec6b2013-09-17 17:44:081013 url = urllib.urlopen(file_url)
[email protected]2e0f2672014-08-13 20:32:581014 if url.getcode() == 200:
1015 try:
1016 data = json.loads(url.read())
1017 except ValueError:
1018 print 'ValueError for JSON URL: %s' % file_url
1019 raise ValueError
1020 else:
1021 raise ValueError
[email protected]b2fe7f22011-10-25 22:58:311022 url.close()
[email protected]4c6fec6b2013-09-17 17:44:081023 if 'webkit_revision' in data:
[email protected]3e7c85322014-06-27 20:27:361024 blink_rev = data['webkit_revision']
1025 if not _IsRevisionNumber(blink_rev):
[email protected]2e0f2672014-08-13 20:32:581026 blink_rev = int(context.GetSVNRevisionFromGitHash(blink_rev, 'blink'))
[email protected]3e7c85322014-06-27 20:27:361027 return blink_rev
[email protected]b2fe7f22011-10-25 22:58:311028 else:
[email protected]ff50d1c2013-04-17 18:49:361029 raise Exception('Could not get blink revision for cr rev %d' % rev)
[email protected]b2fe7f22011-10-25 22:58:311030
[email protected]4df583c2014-07-31 17:11:551031
[email protected]37ed3172013-09-24 23:49:301032def FixChromiumRevForBlink(revisions_final, revisions, self, rev):
1033 """Returns the chromium revision that has the correct blink revision
1034 for blink bisect, DEPS and REVISIONS file might not match since
1035 blink snapshots point to tip of tree blink.
1036 Note: The revisions_final variable might get modified to include
1037 additional revisions."""
pshenoycd6bd682014-09-10 20:50:221038 blink_deps_rev = GetBlinkDEPSRevisionForChromiumRevision(self, rev)
[email protected]37ed3172013-09-24 23:49:301039
1040 while (GetBlinkRevisionForChromiumRevision(self, rev) > blink_deps_rev):
1041 idx = revisions.index(rev)
1042 if idx > 0:
1043 rev = revisions[idx-1]
1044 if rev not in revisions_final:
1045 revisions_final.insert(0, rev)
1046
1047 revisions_final.sort()
1048 return rev
[email protected]b2fe7f22011-10-25 22:58:311049
[email protected]4df583c2014-07-31 17:11:551050
[email protected]5980b752014-07-02 00:34:401051def GetChromiumRevision(context, url):
[email protected]801fb652012-07-20 20:13:501052 """Returns the chromium revision read from given URL."""
1053 try:
1054 # Location of the latest build revision number
[email protected]5980b752014-07-02 00:34:401055 latest_revision = urllib.urlopen(url).read()
1056 if latest_revision.isdigit():
1057 return int(latest_revision)
1058 return context.GetSVNRevisionFromGitHash(latest_revision)
[email protected]4df583c2014-07-31 17:11:551059 except Exception:
1060 print 'Could not determine latest revision. This could be bad...'
[email protected]801fb652012-07-20 20:13:501061 return 999999999
1062
pshenoycd6bd682014-09-10 20:50:221063def GetGitHashFromSVNRevision(svn_revision):
1064 crrev_url = CRREV_URL + str(svn_revision)
1065 url = urllib.urlopen(crrev_url)
1066 if url.getcode() == 200:
1067 data = json.loads(url.read())
1068 if 'git_sha' in data:
1069 return data['git_sha']
1070
pshenoy9ce271f2014-09-02 22:14:051071def PrintChangeLog(min_chromium_rev, max_chromium_rev):
1072 """Prints the changelog URL."""
1073
pshenoycd6bd682014-09-10 20:50:221074 print (' ' + CHANGELOG_URL % (GetGitHashFromSVNRevision(min_chromium_rev),
1075 GetGitHashFromSVNRevision(max_chromium_rev)))
pshenoy9ce271f2014-09-02 22:14:051076
[email protected]801fb652012-07-20 20:13:501077
[email protected]67e0bc62009-09-03 22:06:091078def main():
[email protected]2c1d2732009-10-29 19:52:171079 usage = ('%prog [options] [-- chromium-options]\n'
[email protected]887c9182013-02-12 20:30:311080 'Perform binary search on the snapshot builds to find a minimal\n'
1081 'range of revisions where a behavior change happened. The\n'
1082 'behaviors are described as "good" and "bad".\n'
1083 'It is NOT assumed that the behavior of the later revision is\n'
[email protected]09c58da2013-01-07 21:30:171084 'the bad one.\n'
[email protected]178aab72010-10-08 17:21:381085 '\n'
[email protected]887c9182013-02-12 20:30:311086 'Revision numbers should use\n'
1087 ' Official versions (e.g. 1.0.1000.0) for official builds. (-o)\n'
1088 ' SVN revisions (e.g. 123456) for chromium builds, from trunk.\n'
1089 ' Use base_trunk_revision from http://omahaproxy.appspot.com/\n'
1090 ' for earlier revs.\n'
1091 ' Chrome\'s about: build number and omahaproxy branch_revision\n'
1092 ' are incorrect, they are from branches.\n'
1093 '\n'
[email protected]178aab72010-10-08 17:21:381094 'Tip: add "-- --no-first-run" to bypass the first run prompts.')
[email protected]7ad66a72009-09-04 17:52:331095 parser = optparse.OptionParser(usage=usage)
[email protected]1a45d222009-09-19 01:58:571096 # Strangely, the default help output doesn't include the choice list.
mikecasea8cd284c2014-12-02 21:30:581097 choices = ['mac', 'mac64', 'win', 'win64', 'linux', 'linux64', 'linux-arm',
dmazzoni76e907d2015-01-22 08:14:491098 'chromeos']
[email protected]7ad66a72009-09-04 17:52:331099 parser.add_option('-a', '--archive',
[email protected]4df583c2014-07-31 17:11:551100 choices=choices,
1101 help='The buildbot archive to bisect [%s].' %
1102 '|'.join(choices))
1103 parser.add_option('-o',
1104 action='store_true',
1105 dest='official_builds',
1106 help='Bisect across official Chrome builds (internal '
1107 'only) instead of Chromium archives.')
1108 parser.add_option('-b', '--bad',
1109 type='str',
1110 help='A bad revision to start bisection. '
1111 'May be earlier or later than the good revision. '
1112 'Default is HEAD.')
1113 parser.add_option('-f', '--flash_path',
1114 type='str',
1115 help='Absolute path to a recent Adobe Pepper Flash '
1116 'binary to be used in this bisection (e.g. '
1117 'on Windows C:\...\pepflashplayer.dll and on Linux '
1118 '/opt/google/chrome/PepperFlash/'
1119 'libpepflashplayer.so).')
1120 parser.add_option('-d', '--pdf_path',
1121 type='str',
1122 help='Absolute path to a recent PDF plugin '
1123 'binary to be used in this bisection (e.g. '
1124 'on Windows C:\...\pdf.dll and on Linux '
1125 '/opt/google/chrome/libpdf.so). Option also enables '
1126 'print preview.')
1127 parser.add_option('-g', '--good',
1128 type='str',
1129 help='A good revision to start bisection. ' +
1130 'May be earlier or later than the bad revision. ' +
1131 'Default is 0.')
1132 parser.add_option('-p', '--profile', '--user-data-dir',
1133 type='str',
1134 default='profile',
1135 help='Profile to use; this will not reset every run. '
1136 'Defaults to a clean profile.')
1137 parser.add_option('-t', '--times',
1138 type='int',
1139 default=1,
1140 help='Number of times to run each build before asking '
1141 'if it\'s good or bad. Temporary profiles are reused.')
1142 parser.add_option('-c', '--command',
1143 type='str',
1144 default='%p %a',
1145 help='Command to execute. %p and %a refer to Chrome '
1146 'executable and specified extra arguments '
1147 'respectively. Use %s to specify all extra arguments '
1148 'as one string. Defaults to "%p %a". Note that any '
1149 'extra paths specified should be absolute.')
1150 parser.add_option('-l', '--blink',
1151 action='store_true',
1152 help='Use Blink bisect instead of Chromium. ')
1153 parser.add_option('', '--not-interactive',
1154 action='store_true',
1155 default=False,
1156 help='Use command exit code to tell good/bad revision.')
[email protected]011886692014-08-01 21:00:211157 parser.add_option('--asan',
1158 dest='asan',
1159 action='store_true',
1160 default=False,
1161 help='Allow the script to bisect ASAN builds')
rob724c9062015-01-22 00:26:421162 parser.add_option('--use-local-cache',
1163 dest='use_local_cache',
[email protected]6a7a5d62014-07-09 04:45:501164 action='store_true',
1165 default=False,
rob724c9062015-01-22 00:26:421166 help='Use a local file in the current directory to cache '
1167 'a list of known revisions to speed up the '
1168 'initialization of this script.')
[email protected]b3b20512013-08-26 18:51:041169
[email protected]7ad66a72009-09-04 17:52:331170 (opts, args) = parser.parse_args()
1171
1172 if opts.archive is None:
[email protected]178aab72010-10-08 17:21:381173 print 'Error: missing required parameter: --archive'
1174 print
[email protected]7ad66a72009-09-04 17:52:331175 parser.print_help()
1176 return 1
1177
[email protected]011886692014-08-01 21:00:211178 if opts.asan:
1179 supported_platforms = ['linux', 'mac', 'win']
1180 if opts.archive not in supported_platforms:
1181 print 'Error: ASAN bisecting only supported on these platforms: [%s].' % (
1182 '|'.join(supported_platforms))
1183 return 1
1184 if opts.official_builds:
1185 print 'Error: Do not yet support bisecting official ASAN builds.'
1186 return 1
1187
1188 if opts.asan:
1189 base_url = ASAN_BASE_URL
1190 elif opts.blink:
[email protected]4c6fec6b2013-09-17 17:44:081191 base_url = WEBKIT_BASE_URL
1192 else:
1193 base_url = CHROMIUM_BASE_URL
1194
[email protected]183706d92011-06-10 13:06:221195 # Create the context. Initialize 0 for the revisions as they are set below.
[email protected]2e0f2672014-08-13 20:32:581196 context = PathContext(base_url, opts.archive, opts.good, opts.bad,
rob724c9062015-01-22 00:26:421197 opts.official_builds, opts.asan, opts.use_local_cache,
John Budorick06e5df12015-02-27 17:44:271198 opts.flash_path, opts.pdf_path)
mikecasea8cd284c2014-12-02 21:30:581199
[email protected]67e0bc62009-09-03 22:06:091200 # Pick a starting point, try to get HEAD for this.
[email protected]2e0f2672014-08-13 20:32:581201 if not opts.bad:
1202 context.bad_revision = '999.0.0.0'
1203 context.bad_revision = GetChromiumRevision(
1204 context, context.GetLastChangeURL())
[email protected]67e0bc62009-09-03 22:06:091205
1206 # Find out when we were good.
[email protected]2e0f2672014-08-13 20:32:581207 if not opts.good:
1208 context.good_revision = '0.0.0.0' if opts.official_builds else 0
[email protected]801fb652012-07-20 20:13:501209
[email protected]fc3702e2013-11-09 04:23:001210 if opts.flash_path:
[email protected]2e0f2672014-08-13 20:32:581211 msg = 'Could not find Flash binary at %s' % opts.flash_path
1212 assert os.path.exists(opts.flash_path), msg
[email protected]fc3702e2013-11-09 04:23:001213
[email protected]cdb77062014-07-21 18:07:151214 if opts.pdf_path:
[email protected]2e0f2672014-08-13 20:32:581215 msg = 'Could not find PDF binary at %s' % opts.pdf_path
1216 assert os.path.exists(opts.pdf_path), msg
[email protected]cdb77062014-07-21 18:07:151217
[email protected]801fb652012-07-20 20:13:501218 if opts.official_builds:
[email protected]2e0f2672014-08-13 20:32:581219 context.good_revision = LooseVersion(context.good_revision)
1220 context.bad_revision = LooseVersion(context.bad_revision)
[email protected]801fb652012-07-20 20:13:501221 else:
[email protected]2e0f2672014-08-13 20:32:581222 context.good_revision = int(context.good_revision)
1223 context.bad_revision = int(context.bad_revision)
[email protected]801fb652012-07-20 20:13:501224
[email protected]5e93cf162012-01-28 02:16:561225 if opts.times < 1:
1226 print('Number of times to run (%d) must be greater than or equal to 1.' %
1227 opts.times)
1228 parser.print_help()
1229 return 1
1230
[email protected]011886692014-08-01 21:00:211231 if opts.asan:
1232 evaluator = IsGoodASANBuild
1233 else:
1234 evaluator = AskIsGoodBuild
1235
[email protected]2e0f2672014-08-13 20:32:581236 # Save these revision numbers to compare when showing the changelog URL
1237 # after the bisect.
1238 good_rev = context.good_revision
1239 bad_rev = context.bad_revision
1240
1241 (min_chromium_rev, max_chromium_rev, context) = Bisect(
1242 context, opts.times, opts.command, args, opts.profile,
[email protected]011886692014-08-01 21:00:211243 not opts.not_interactive, evaluator)
[email protected]67e0bc62009-09-03 22:06:091244
[email protected]ff50d1c2013-04-17 18:49:361245 # Get corresponding blink revisions.
[email protected]b2fe7f22011-10-25 22:58:311246 try:
[email protected]4c6fec6b2013-09-17 17:44:081247 min_blink_rev = GetBlinkRevisionForChromiumRevision(context,
1248 min_chromium_rev)
1249 max_blink_rev = GetBlinkRevisionForChromiumRevision(context,
1250 max_chromium_rev)
[email protected]4df583c2014-07-31 17:11:551251 except Exception:
[email protected]b2fe7f22011-10-25 22:58:311252 # Silently ignore the failure.
[email protected]ff50d1c2013-04-17 18:49:361253 min_blink_rev, max_blink_rev = 0, 0
[email protected]b2fe7f22011-10-25 22:58:311254
[email protected]3bdaa4752013-09-30 20:13:361255 if opts.blink:
1256 # We're done. Let the user know the results in an official manner.
1257 if good_rev > bad_rev:
1258 print DONE_MESSAGE_GOOD_MAX % (str(min_blink_rev), str(max_blink_rev))
1259 else:
1260 print DONE_MESSAGE_GOOD_MIN % (str(min_blink_rev), str(max_blink_rev))
[email protected]eadd95d2012-11-02 22:42:091261
[email protected]ff50d1c2013-04-17 18:49:361262 print 'BLINK CHANGELOG URL:'
1263 print ' ' + BLINK_CHANGELOG_URL % (max_blink_rev, min_blink_rev)
[email protected]3bdaa4752013-09-30 20:13:361264
[email protected]d0149c5c2012-05-29 21:12:111265 else:
[email protected]3bdaa4752013-09-30 20:13:361266 # We're done. Let the user know the results in an official manner.
1267 if good_rev > bad_rev:
1268 print DONE_MESSAGE_GOOD_MAX % (str(min_chromium_rev),
1269 str(max_chromium_rev))
1270 else:
1271 print DONE_MESSAGE_GOOD_MIN % (str(min_chromium_rev),
1272 str(max_chromium_rev))
1273 if min_blink_rev != max_blink_rev:
[email protected]4df583c2014-07-31 17:11:551274 print ('NOTE: There is a Blink roll in the range, '
1275 'you might also want to do a Blink bisect.')
[email protected]3bdaa4752013-09-30 20:13:361276
1277 print 'CHANGELOG URL:'
1278 if opts.official_builds:
1279 print OFFICIAL_CHANGELOG_URL % (min_chromium_rev, max_chromium_rev)
1280 else:
pshenoy9ce271f2014-09-02 22:14:051281 PrintChangeLog(min_chromium_rev, max_chromium_rev)
[email protected]cb155a82011-11-29 17:25:341282
[email protected]4df583c2014-07-31 17:11:551283
[email protected]67e0bc62009-09-03 22:06:091284if __name__ == '__main__':
[email protected]7ad66a72009-09-04 17:52:331285 sys.exit(main())