OLD | NEW |
---|---|
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 """Snapshot Build Bisect Tool | 6 """Snapshot Build Bisect Tool |
7 | 7 |
8 This script bisects a snapshot archive using binary search. It starts at | 8 This script bisects a snapshot archive using binary search. It starts at |
9 a bad revision (it will try to guess HEAD) and asks for a last known-good | 9 a bad revision (it will try to guess HEAD) and asks for a last known-good |
10 revision. It will then binary search across this revision range by downloading, | 10 revision. It will then binary search across this revision range by downloading, |
(...skipping 14 matching lines...) Expand all Loading... | |
25 | 25 |
26 # Base URL for downloading official builds. | 26 # Base URL for downloading official builds. |
27 GOOGLE_APIS_URL = 'commondatastorage.googleapis.com' | 27 GOOGLE_APIS_URL = 'commondatastorage.googleapis.com' |
28 | 28 |
29 # The base URL for official builds. | 29 # The base URL for official builds. |
30 OFFICIAL_BASE_URL = 'http://%s/%s' % (GOOGLE_APIS_URL, GS_BUCKET_NAME) | 30 OFFICIAL_BASE_URL = 'http://%s/%s' % (GOOGLE_APIS_URL, GS_BUCKET_NAME) |
31 | 31 |
32 # URL template for viewing changelogs between revisions. | 32 # URL template for viewing changelogs between revisions. |
33 CHANGELOG_URL = ('https://chromium.googlesource.com/chromium/src/+log/%s..%s') | 33 CHANGELOG_URL = ('https://chromium.googlesource.com/chromium/src/+log/%s..%s') |
34 | 34 |
35 # GS bucket name for tip of tree android builds. | |
36 ANDROID_TOT_BUCKET_NAME = ('chrome-android-tot/bisect') | |
37 | |
35 # GS bucket name for android unsigned official builds. | 38 # GS bucket name for android unsigned official builds. |
36 ANDROID_BUCKET_NAME = 'chrome-unsigned/android-C4MPAR1' | 39 ANDROID_BUCKET_NAME = 'chrome-unsigned/android-C4MPAR1' |
37 | 40 |
38 # The base URL for android official builds. | 41 # The base URL for android official builds. |
39 ANDROID_OFFICIAL_BASE_URL = 'http://%s/%s' % (GOOGLE_APIS_URL, ANDROID_BUCKET_NA ME) | 42 ANDROID_OFFICIAL_BASE_URL = 'http://%s/%s' % (GOOGLE_APIS_URL, ANDROID_BUCKET_NA ME) |
40 | 43 |
41 # URL to convert SVN revision to git hash. | 44 # URL to convert SVN revision to git hash. |
42 CRREV_URL = ('https://cr-rev.appspot.com/_ah/api/crrev/v1/redirect/') | 45 CRREV_URL = ('https://cr-rev.appspot.com/_ah/api/crrev/v1/redirect/') |
43 | 46 |
44 # URL template for viewing changelogs between official versions. | 47 # URL template for viewing changelogs between official versions. |
(...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
134 self.is_official = is_official | 137 self.is_official = is_official |
135 self.is_asan = is_asan | 138 self.is_asan = is_asan |
136 self.build_type = 'release' | 139 self.build_type = 'release' |
137 self.flash_path = flash_path | 140 self.flash_path = flash_path |
138 # Dictionary which stores svn revision number as key and it's | 141 # Dictionary which stores svn revision number as key and it's |
139 # corresponding git hash as value. This data is populated in | 142 # corresponding git hash as value. This data is populated in |
140 # _FetchAndParse and used later in GetDownloadURL while downloading | 143 # _FetchAndParse and used later in GetDownloadURL while downloading |
141 # the build. | 144 # the build. |
142 self.githash_svn_dict = {} | 145 self.githash_svn_dict = {} |
143 self.pdf_path = pdf_path | 146 self.pdf_path = pdf_path |
144 # android_apk defaults to Chrome.apk | |
145 self.android_apk = android_apk if android_apk else 'Chrome.apk' | |
146 # The name of the ZIP file in a revision directory on the server. | 147 # The name of the ZIP file in a revision directory on the server. |
147 self.archive_name = None | 148 self.archive_name = None |
148 | 149 # Whether the build should be downloaded using gsutil. |
150 self.download_with_gsutil = False | |
149 # If the script is run from a local Chromium checkout, | 151 # If the script is run from a local Chromium checkout, |
150 # "--use-local-repo" option can be used to make the script run faster. | 152 # "--use-local-repo" option can be used to make the script run faster. |
151 # It uses "git svn find-rev <SHA1>" command to convert git hash to svn | 153 # It uses "git svn find-rev <SHA1>" command to convert git hash to svn |
152 # revision number. | 154 # revision number. |
153 self.use_local_repo = use_local_repo | 155 self.use_local_repo = use_local_repo |
154 | 156 |
155 # If the script is being used for android builds. | 157 # If the script is being used for android builds. |
156 self.android = self.platform.startswith('android') | 158 self.is_android = self.platform.startswith('android') |
159 # android_apk defaults to Chrome.apk | |
160 if self.is_android: | |
161 self.android_apk = android_apk if android_apk else 'Chrome.apk' | |
157 | 162 |
158 # Set some internal members: | 163 # Set some internal members: |
159 # _listing_platform_dir = Directory that holds revisions. Ends with a '/'. | 164 # _listing_platform_dir = Directory that holds revisions. Ends with a '/'. |
160 # _archive_extract_dir = Uncompressed directory in the archive_name file. | 165 # _archive_extract_dir = Uncompressed directory in the archive_name file. |
161 # _binary_name = The name of the executable to run. | 166 # _binary_name = The name of the executable to run. |
162 if self.platform in ('linux', 'linux64', 'linux-arm'): | 167 if self.platform in ('linux', 'linux64', 'linux-arm'): |
163 self._binary_name = 'chrome' | 168 self._binary_name = 'chrome' |
164 elif self.platform in ('mac', 'mac64'): | 169 elif self.platform in ('mac', 'mac64'): |
165 self.archive_name = 'chrome-mac.zip' | 170 self.archive_name = 'chrome-mac.zip' |
166 self._archive_extract_dir = 'chrome-mac' | 171 self._archive_extract_dir = 'chrome-mac' |
167 elif self.platform in ('win', 'win64'): | 172 elif self.platform in ('win', 'win64'): |
168 self.archive_name = 'chrome-win32.zip' | 173 self.archive_name = 'chrome-win32.zip' |
169 self._archive_extract_dir = 'chrome-win32' | 174 self._archive_extract_dir = 'chrome-win32' |
170 self._binary_name = 'chrome.exe' | 175 self._binary_name = 'chrome.exe' |
171 elif self.android: | 176 elif self.is_android: |
172 pass | 177 pass |
173 else: | 178 else: |
174 raise Exception('Invalid platform: %s' % self.platform) | 179 raise Exception('Invalid platform: %s' % self.platform) |
175 | 180 |
176 if is_official: | 181 if is_official: |
177 if self.platform == 'linux': | 182 if self.platform == 'linux': |
178 self._listing_platform_dir = 'precise32/' | 183 self._listing_platform_dir = 'precise32/' |
179 self.archive_name = 'chrome-precise32.zip' | 184 self.archive_name = 'chrome-precise32.zip' |
180 self._archive_extract_dir = 'chrome-precise32' | 185 self._archive_extract_dir = 'chrome-precise32' |
181 elif self.platform == 'linux64': | 186 elif self.platform == 'linux64': |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
216 self._listing_platform_dir = 'Linux/' | 221 self._listing_platform_dir = 'Linux/' |
217 elif self.platform == 'linux64': | 222 elif self.platform == 'linux64': |
218 self._listing_platform_dir = 'Linux_x64/' | 223 self._listing_platform_dir = 'Linux_x64/' |
219 elif self.platform == 'linux-arm': | 224 elif self.platform == 'linux-arm': |
220 self._listing_platform_dir = 'Linux_ARM_Cross-Compile/' | 225 self._listing_platform_dir = 'Linux_ARM_Cross-Compile/' |
221 elif self.platform == 'mac': | 226 elif self.platform == 'mac': |
222 self._listing_platform_dir = 'Mac/' | 227 self._listing_platform_dir = 'Mac/' |
223 self._binary_name = 'Chromium.app/Contents/MacOS/Chromium' | 228 self._binary_name = 'Chromium.app/Contents/MacOS/Chromium' |
224 elif self.platform == 'win': | 229 elif self.platform == 'win': |
225 self._listing_platform_dir = 'Win/' | 230 self._listing_platform_dir = 'Win/' |
231 elif self.platform == 'android-arm': | |
232 self.archive_name = 'bisect_android.zip' | |
233 # Need to download builds using gsutil instead of visiting url for | |
234 # authentication reasons. | |
235 self.download_with_gsutil = True | |
226 | 236 |
227 def GetASANPlatformDir(self): | 237 def GetASANPlatformDir(self): |
228 """ASAN builds are in directories like "linux-release", or have filenames | 238 """ASAN builds are in directories like "linux-release", or have filenames |
229 like "asan-win32-release-277079.zip". This aligns to our platform names | 239 like "asan-win32-release-277079.zip". This aligns to our platform names |
230 except in the case of Windows where they use "win32" instead of "win".""" | 240 except in the case of Windows where they use "win32" instead of "win".""" |
231 if self.platform == 'win': | 241 if self.platform == 'win': |
232 return 'win32' | 242 return 'win32' |
233 else: | 243 else: |
234 return self.platform | 244 return self.platform |
235 | 245 |
236 def GetListingURL(self, marker=None): | 246 def GetListingURL(self, marker=None): |
237 """Returns the URL for a directory listing, with an optional marker.""" | 247 """Returns the URL for a directory listing, with an optional marker.""" |
238 marker_param = '' | 248 marker_param = '' |
239 if marker: | 249 if marker: |
240 marker_param = '&marker=' + str(marker) | 250 marker_param = '&marker=' + str(marker) |
241 if self.is_asan: | 251 if self.is_asan: |
242 prefix = '%s-%s' % (self.GetASANPlatformDir(), self.build_type) | 252 prefix = '%s-%s' % (self.GetASANPlatformDir(), self.build_type) |
243 return self.base_url + '/?delimiter=&prefix=' + prefix + marker_param | 253 return self.base_url + '/?delimiter=&prefix=' + prefix + marker_param |
244 else: | 254 else: |
245 return (self.base_url + '/?delimiter=/&prefix=' + | 255 return (self.base_url + '/?delimiter=/&prefix=' + |
246 self._listing_platform_dir + marker_param) | 256 self._listing_platform_dir + marker_param) |
247 | 257 |
248 def GetDownloadURL(self, revision): | 258 def GetDownloadURL(self, revision): |
249 """Gets the download URL for a build archive of a specific revision.""" | 259 """Gets the download URL for a build archive of a specific revision.""" |
250 if self.is_asan: | 260 if self.is_asan: |
251 return '%s/%s-%s/%s-%d.zip' % ( | 261 return '%s/%s-%s/%s-%d.zip' % ( |
252 ASAN_BASE_URL, self.GetASANPlatformDir(), self.build_type, | 262 ASAN_BASE_URL, self.GetASANPlatformDir(), self.build_type, |
253 self.GetASANBaseName(), revision) | 263 self.GetASANBaseName(), revision) |
254 if self.is_official: | 264 if self.is_official: |
255 if self.android: | 265 if self.is_android: |
256 official_base_url = ANDROID_OFFICIAL_BASE_URL | 266 official_base_url = ANDROID_OFFICIAL_BASE_URL |
257 else: | 267 else: |
258 official_base_url = OFFICIAL_BASE_URL | 268 official_base_url = OFFICIAL_BASE_URL |
259 return '%s/%s/%s%s' % ( | 269 return '%s/%s/%s%s' % ( |
260 official_base_url, revision, self._listing_platform_dir, | 270 official_base_url, revision, self._listing_platform_dir, |
261 self.archive_name) | 271 self.archive_name) |
262 else: | 272 else: |
263 if str(revision) in self.githash_svn_dict: | 273 if self.is_android: |
264 revision = self.githash_svn_dict[str(revision)] | 274 # These files need to be downloaded through gsutil. |
265 return '%s/%s%s/%s' % (self.base_url, self._listing_platform_dir, | 275 return ('gs://%s/%s/%s' % (ANDROID_TOT_BUCKET_NAME, revision, |
266 revision, self.archive_name) | 276 self.archive_name)) |
277 else: | |
278 if str(revision) in self.githash_svn_dict: | |
279 revision = self.githash_svn_dict[str(revision)] | |
280 return '%s/%s%s/%s' % (self.base_url, self._listing_platform_dir, | |
281 revision, self.archive_name) | |
267 | 282 |
268 def GetLastChangeURL(self): | 283 def GetLastChangeURL(self): |
269 """Returns a URL to the LAST_CHANGE file.""" | 284 """Returns a URL to the LAST_CHANGE file.""" |
270 return self.base_url + '/' + self._listing_platform_dir + 'LAST_CHANGE' | 285 return self.base_url + '/' + self._listing_platform_dir + 'LAST_CHANGE' |
271 | 286 |
272 def GetASANBaseName(self): | 287 def GetASANBaseName(self): |
273 """Returns the base name of the ASAN zip file.""" | 288 """Returns the base name of the ASAN zip file.""" |
274 if 'linux' in self.platform: | 289 if 'linux' in self.platform: |
275 return 'asan-symbolized-%s-%s' % (self.GetASANPlatformDir(), | 290 return 'asan-symbolized-%s-%s' % (self.GetASANPlatformDir(), |
276 self.build_type) | 291 self.build_type) |
(...skipping 173 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
450 self.good_revision = FixChromiumRevForBlink(revlist, | 465 self.good_revision = FixChromiumRevForBlink(revlist, |
451 revlist_all, | 466 revlist_all, |
452 self, | 467 self, |
453 self.good_revision) | 468 self.good_revision) |
454 self.bad_revision = FixChromiumRevForBlink(revlist, | 469 self.bad_revision = FixChromiumRevForBlink(revlist, |
455 revlist_all, | 470 revlist_all, |
456 self, | 471 self, |
457 self.bad_revision) | 472 self.bad_revision) |
458 return revlist | 473 return revlist |
459 | 474 |
475 def _GetHashToNumberDict(self): | |
476 """Gets the mapping of git hashes to git numbers from Google Storage.""" | |
477 gs_file = 'gs://%s/gitnumbers_dict.json' % ANDROID_TOT_BUCKET_NAME | |
478 local_file = 'gitnumbers_dict.json' | |
479 GsutilDownload(gs_file, local_file) | |
480 json_data = open(local_file).read() | |
481 os.remove(local_file) | |
482 return json.loads(json_data) | |
483 | |
484 def GetAndroidToTRevisions(self): | |
485 """Gets the ordered list of revisions between self.good_revision and | |
486 self.bad_revision from the Android tip of tree GS bucket. | |
487 """ | |
488 # Dictionary that maps git hashes to git numbers. The git numbers | |
489 # let us order the revisions. | |
490 hash_to_num = self._GetHashToNumberDict() | |
491 try: | |
492 good_rev_num = hash_to_num[self.good_revision] | |
493 bad_rev_num = hash_to_num[self.bad_revision] | |
494 except KeyError: | |
495 exit('Error. Make sure the good and bad revisions are valid git hashes.') | |
496 | |
497 # List of all builds by their git hashes in the storage bucket. | |
498 hash_list = GsutilList(ANDROID_TOT_BUCKET_NAME) | |
499 | |
500 # Get list of builds that we want to bisect over. | |
501 final_list = [] | |
502 minnum = min(good_rev_num, bad_rev_num) | |
503 maxnum = max(good_rev_num, bad_rev_num) | |
504 for githash in hash_list: | |
505 if len(githash) != 40: | |
506 continue | |
507 gitnumber = hash_to_num[githash] | |
508 if minnum < gitnumber < maxnum: | |
509 final_list.append(githash) | |
510 return sorted(final_list, key=lambda h: hash_to_num[h]) | |
511 | |
460 def GetOfficialBuildsList(self): | 512 def GetOfficialBuildsList(self): |
461 """Gets the list of official build numbers between self.good_revision and | 513 """Gets the list of official build numbers between self.good_revision and |
462 self.bad_revision.""" | 514 self.bad_revision.""" |
463 | 515 |
464 def CheckDepotToolsInPath(): | |
465 delimiter = ';' if sys.platform.startswith('win') else ':' | |
466 path_list = os.environ['PATH'].split(delimiter) | |
467 for path in path_list: | |
468 if path.rstrip(os.path.sep).endswith('depot_tools'): | |
469 return path | |
470 return None | |
471 | |
472 def RunGsutilCommand(args): | |
473 gsutil_path = CheckDepotToolsInPath() | |
474 if gsutil_path is None: | |
475 print ('Follow the instructions in this document ' | |
476 'http://dev.chromium.org/developers/how-tos/install-depot-tools' | |
477 ' to install depot_tools and then try again.') | |
478 sys.exit(1) | |
479 gsutil_path = os.path.join(gsutil_path, 'third_party', 'gsutil', 'gsutil') | |
480 gsutil = subprocess.Popen([sys.executable, gsutil_path] + args, | |
481 stdout=subprocess.PIPE, stderr=subprocess.PIPE, | |
482 env=None) | |
483 stdout, stderr = gsutil.communicate() | |
484 if gsutil.returncode: | |
485 if (re.findall(r'status[ |=]40[1|3]', stderr) or | |
486 stderr.startswith(CREDENTIAL_ERROR_MESSAGE)): | |
487 print ('Follow these steps to configure your credentials and try' | |
488 ' running the bisect-builds.py again.:\n' | |
489 ' 1. Run "python %s config" and follow its instructions.\n' | |
490 ' 2. If you have a @google.com account, use that account.\n' | |
491 ' 3. For the project-id, just enter 0.' % gsutil_path) | |
492 sys.exit(1) | |
493 else: | |
494 raise Exception('Error running the gsutil command: %s' % stderr) | |
495 return stdout | |
496 | |
497 def GsutilList(bucket): | |
498 query = 'gs://%s/' % bucket | |
499 stdout = RunGsutilCommand(['ls', query]) | |
500 return [url[len(query):].strip('/') for url in stdout.splitlines()] | |
501 | |
502 # Download the revlist and filter for just the range between good and bad. | 516 # Download the revlist and filter for just the range between good and bad. |
503 minrev = min(self.good_revision, self.bad_revision) | 517 minrev = min(self.good_revision, self.bad_revision) |
504 maxrev = max(self.good_revision, self.bad_revision) | 518 maxrev = max(self.good_revision, self.bad_revision) |
505 if self.android: | 519 if self.is_android: |
506 gs_bucket_name = ANDROID_BUCKET_NAME | 520 gs_bucket_name = ANDROID_BUCKET_NAME |
507 else: | 521 else: |
508 gs_bucket_name = GS_BUCKET_NAME | 522 gs_bucket_name = GS_BUCKET_NAME |
509 build_numbers = GsutilList(gs_bucket_name) | 523 build_numbers = GsutilList(gs_bucket_name) |
510 revision_re = re.compile(r'(\d\d\.\d\.\d{4}\.\d+)') | 524 revision_re = re.compile(r'(\d\d\.\d\.\d{4}\.\d+)') |
511 build_numbers = filter(lambda b: revision_re.search(b), build_numbers) | 525 build_numbers = filter(lambda b: revision_re.search(b), build_numbers) |
512 final_list = [] | 526 final_list = [] |
513 parsed_build_numbers = [LooseVersion(x) for x in build_numbers] | 527 parsed_build_numbers = [LooseVersion(x) for x in build_numbers] |
514 connection = httplib.HTTPConnection(GOOGLE_APIS_URL) | 528 connection = httplib.HTTPConnection(GOOGLE_APIS_URL) |
515 for build_number in sorted(parsed_build_numbers): | 529 for build_number in sorted(parsed_build_numbers): |
516 if build_number > maxrev: | 530 if build_number > maxrev: |
517 break | 531 break |
518 if build_number < minrev: | 532 if build_number < minrev: |
519 continue | 533 continue |
520 path = ('/' + gs_bucket_name + '/' + str(build_number) + '/' + | 534 path = ('/' + gs_bucket_name + '/' + str(build_number) + '/' + |
521 self._listing_platform_dir + self.archive_name) | 535 self._listing_platform_dir + self.archive_name) |
522 connection.request('HEAD', path) | 536 connection.request('HEAD', path) |
523 response = connection.getresponse() | 537 response = connection.getresponse() |
524 if response.status == 200: | 538 if response.status == 200: |
525 final_list.append(str(build_number)) | 539 final_list.append(str(build_number)) |
526 response.read() | 540 response.read() |
527 connection.close() | 541 connection.close() |
528 return final_list | 542 return final_list |
529 | 543 |
Robert Sesek
2015/02/04 17:27:54
nit: add another blank line
mikecase (-- gone --)
2015/02/04 22:50:44
Done.
| |
544 def CheckDepotToolsInPath(): | |
545 delimiter = ';' if sys.platform.startswith('win') else ':' | |
Robert Sesek
2015/02/04 17:27:54
nit: indent is wrong
mikecase (-- gone --)
2015/02/04 22:50:44
Good eye.
| |
546 path_list = os.environ['PATH'].split(delimiter) | |
547 for path in path_list: | |
548 if path.rstrip(os.path.sep).endswith('depot_tools'): | |
549 return path | |
550 return None | |
551 | |
552 | |
553 def RunGsutilCommand(args): | |
554 gsutil_path = CheckDepotToolsInPath() | |
555 if gsutil_path is None: | |
556 print ('Follow the instructions in this document ' | |
557 'http://dev.chromium.org/developers/how-tos/install-depot-tools' | |
558 ' to install depot_tools and then try again.') | |
559 sys.exit(1) | |
560 gsutil_path = os.path.join(gsutil_path, 'third_party', 'gsutil', 'gsutil') | |
561 gsutil = subprocess.Popen([sys.executable, gsutil_path] + args, | |
562 stdout=subprocess.PIPE, stderr=subprocess.PIPE, | |
563 env=None) | |
564 stdout, stderr = gsutil.communicate() | |
565 if gsutil.returncode: | |
566 if (re.findall(r'status[ |=]40[1|3]', stderr) or | |
567 stderr.startswith(CREDENTIAL_ERROR_MESSAGE)): | |
568 print ('Follow these steps to configure your credentials and try' | |
569 ' running the bisect-builds.py again.:\n' | |
570 ' 1. Run "python %s config" and follow its instructions.\n' | |
571 ' 2. If you have a @google.com account, use that account.\n' | |
572 ' 3. For the project-id, just enter 0.' % gsutil_path) | |
573 sys.exit(1) | |
574 else: | |
575 raise Exception('Error running the gsutil command: %s' % stderr) | |
576 return stdout | |
577 | |
578 | |
579 def GsutilList(bucket): | |
580 query = 'gs://%s/' % bucket | |
581 stdout = RunGsutilCommand(['ls', query]) | |
582 return [url[len(query):].strip('/') for url in stdout.splitlines()] | |
583 | |
584 | |
585 def GsutilDownload(gs_download_url, filename): | |
586 RunGsutilCommand(['cp', gs_download_url, filename]) | |
587 | |
588 | |
530 def UnzipFilenameToDir(filename, directory): | 589 def UnzipFilenameToDir(filename, directory): |
531 """Unzip |filename| to |directory|.""" | 590 """Unzip |filename| to |directory|.""" |
532 cwd = os.getcwd() | 591 cwd = os.getcwd() |
533 if not os.path.isabs(filename): | 592 if not os.path.isabs(filename): |
534 filename = os.path.join(cwd, filename) | 593 filename = os.path.join(cwd, filename) |
535 zf = zipfile.ZipFile(filename) | 594 zf = zipfile.ZipFile(filename) |
536 # Make base. | 595 # Make base. |
537 if not os.path.isdir(directory): | 596 if not os.path.isdir(directory): |
538 os.mkdir(directory) | 597 os.mkdir(directory) |
539 os.chdir(directory) | 598 os.chdir(directory) |
540 # Extract files. | 599 # Extract files. |
541 for info in zf.infolist(): | 600 for info in zf.infolist(): |
542 name = info.filename | 601 name = info.filename |
543 if name.endswith('/'): # dir | 602 if name.endswith('/'): # dir |
544 if not os.path.isdir(name): | 603 if not os.path.isdir(name): |
545 os.makedirs(name) | 604 os.makedirs(name) |
546 else: # file | 605 else: # file |
547 directory = os.path.dirname(name) | 606 directory = os.path.dirname(name) |
548 if not os.path.isdir(directory): | 607 if directory and not os.path.isdir(directory): |
549 os.makedirs(directory) | 608 os.makedirs(directory) |
550 out = open(name, 'wb') | 609 out = open(name, 'wb') |
551 out.write(zf.read(name)) | 610 out.write(zf.read(name)) |
552 out.close() | 611 out.close() |
553 # Set permissions. Permission info in external_attr is shifted 16 bits. | 612 # Set permissions. Permission info in external_attr is shifted 16 bits. |
554 os.chmod(name, info.external_attr >> 16L) | 613 os.chmod(name, info.external_attr >> 16L) |
555 os.chdir(cwd) | 614 os.chdir(cwd) |
556 | 615 |
557 | 616 |
558 def FetchRevision(context, rev, filename, quit_event=None, progress_event=None): | 617 def FetchRevision(context, rev, filename, quit_event=None, progress_event=None): |
(...skipping 14 matching lines...) Expand all Loading... | |
573 size = blocknum * blocksize | 632 size = blocknum * blocksize |
574 if totalsize == -1: # Total size not known. | 633 if totalsize == -1: # Total size not known. |
575 progress = 'Received %d bytes' % size | 634 progress = 'Received %d bytes' % size |
576 else: | 635 else: |
577 size = min(totalsize, size) | 636 size = min(totalsize, size) |
578 progress = 'Received %d of %d bytes, %.2f%%' % ( | 637 progress = 'Received %d of %d bytes, %.2f%%' % ( |
579 size, totalsize, 100.0 * size / totalsize) | 638 size, totalsize, 100.0 * size / totalsize) |
580 # Send a \r to let all progress messages use just one line of output. | 639 # Send a \r to let all progress messages use just one line of output. |
581 sys.stdout.write('\r' + progress) | 640 sys.stdout.write('\r' + progress) |
582 sys.stdout.flush() | 641 sys.stdout.flush() |
583 | |
584 download_url = context.GetDownloadURL(rev) | 642 download_url = context.GetDownloadURL(rev) |
585 try: | 643 try: |
586 urllib.urlretrieve(download_url, filename, ReportHook) | 644 if context.download_with_gsutil: |
645 GsutilDownload(download_url, filename) | |
646 else: | |
647 urllib.urlretrieve(download_url, filename, ReportHook) | |
587 if progress_event and progress_event.isSet(): | 648 if progress_event and progress_event.isSet(): |
588 print | 649 print |
650 | |
589 except RuntimeError: | 651 except RuntimeError: |
590 pass | 652 pass |
591 | 653 |
592 | 654 |
655 def RunADBCommand(args): | |
656 cmd = ['adb'] + args | |
657 adb = subprocess.Popen(['adb'] + args, | |
658 stdout=subprocess.PIPE, stderr=subprocess.PIPE, | |
659 env=None) | |
660 stdout, stderr = adb.communicate() | |
661 return stdout | |
662 | |
663 | |
593 def IsADBInstalled(): | 664 def IsADBInstalled(): |
594 """Checks if ADB is in the environment path.""" | 665 """Checks if ADB is in the environment path.""" |
595 try: | 666 try: |
596 adb_output = subprocess.check_output(['adb', 'version']) | 667 adb_output = RunADBCommand(['version']) |
597 return ('Android Debug Bridge' in adb_output) | 668 return ('Android Debug Bridge' in adb_output) |
598 except OSError: | 669 except OSError: |
599 return False | 670 return False |
600 | 671 |
601 | 672 |
602 def GetAndroidDeviceList(): | 673 def GetAndroidDeviceList(): |
603 """Returns the list of Android devices attached to the host machine.""" | 674 """Returns the list of Android devices attached to the host machine.""" |
604 lines = subprocess.check_output(['adb', 'devices']).split('\n')[1:] | 675 lines = RunADBCommand(['devices']).split('\n')[1:] |
605 devices = [] | 676 devices = [] |
606 for line in lines: | 677 for line in lines: |
607 m = re.match('^(.*?)\s+device$', line) | 678 m = re.match('^(.*?)\s+device$', line) |
608 if not m: | 679 if not m: |
609 continue | 680 continue |
610 devices.append(m.group(1)) | 681 devices.append(m.group(1)) |
611 return devices | 682 return devices |
612 | 683 |
613 | 684 |
614 def RunAndroidRevision(context, revision, apk_file): | 685 def RunAndroidRevision(context, revision, zip_file): |
615 """Given a Chrome apk, install it on a local device, and launch Chrome.""" | 686 """Given a Chrome apk, install it on a local device, and launch Chrome.""" |
616 devices = GetAndroidDeviceList() | 687 devices = GetAndroidDeviceList() |
617 if len(devices) is not 1: | 688 if len(devices) is not 1: |
618 sys.exit('Please have 1 Android device plugged in. %d devices found' | 689 sys.exit('Please have 1 Android device plugged in. %d devices found' |
619 % len(devices)) | 690 % len(devices)) |
620 | 691 |
621 devnull = open(os.devnull, 'w') | 692 if context.is_official: |
693 # Downloaded file is just the .apk in this case. | |
694 apk_file = zip_file | |
695 else: | |
696 cwd = os.getcwd() | |
697 tempdir = tempfile.mkdtemp(prefix='bisect_tmp') | |
698 UnzipFilenameToDir(zip_file, tempdir) | |
699 os.chdir(tempdir) | |
700 apk_file = context.android_apk | |
701 | |
622 package_name = ANDROID_CHROME_PACKAGE_NAME[context.android_apk] | 702 package_name = ANDROID_CHROME_PACKAGE_NAME[context.android_apk] |
623 | 703 print 'Installing...' |
624 print 'Installing new Chrome version...' | 704 RunADBCommand(['install', '-r', '-d', apk_file]) |
625 subprocess.call(['adb', 'install', '-r', '-d', apk_file], | |
626 stdout=devnull, stderr=devnull) | |
627 | 705 |
628 print 'Launching Chrome...\n' | 706 print 'Launching Chrome...\n' |
629 subprocess.call(['adb', 'shell', 'am', 'start', '-a', | 707 RunADBCommand(['shell', 'am', 'start', '-a', |
630 'android.intent.action.VIEW', '-n', package_name + | 708 'android.intent.action.VIEW', '-n', package_name + |
631 '/com.google.android.apps.chrome.Main'], stdout=devnull, stderr=devnull) | 709 '/com.google.android.apps.chrome.Main']) |
632 | 710 |
633 | 711 |
634 def RunRevision(context, revision, zip_file, profile, num_runs, command, args): | 712 def RunRevision(context, revision, zip_file, profile, num_runs, command, args): |
635 """Given a zipped revision, unzip it and run the test.""" | 713 """Given a zipped revision, unzip it and run the test.""" |
636 print 'Trying revision %s...' % str(revision) | 714 print 'Trying revision %s...' % str(revision) |
637 | 715 |
638 if context.android: | 716 if context.is_android: |
639 RunAndroidRevision(context, revision, zip_file) | 717 RunAndroidRevision(context, revision, zip_file) |
640 # TODO(mikecase): Support running command to auto-bisect Android. | 718 # TODO(mikecase): Support running command to auto-bisect Android. |
641 return (None, None, None) | 719 return (None, None, None) |
642 | 720 |
643 # Create a temp directory and unzip the revision into it. | 721 # Create a temp directory and unzip the revision into it. |
644 cwd = os.getcwd() | 722 cwd = os.getcwd() |
645 tempdir = tempfile.mkdtemp(prefix='bisect_tmp') | 723 tempdir = tempfile.mkdtemp(prefix='bisect_tmp') |
646 UnzipFilenameToDir(zip_file, tempdir) | 724 UnzipFilenameToDir(zip_file, tempdir) |
647 os.chdir(tempdir) | 725 os.chdir(tempdir) |
648 | 726 |
(...skipping 26 matching lines...) Expand all Loading... | |
675 replace('%s', ' '.join(testargs))) | 753 replace('%s', ' '.join(testargs))) |
676 | 754 |
677 results = [] | 755 results = [] |
678 for _ in range(num_runs): | 756 for _ in range(num_runs): |
679 subproc = subprocess.Popen(runcommand, | 757 subproc = subprocess.Popen(runcommand, |
680 bufsize=-1, | 758 bufsize=-1, |
681 stdout=subprocess.PIPE, | 759 stdout=subprocess.PIPE, |
682 stderr=subprocess.PIPE) | 760 stderr=subprocess.PIPE) |
683 (stdout, stderr) = subproc.communicate() | 761 (stdout, stderr) = subproc.communicate() |
684 results.append((subproc.returncode, stdout, stderr)) | 762 results.append((subproc.returncode, stdout, stderr)) |
685 | |
686 os.chdir(cwd) | 763 os.chdir(cwd) |
687 try: | 764 try: |
688 shutil.rmtree(tempdir, True) | 765 shutil.rmtree(tempdir, True) |
689 except Exception: | 766 except Exception: |
690 pass | 767 pass |
691 | 768 |
692 for (returncode, stdout, stderr) in results: | 769 for (returncode, stdout, stderr) in results: |
693 if returncode: | 770 if returncode: |
694 return (returncode, stdout, stderr) | 771 return (returncode, stdout, stderr) |
695 return results[0] | 772 return results[0] |
(...skipping 117 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
813 | 890 |
814 print 'Downloading list of known revisions...', | 891 print 'Downloading list of known revisions...', |
815 if not context.use_local_repo and not context.is_official: | 892 if not context.use_local_repo and not context.is_official: |
816 print '(use --use-local-repo for speed if you have a local checkout)' | 893 print '(use --use-local-repo for speed if you have a local checkout)' |
817 else: | 894 else: |
818 print | 895 print |
819 _GetDownloadPath = lambda rev: os.path.join(cwd, | 896 _GetDownloadPath = lambda rev: os.path.join(cwd, |
820 '%s-%s' % (str(rev), context.archive_name)) | 897 '%s-%s' % (str(rev), context.archive_name)) |
821 if context.is_official: | 898 if context.is_official: |
822 revlist = context.GetOfficialBuildsList() | 899 revlist = context.GetOfficialBuildsList() |
900 elif context.is_android: # Android non-official | |
901 revlist = context.GetAndroidToTRevisions() | |
823 else: | 902 else: |
824 revlist = context.GetRevList() | 903 revlist = context.GetRevList() |
825 | 904 |
826 # Get a list of revisions to bisect across. | 905 # Get a list of revisions to bisect across. |
827 if len(revlist) < 2: # Don't have enough builds to bisect. | 906 if len(revlist) < 2: # Don't have enough builds to bisect. |
828 msg = 'We don\'t have enough builds to bisect. revlist: %s' % revlist | 907 msg = 'We don\'t have enough builds to bisect. revlist: %s' % revlist |
829 raise RuntimeError(msg) | 908 raise RuntimeError(msg) |
830 | 909 |
831 # Figure out our bookends and first pivot point; fetch the pivot revision. | 910 # Figure out our bookends and first pivot point; fetch the pivot revision. |
832 minrev = 0 | 911 minrev = 0 |
(...skipping 362 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1195 elif opts.blink: | 1274 elif opts.blink: |
1196 base_url = WEBKIT_BASE_URL | 1275 base_url = WEBKIT_BASE_URL |
1197 else: | 1276 else: |
1198 base_url = CHROMIUM_BASE_URL | 1277 base_url = CHROMIUM_BASE_URL |
1199 | 1278 |
1200 # Create the context. Initialize 0 for the revisions as they are set below. | 1279 # Create the context. Initialize 0 for the revisions as they are set below. |
1201 context = PathContext(base_url, opts.archive, opts.good, opts.bad, | 1280 context = PathContext(base_url, opts.archive, opts.good, opts.bad, |
1202 opts.official_builds, opts.asan, opts.use_local_repo, | 1281 opts.official_builds, opts.asan, opts.use_local_repo, |
1203 opts.flash_path, opts.pdf_path, opts.apk) | 1282 opts.flash_path, opts.pdf_path, opts.apk) |
1204 | 1283 |
1205 # TODO(mikecase): Add support to bisect on nonofficial builds for Android. | 1284 if context.is_android and not opts.official_builds: |
1206 if context.android and not opts.official_builds: | 1285 if(context.platform != 'android-arm' or |
Robert Sesek
2015/02/04 17:27:54
nit: space before (
mikecase (-- gone --)
2015/02/04 22:50:44
Done.
| |
1207 sys.exit('Can only bisect on official builds for Android.') | 1286 context.android_apk != 'Chrome.apk'): |
1287 sys.exit('For non-official builds, can only bisect' | |
1288 ' Chrome.apk arm builds.') | |
1208 | 1289 |
1209 # If bisecting Android, we make sure we have ADB setup. | 1290 # If bisecting Android, we make sure we have ADB setup. |
1210 if context.android: | 1291 if context.is_android: |
1211 if opts.adb_path: | 1292 if opts.adb_path: |
1212 os.environ['PATH'] = '%s:%s' % (os.path.dirname(opts.adb_path), | 1293 os.environ['PATH'] = '%s:%s' % (os.path.dirname(opts.adb_path), |
1213 os.environ['PATH']) | 1294 os.environ['PATH']) |
1214 if not IsADBInstalled(): | 1295 if not IsADBInstalled(): |
1215 sys.exit('Please have "adb" in PATH or use adb_path command line option' | 1296 sys.exit('Please have "adb" in PATH or use adb_path command line option' |
1216 'to bisect Android builds.') | 1297 'to bisect Android builds.') |
1217 | 1298 |
1218 # Pick a starting point, try to get HEAD for this. | 1299 # Pick a starting point, try to get HEAD for this. |
1219 if not opts.bad: | 1300 if not opts.bad: |
1220 context.bad_revision = '999.0.0.0' | 1301 context.bad_revision = '999.0.0.0' |
1221 context.bad_revision = GetChromiumRevision( | 1302 context.bad_revision = GetChromiumRevision( |
1222 context, context.GetLastChangeURL()) | 1303 context, context.GetLastChangeURL()) |
1223 | 1304 |
1224 # Find out when we were good. | 1305 # Find out when we were good. |
1225 if not opts.good: | 1306 if not opts.good: |
1226 context.good_revision = '0.0.0.0' if opts.official_builds else 0 | 1307 context.good_revision = '0.0.0.0' if opts.official_builds else 0 |
1227 | 1308 |
1228 if opts.flash_path: | 1309 if opts.flash_path: |
1229 msg = 'Could not find Flash binary at %s' % opts.flash_path | 1310 msg = 'Could not find Flash binary at %s' % opts.flash_path |
1230 assert os.path.exists(opts.flash_path), msg | 1311 assert os.path.exists(opts.flash_path), msg |
1231 | 1312 |
1232 if opts.pdf_path: | 1313 if opts.pdf_path: |
1233 msg = 'Could not find PDF binary at %s' % opts.pdf_path | 1314 msg = 'Could not find PDF binary at %s' % opts.pdf_path |
1234 assert os.path.exists(opts.pdf_path), msg | 1315 assert os.path.exists(opts.pdf_path), msg |
1235 | 1316 |
1236 if opts.official_builds: | 1317 if opts.official_builds: |
1237 context.good_revision = LooseVersion(context.good_revision) | 1318 context.good_revision = LooseVersion(context.good_revision) |
1238 context.bad_revision = LooseVersion(context.bad_revision) | 1319 context.bad_revision = LooseVersion(context.bad_revision) |
1320 elif context.is_android: | |
1321 # Revisions are git hashes and should be left as strings. | |
1322 pass | |
1239 else: | 1323 else: |
1240 context.good_revision = int(context.good_revision) | 1324 context.good_revision = int(context.good_revision) |
1241 context.bad_revision = int(context.bad_revision) | 1325 context.bad_revision = int(context.bad_revision) |
1242 | 1326 |
1243 if opts.times < 1: | 1327 if opts.times < 1: |
1244 print('Number of times to run (%d) must be greater than or equal to 1.' % | 1328 print('Number of times to run (%d) must be greater than or equal to 1.' % |
1245 opts.times) | 1329 opts.times) |
1246 parser.print_help() | 1330 parser.print_help() |
1247 return 1 | 1331 return 1 |
1248 | 1332 |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1294 | 1378 |
1295 print 'CHANGELOG URL:' | 1379 print 'CHANGELOG URL:' |
1296 if opts.official_builds: | 1380 if opts.official_builds: |
1297 print OFFICIAL_CHANGELOG_URL % (min_chromium_rev, max_chromium_rev) | 1381 print OFFICIAL_CHANGELOG_URL % (min_chromium_rev, max_chromium_rev) |
1298 else: | 1382 else: |
1299 PrintChangeLog(min_chromium_rev, max_chromium_rev) | 1383 PrintChangeLog(min_chromium_rev, max_chromium_rev) |
1300 | 1384 |
1301 | 1385 |
1302 if __name__ == '__main__': | 1386 if __name__ == '__main__': |
1303 sys.exit(main()) | 1387 sys.exit(main()) |
OLD | NEW |