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 90 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
135 self.is_official = is_official | 138 self.is_official = is_official |
136 self.is_asan = is_asan | 139 self.is_asan = is_asan |
137 self.build_type = 'release' | 140 self.build_type = 'release' |
138 self.flash_path = flash_path | 141 self.flash_path = flash_path |
139 # Dictionary which stores svn revision number as key and it's | 142 # Dictionary which stores svn revision number as key and it's |
140 # corresponding git hash as value. This data is populated in | 143 # corresponding git hash as value. This data is populated in |
141 # _FetchAndParse and used later in GetDownloadURL while downloading | 144 # _FetchAndParse and used later in GetDownloadURL while downloading |
142 # the build. | 145 # the build. |
143 self.githash_svn_dict = {} | 146 self.githash_svn_dict = {} |
144 self.pdf_path = pdf_path | 147 self.pdf_path = pdf_path |
145 # android_apk defaults to Chrome.apk | |
146 self.android_apk = android_apk if android_apk else 'Chrome.apk' | |
147 # The name of the ZIP file in a revision directory on the server. | 148 # The name of the ZIP file in a revision directory on the server. |
148 self.archive_name = None | 149 self.archive_name = None |
149 | 150 |
150 # Whether to cache and use the list of known revisions in a local file to | 151 # Whether to cache and use the list of known revisions in a local file to |
151 # speed up the initialization of the script at the next run. | 152 # speed up the initialization of the script at the next run. |
152 self.use_local_cache = use_local_cache | 153 self.use_local_cache = use_local_cache |
153 | 154 |
154 # Locate the local checkout to speed up the script by using locally stored | 155 # Locate the local checkout to speed up the script by using locally stored |
155 # metadata. | 156 # metadata. |
156 abs_file_path = os.path.abspath(os.path.realpath(__file__)) | 157 abs_file_path = os.path.abspath(os.path.realpath(__file__)) |
157 local_src_path = os.path.join(os.path.dirname(abs_file_path), '..') | 158 local_src_path = os.path.join(os.path.dirname(abs_file_path), '..') |
158 if abs_file_path.endswith(os.path.join('tools', 'bisect-builds.py')) and\ | 159 if abs_file_path.endswith(os.path.join('tools', 'bisect-builds.py')) and\ |
159 os.path.exists(os.path.join(local_src_path, '.git')): | 160 os.path.exists(os.path.join(local_src_path, '.git')): |
160 self.local_src_path = os.path.normpath(local_src_path) | 161 self.local_src_path = os.path.normpath(local_src_path) |
161 else: | 162 else: |
162 self.local_src_path = None | 163 self.local_src_path = None |
163 | 164 |
| 165 # Whether the build should be downloaded using gsutil. |
| 166 self.download_with_gsutil = False |
| 167 |
164 # If the script is being used for android builds. | 168 # If the script is being used for android builds. |
165 self.android = self.platform.startswith('android') | 169 self.is_android = self.platform.startswith('android') |
| 170 # android_apk defaults to Chrome.apk |
| 171 if self.is_android: |
| 172 self.android_apk = android_apk if android_apk else 'Chrome.apk' |
166 | 173 |
167 # Set some internal members: | 174 # Set some internal members: |
168 # _listing_platform_dir = Directory that holds revisions. Ends with a '/'. | 175 # _listing_platform_dir = Directory that holds revisions. Ends with a '/'. |
169 # _archive_extract_dir = Uncompressed directory in the archive_name file. | 176 # _archive_extract_dir = Uncompressed directory in the archive_name file. |
170 # _binary_name = The name of the executable to run. | 177 # _binary_name = The name of the executable to run. |
171 if self.platform in ('linux', 'linux64', 'linux-arm', 'chromeos'): | 178 if self.platform in ('linux', 'linux64', 'linux-arm', 'chromeos'): |
172 self._binary_name = 'chrome' | 179 self._binary_name = 'chrome' |
173 elif self.platform in ('mac', 'mac64'): | 180 elif self.platform in ('mac', 'mac64'): |
174 self.archive_name = 'chrome-mac.zip' | 181 self.archive_name = 'chrome-mac.zip' |
175 self._archive_extract_dir = 'chrome-mac' | 182 self._archive_extract_dir = 'chrome-mac' |
176 elif self.platform in ('win', 'win64'): | 183 elif self.platform in ('win', 'win64'): |
177 self.archive_name = 'chrome-win32.zip' | 184 self.archive_name = 'chrome-win32.zip' |
178 self._archive_extract_dir = 'chrome-win32' | 185 self._archive_extract_dir = 'chrome-win32' |
179 self._binary_name = 'chrome.exe' | 186 self._binary_name = 'chrome.exe' |
180 elif self.android: | 187 elif self.is_android: |
181 pass | 188 pass |
182 else: | 189 else: |
183 raise Exception('Invalid platform: %s' % self.platform) | 190 raise Exception('Invalid platform: %s' % self.platform) |
184 | 191 |
185 if is_official: | 192 if is_official: |
186 if self.platform == 'linux': | 193 if self.platform == 'linux': |
187 self._listing_platform_dir = 'precise32/' | 194 self._listing_platform_dir = 'precise32/' |
188 self.archive_name = 'chrome-precise32.zip' | 195 self.archive_name = 'chrome-precise32.zip' |
189 self._archive_extract_dir = 'chrome-precise32' | 196 self._archive_extract_dir = 'chrome-precise32' |
190 elif self.platform == 'linux64': | 197 elif self.platform == 'linux64': |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
227 self._listing_platform_dir = 'Linux_x64/' | 234 self._listing_platform_dir = 'Linux_x64/' |
228 elif self.platform == 'linux-arm': | 235 elif self.platform == 'linux-arm': |
229 self._listing_platform_dir = 'Linux_ARM_Cross-Compile/' | 236 self._listing_platform_dir = 'Linux_ARM_Cross-Compile/' |
230 elif self.platform == 'chromeos': | 237 elif self.platform == 'chromeos': |
231 self._listing_platform_dir = 'Linux_ChromiumOS_Full/' | 238 self._listing_platform_dir = 'Linux_ChromiumOS_Full/' |
232 elif self.platform == 'mac': | 239 elif self.platform == 'mac': |
233 self._listing_platform_dir = 'Mac/' | 240 self._listing_platform_dir = 'Mac/' |
234 self._binary_name = 'Chromium.app/Contents/MacOS/Chromium' | 241 self._binary_name = 'Chromium.app/Contents/MacOS/Chromium' |
235 elif self.platform == 'win': | 242 elif self.platform == 'win': |
236 self._listing_platform_dir = 'Win/' | 243 self._listing_platform_dir = 'Win/' |
| 244 elif self.platform == 'android-arm': |
| 245 self.archive_name = 'bisect_android.zip' |
| 246 # Need to download builds using gsutil instead of visiting url for |
| 247 # authentication reasons. |
| 248 self.download_with_gsutil = True |
237 | 249 |
238 def GetASANPlatformDir(self): | 250 def GetASANPlatformDir(self): |
239 """ASAN builds are in directories like "linux-release", or have filenames | 251 """ASAN builds are in directories like "linux-release", or have filenames |
240 like "asan-win32-release-277079.zip". This aligns to our platform names | 252 like "asan-win32-release-277079.zip". This aligns to our platform names |
241 except in the case of Windows where they use "win32" instead of "win".""" | 253 except in the case of Windows where they use "win32" instead of "win".""" |
242 if self.platform == 'win': | 254 if self.platform == 'win': |
243 return 'win32' | 255 return 'win32' |
244 else: | 256 else: |
245 return self.platform | 257 return self.platform |
246 | 258 |
247 def GetListingURL(self, marker=None): | 259 def GetListingURL(self, marker=None): |
248 """Returns the URL for a directory listing, with an optional marker.""" | 260 """Returns the URL for a directory listing, with an optional marker.""" |
249 marker_param = '' | 261 marker_param = '' |
250 if marker: | 262 if marker: |
251 marker_param = '&marker=' + str(marker) | 263 marker_param = '&marker=' + str(marker) |
252 if self.is_asan: | 264 if self.is_asan: |
253 prefix = '%s-%s' % (self.GetASANPlatformDir(), self.build_type) | 265 prefix = '%s-%s' % (self.GetASANPlatformDir(), self.build_type) |
254 return self.base_url + '/?delimiter=&prefix=' + prefix + marker_param | 266 return self.base_url + '/?delimiter=&prefix=' + prefix + marker_param |
255 else: | 267 else: |
256 return (self.base_url + '/?delimiter=/&prefix=' + | 268 return (self.base_url + '/?delimiter=/&prefix=' + |
257 self._listing_platform_dir + marker_param) | 269 self._listing_platform_dir + marker_param) |
258 | 270 |
259 def GetDownloadURL(self, revision): | 271 def GetDownloadURL(self, revision): |
260 """Gets the download URL for a build archive of a specific revision.""" | 272 """Gets the download URL for a build archive of a specific revision.""" |
261 if self.is_asan: | 273 if self.is_asan: |
262 return '%s/%s-%s/%s-%d.zip' % ( | 274 return '%s/%s-%s/%s-%d.zip' % ( |
263 ASAN_BASE_URL, self.GetASANPlatformDir(), self.build_type, | 275 ASAN_BASE_URL, self.GetASANPlatformDir(), self.build_type, |
264 self.GetASANBaseName(), revision) | 276 self.GetASANBaseName(), revision) |
265 if self.is_official: | 277 if self.is_official: |
266 if self.android: | 278 if self.is_android: |
267 official_base_url = ANDROID_OFFICIAL_BASE_URL | 279 official_base_url = ANDROID_OFFICIAL_BASE_URL |
268 else: | 280 else: |
269 official_base_url = OFFICIAL_BASE_URL | 281 official_base_url = OFFICIAL_BASE_URL |
270 return '%s/%s/%s%s' % ( | 282 return '%s/%s/%s%s' % ( |
271 official_base_url, revision, self._listing_platform_dir, | 283 official_base_url, revision, self._listing_platform_dir, |
272 self.archive_name) | 284 self.archive_name) |
273 else: | 285 else: |
274 if str(revision) in self.githash_svn_dict: | 286 if self.is_android: |
275 revision = self.githash_svn_dict[str(revision)] | 287 # These files need to be downloaded through gsutil. |
276 return '%s/%s%s/%s' % (self.base_url, self._listing_platform_dir, | 288 return ('gs://%s/%s/%s' % (ANDROID_TOT_BUCKET_NAME, revision, |
277 revision, self.archive_name) | 289 self.archive_name)) |
| 290 else: |
| 291 if str(revision) in self.githash_svn_dict: |
| 292 revision = self.githash_svn_dict[str(revision)] |
| 293 return '%s/%s%s/%s' % (self.base_url, self._listing_platform_dir, |
| 294 revision, self.archive_name) |
278 | 295 |
279 def GetLastChangeURL(self): | 296 def GetLastChangeURL(self): |
280 """Returns a URL to the LAST_CHANGE file.""" | 297 """Returns a URL to the LAST_CHANGE file.""" |
281 return self.base_url + '/' + self._listing_platform_dir + 'LAST_CHANGE' | 298 return self.base_url + '/' + self._listing_platform_dir + 'LAST_CHANGE' |
282 | 299 |
283 def GetASANBaseName(self): | 300 def GetASANBaseName(self): |
284 """Returns the base name of the ASAN zip file.""" | 301 """Returns the base name of the ASAN zip file.""" |
285 if 'linux' in self.platform: | 302 if 'linux' in self.platform: |
286 return 'asan-symbolized-%s-%s' % (self.GetASANPlatformDir(), | 303 return 'asan-symbolized-%s-%s' % (self.GetASANPlatformDir(), |
287 self.build_type) | 304 self.build_type) |
(...skipping 249 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
537 self.good_revision = FixChromiumRevForBlink(revlist, | 554 self.good_revision = FixChromiumRevForBlink(revlist, |
538 revlist_all, | 555 revlist_all, |
539 self, | 556 self, |
540 self.good_revision) | 557 self.good_revision) |
541 self.bad_revision = FixChromiumRevForBlink(revlist, | 558 self.bad_revision = FixChromiumRevForBlink(revlist, |
542 revlist_all, | 559 revlist_all, |
543 self, | 560 self, |
544 self.bad_revision) | 561 self.bad_revision) |
545 return revlist | 562 return revlist |
546 | 563 |
| 564 def _GetHashToNumberDict(self): |
| 565 """Gets the mapping of git hashes to git numbers from Google Storage.""" |
| 566 gs_file = 'gs://%s/gitnumbers_dict.json' % ANDROID_TOT_BUCKET_NAME |
| 567 local_file = 'gitnumbers_dict.json' |
| 568 GsutilDownload(gs_file, local_file) |
| 569 json_data = open(local_file).read() |
| 570 os.remove(local_file) |
| 571 return json.loads(json_data) |
| 572 |
| 573 def GetAndroidToTRevisions(self): |
| 574 """Gets the ordered list of revisions between self.good_revision and |
| 575 self.bad_revision from the Android tip of tree GS bucket. |
| 576 """ |
| 577 # Dictionary that maps git hashes to git numbers. The git numbers |
| 578 # let us order the revisions. |
| 579 hash_to_num = self._GetHashToNumberDict() |
| 580 try: |
| 581 good_rev_num = hash_to_num[self.good_revision] |
| 582 bad_rev_num = hash_to_num[self.bad_revision] |
| 583 except KeyError: |
| 584 exit('Error. Make sure the good and bad revisions are valid git hashes.') |
| 585 |
| 586 # List of all builds by their git hashes in the storage bucket. |
| 587 hash_list = GsutilList(ANDROID_TOT_BUCKET_NAME) |
| 588 |
| 589 # Get list of builds that we want to bisect over. |
| 590 final_list = [] |
| 591 minnum = min(good_rev_num, bad_rev_num) |
| 592 maxnum = max(good_rev_num, bad_rev_num) |
| 593 for githash in hash_list: |
| 594 if len(githash) != 40: |
| 595 continue |
| 596 gitnumber = hash_to_num[githash] |
| 597 if minnum < gitnumber < maxnum: |
| 598 final_list.append(githash) |
| 599 return sorted(final_list, key=lambda h: hash_to_num[h]) |
| 600 |
547 def GetOfficialBuildsList(self): | 601 def GetOfficialBuildsList(self): |
548 """Gets the list of official build numbers between self.good_revision and | 602 """Gets the list of official build numbers between self.good_revision and |
549 self.bad_revision.""" | 603 self.bad_revision.""" |
550 | 604 |
551 def CheckDepotToolsInPath(): | |
552 delimiter = ';' if sys.platform.startswith('win') else ':' | |
553 path_list = os.environ['PATH'].split(delimiter) | |
554 for path in path_list: | |
555 if path.rstrip(os.path.sep).endswith('depot_tools'): | |
556 return path | |
557 return None | |
558 | |
559 def RunGsutilCommand(args): | |
560 gsutil_path = CheckDepotToolsInPath() | |
561 if gsutil_path is None: | |
562 print ('Follow the instructions in this document ' | |
563 'http://dev.chromium.org/developers/how-tos/install-depot-tools' | |
564 ' to install depot_tools and then try again.') | |
565 sys.exit(1) | |
566 gsutil_path = os.path.join(gsutil_path, 'third_party', 'gsutil', 'gsutil') | |
567 gsutil = subprocess.Popen([sys.executable, gsutil_path] + args, | |
568 stdout=subprocess.PIPE, stderr=subprocess.PIPE, | |
569 env=None) | |
570 stdout, stderr = gsutil.communicate() | |
571 if gsutil.returncode: | |
572 if (re.findall(r'status[ |=]40[1|3]', stderr) or | |
573 stderr.startswith(CREDENTIAL_ERROR_MESSAGE)): | |
574 print ('Follow these steps to configure your credentials and try' | |
575 ' running the bisect-builds.py again.:\n' | |
576 ' 1. Run "python %s config" and follow its instructions.\n' | |
577 ' 2. If you have a @google.com account, use that account.\n' | |
578 ' 3. For the project-id, just enter 0.' % gsutil_path) | |
579 sys.exit(1) | |
580 else: | |
581 raise Exception('Error running the gsutil command: %s' % stderr) | |
582 return stdout | |
583 | |
584 def GsutilList(bucket): | |
585 query = 'gs://%s/' % bucket | |
586 stdout = RunGsutilCommand(['ls', query]) | |
587 return [url[len(query):].strip('/') for url in stdout.splitlines()] | |
588 | |
589 # Download the revlist and filter for just the range between good and bad. | 605 # Download the revlist and filter for just the range between good and bad. |
590 minrev = min(self.good_revision, self.bad_revision) | 606 minrev = min(self.good_revision, self.bad_revision) |
591 maxrev = max(self.good_revision, self.bad_revision) | 607 maxrev = max(self.good_revision, self.bad_revision) |
592 if self.android: | 608 if self.is_android: |
593 gs_bucket_name = ANDROID_BUCKET_NAME | 609 gs_bucket_name = ANDROID_BUCKET_NAME |
594 else: | 610 else: |
595 gs_bucket_name = GS_BUCKET_NAME | 611 gs_bucket_name = GS_BUCKET_NAME |
596 build_numbers = GsutilList(gs_bucket_name) | 612 build_numbers = GsutilList(gs_bucket_name) |
597 revision_re = re.compile(r'(\d\d\.\d\.\d{4}\.\d+)') | 613 revision_re = re.compile(r'(\d\d\.\d\.\d{4}\.\d+)') |
598 build_numbers = filter(lambda b: revision_re.search(b), build_numbers) | 614 build_numbers = filter(lambda b: revision_re.search(b), build_numbers) |
599 final_list = [] | 615 final_list = [] |
600 parsed_build_numbers = [LooseVersion(x) for x in build_numbers] | 616 parsed_build_numbers = [LooseVersion(x) for x in build_numbers] |
601 connection = httplib.HTTPConnection(GOOGLE_APIS_URL) | 617 connection = httplib.HTTPConnection(GOOGLE_APIS_URL) |
602 for build_number in sorted(parsed_build_numbers): | 618 for build_number in sorted(parsed_build_numbers): |
603 if build_number > maxrev: | 619 if build_number > maxrev: |
604 break | 620 break |
605 if build_number < minrev: | 621 if build_number < minrev: |
606 continue | 622 continue |
607 path = ('/' + gs_bucket_name + '/' + str(build_number) + '/' + | 623 path = ('/' + gs_bucket_name + '/' + str(build_number) + '/' + |
608 self._listing_platform_dir + self.archive_name) | 624 self._listing_platform_dir + self.archive_name) |
609 connection.request('HEAD', path) | 625 connection.request('HEAD', path) |
610 response = connection.getresponse() | 626 response = connection.getresponse() |
611 if response.status == 200: | 627 if response.status == 200: |
612 final_list.append(str(build_number)) | 628 final_list.append(str(build_number)) |
613 response.read() | 629 response.read() |
614 connection.close() | 630 connection.close() |
615 return final_list | 631 return final_list |
616 | 632 |
| 633 |
| 634 def CheckDepotToolsInPath(): |
| 635 delimiter = ';' if sys.platform.startswith('win') else ':' |
| 636 path_list = os.environ['PATH'].split(delimiter) |
| 637 for path in path_list: |
| 638 if path.rstrip(os.path.sep).endswith('depot_tools'): |
| 639 return path |
| 640 return None |
| 641 |
| 642 |
| 643 def RunGsutilCommand(args): |
| 644 gsutil_path = CheckDepotToolsInPath() |
| 645 if gsutil_path is None: |
| 646 print ('Follow the instructions in this document ' |
| 647 'http://dev.chromium.org/developers/how-tos/install-depot-tools' |
| 648 ' to install depot_tools and then try again.') |
| 649 sys.exit(1) |
| 650 gsutil_path = os.path.join(gsutil_path, 'third_party', 'gsutil', 'gsutil') |
| 651 gsutil = subprocess.Popen([sys.executable, gsutil_path] + args, |
| 652 stdout=subprocess.PIPE, stderr=subprocess.PIPE, |
| 653 env=None) |
| 654 stdout, stderr = gsutil.communicate() |
| 655 if gsutil.returncode: |
| 656 if (re.findall(r'status[ |=]40[1|3]', stderr) or |
| 657 stderr.startswith(CREDENTIAL_ERROR_MESSAGE)): |
| 658 print ('Follow these steps to configure your credentials and try' |
| 659 ' running the bisect-builds.py again.:\n' |
| 660 ' 1. Run "python %s config" and follow its instructions.\n' |
| 661 ' 2. If you have a @google.com account, use that account.\n' |
| 662 ' 3. For the project-id, just enter 0.' % gsutil_path) |
| 663 sys.exit(1) |
| 664 else: |
| 665 raise Exception('Error running the gsutil command: %s' % stderr) |
| 666 return stdout |
| 667 |
| 668 |
| 669 def GsutilList(bucket): |
| 670 query = 'gs://%s/' % bucket |
| 671 stdout = RunGsutilCommand(['ls', query]) |
| 672 return [url[len(query):].strip('/') for url in stdout.splitlines()] |
| 673 |
| 674 |
| 675 def GsutilDownload(gs_download_url, filename): |
| 676 RunGsutilCommand(['cp', gs_download_url, filename]) |
| 677 |
| 678 |
617 def UnzipFilenameToDir(filename, directory): | 679 def UnzipFilenameToDir(filename, directory): |
618 """Unzip |filename| to |directory|.""" | 680 """Unzip |filename| to |directory|.""" |
619 cwd = os.getcwd() | 681 cwd = os.getcwd() |
620 if not os.path.isabs(filename): | 682 if not os.path.isabs(filename): |
621 filename = os.path.join(cwd, filename) | 683 filename = os.path.join(cwd, filename) |
622 zf = zipfile.ZipFile(filename) | 684 zf = zipfile.ZipFile(filename) |
623 # Make base. | 685 # Make base. |
624 if not os.path.isdir(directory): | 686 if not os.path.isdir(directory): |
625 os.mkdir(directory) | 687 os.mkdir(directory) |
626 os.chdir(directory) | 688 os.chdir(directory) |
627 # Extract files. | 689 # Extract files. |
628 for info in zf.infolist(): | 690 for info in zf.infolist(): |
629 name = info.filename | 691 name = info.filename |
630 if name.endswith('/'): # dir | 692 if name.endswith('/'): # dir |
631 if not os.path.isdir(name): | 693 if not os.path.isdir(name): |
632 os.makedirs(name) | 694 os.makedirs(name) |
633 else: # file | 695 else: # file |
634 directory = os.path.dirname(name) | 696 directory = os.path.dirname(name) |
635 if not os.path.isdir(directory): | 697 if directory and not os.path.isdir(directory): |
636 os.makedirs(directory) | 698 os.makedirs(directory) |
637 out = open(name, 'wb') | 699 out = open(name, 'wb') |
638 out.write(zf.read(name)) | 700 out.write(zf.read(name)) |
639 out.close() | 701 out.close() |
640 # Set permissions. Permission info in external_attr is shifted 16 bits. | 702 # Set permissions. Permission info in external_attr is shifted 16 bits. |
641 os.chmod(name, info.external_attr >> 16L) | 703 os.chmod(name, info.external_attr >> 16L) |
642 os.chdir(cwd) | 704 os.chdir(cwd) |
643 | 705 |
644 | 706 |
645 def FetchRevision(context, rev, filename, quit_event=None, progress_event=None): | 707 def FetchRevision(context, rev, filename, quit_event=None, progress_event=None): |
(...skipping 14 matching lines...) Expand all Loading... |
660 size = blocknum * blocksize | 722 size = blocknum * blocksize |
661 if totalsize == -1: # Total size not known. | 723 if totalsize == -1: # Total size not known. |
662 progress = 'Received %d bytes' % size | 724 progress = 'Received %d bytes' % size |
663 else: | 725 else: |
664 size = min(totalsize, size) | 726 size = min(totalsize, size) |
665 progress = 'Received %d of %d bytes, %.2f%%' % ( | 727 progress = 'Received %d of %d bytes, %.2f%%' % ( |
666 size, totalsize, 100.0 * size / totalsize) | 728 size, totalsize, 100.0 * size / totalsize) |
667 # Send a \r to let all progress messages use just one line of output. | 729 # Send a \r to let all progress messages use just one line of output. |
668 sys.stdout.write('\r' + progress) | 730 sys.stdout.write('\r' + progress) |
669 sys.stdout.flush() | 731 sys.stdout.flush() |
670 | |
671 download_url = context.GetDownloadURL(rev) | 732 download_url = context.GetDownloadURL(rev) |
672 try: | 733 try: |
673 urllib.urlretrieve(download_url, filename, ReportHook) | 734 if context.download_with_gsutil: |
| 735 GsutilDownload(download_url, filename) |
| 736 else: |
| 737 urllib.urlretrieve(download_url, filename, ReportHook) |
674 if progress_event and progress_event.isSet(): | 738 if progress_event and progress_event.isSet(): |
675 print | 739 print |
| 740 |
676 except RuntimeError: | 741 except RuntimeError: |
677 pass | 742 pass |
678 | 743 |
679 | 744 |
| 745 def RunADBCommand(args): |
| 746 cmd = ['adb'] + args |
| 747 adb = subprocess.Popen(['adb'] + args, |
| 748 stdout=subprocess.PIPE, stderr=subprocess.PIPE, |
| 749 env=None) |
| 750 stdout, stderr = adb.communicate() |
| 751 return stdout |
| 752 |
| 753 |
680 def IsADBInstalled(): | 754 def IsADBInstalled(): |
681 """Checks if ADB is in the environment path.""" | 755 """Checks if ADB is in the environment path.""" |
682 try: | 756 try: |
683 adb_output = subprocess.check_output(['adb', 'version']) | 757 adb_output = RunADBCommand(['version']) |
684 return ('Android Debug Bridge' in adb_output) | 758 return ('Android Debug Bridge' in adb_output) |
685 except OSError: | 759 except OSError: |
686 return False | 760 return False |
687 | 761 |
688 | 762 |
689 def GetAndroidDeviceList(): | 763 def GetAndroidDeviceList(): |
690 """Returns the list of Android devices attached to the host machine.""" | 764 """Returns the list of Android devices attached to the host machine.""" |
691 lines = subprocess.check_output(['adb', 'devices']).split('\n')[1:] | 765 lines = RunADBCommand(['devices']).split('\n')[1:] |
692 devices = [] | 766 devices = [] |
693 for line in lines: | 767 for line in lines: |
694 m = re.match('^(.*?)\s+device$', line) | 768 m = re.match('^(.*?)\s+device$', line) |
695 if not m: | 769 if not m: |
696 continue | 770 continue |
697 devices.append(m.group(1)) | 771 devices.append(m.group(1)) |
698 return devices | 772 return devices |
699 | 773 |
700 | 774 |
701 def RunAndroidRevision(context, revision, apk_file): | 775 def RunAndroidRevision(context, revision, zip_file): |
702 """Given a Chrome apk, install it on a local device, and launch Chrome.""" | 776 """Given a Chrome apk, install it on a local device, and launch Chrome.""" |
703 devices = GetAndroidDeviceList() | 777 devices = GetAndroidDeviceList() |
704 if len(devices) is not 1: | 778 if len(devices) is not 1: |
705 sys.exit('Please have 1 Android device plugged in. %d devices found' | 779 sys.exit('Please have 1 Android device plugged in. %d devices found' |
706 % len(devices)) | 780 % len(devices)) |
707 | 781 |
708 devnull = open(os.devnull, 'w') | 782 if context.is_official: |
| 783 # Downloaded file is just the .apk in this case. |
| 784 apk_file = zip_file |
| 785 else: |
| 786 cwd = os.getcwd() |
| 787 tempdir = tempfile.mkdtemp(prefix='bisect_tmp') |
| 788 UnzipFilenameToDir(zip_file, tempdir) |
| 789 os.chdir(tempdir) |
| 790 apk_file = context.android_apk |
| 791 |
709 package_name = ANDROID_CHROME_PACKAGE_NAME[context.android_apk] | 792 package_name = ANDROID_CHROME_PACKAGE_NAME[context.android_apk] |
710 | 793 print 'Installing...' |
711 print 'Installing new Chrome version...' | 794 RunADBCommand(['install', '-r', '-d', apk_file]) |
712 subprocess.call(['adb', 'install', '-r', '-d', apk_file], | |
713 stdout=devnull, stderr=devnull) | |
714 | 795 |
715 print 'Launching Chrome...\n' | 796 print 'Launching Chrome...\n' |
716 subprocess.call(['adb', 'shell', 'am', 'start', '-a', | 797 RunADBCommand(['shell', 'am', 'start', '-a', |
717 'android.intent.action.VIEW', '-n', package_name + | 798 'android.intent.action.VIEW', '-n', package_name + |
718 '/com.google.android.apps.chrome.Main'], stdout=devnull, stderr=devnull) | 799 '/com.google.android.apps.chrome.Main']) |
719 | 800 |
720 | 801 |
721 def RunRevision(context, revision, zip_file, profile, num_runs, command, args): | 802 def RunRevision(context, revision, zip_file, profile, num_runs, command, args): |
722 """Given a zipped revision, unzip it and run the test.""" | 803 """Given a zipped revision, unzip it and run the test.""" |
723 print 'Trying revision %s...' % str(revision) | 804 print 'Trying revision %s...' % str(revision) |
724 | 805 |
725 if context.android: | 806 if context.is_android: |
726 RunAndroidRevision(context, revision, zip_file) | 807 RunAndroidRevision(context, revision, zip_file) |
727 # TODO(mikecase): Support running command to auto-bisect Android. | 808 # TODO(mikecase): Support running command to auto-bisect Android. |
728 return (None, None, None) | 809 return (None, None, None) |
729 | 810 |
730 # Create a temp directory and unzip the revision into it. | 811 # Create a temp directory and unzip the revision into it. |
731 cwd = os.getcwd() | 812 cwd = os.getcwd() |
732 tempdir = tempfile.mkdtemp(prefix='bisect_tmp') | 813 tempdir = tempfile.mkdtemp(prefix='bisect_tmp') |
733 UnzipFilenameToDir(zip_file, tempdir) | 814 UnzipFilenameToDir(zip_file, tempdir) |
734 | 815 |
735 # Hack: Chrome OS archives are missing icudtl.dat; try to copy it from | 816 # Hack: Chrome OS archives are missing icudtl.dat; try to copy it from |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
772 replace('%s', ' '.join(testargs))) | 853 replace('%s', ' '.join(testargs))) |
773 | 854 |
774 results = [] | 855 results = [] |
775 for _ in range(num_runs): | 856 for _ in range(num_runs): |
776 subproc = subprocess.Popen(runcommand, | 857 subproc = subprocess.Popen(runcommand, |
777 bufsize=-1, | 858 bufsize=-1, |
778 stdout=subprocess.PIPE, | 859 stdout=subprocess.PIPE, |
779 stderr=subprocess.PIPE) | 860 stderr=subprocess.PIPE) |
780 (stdout, stderr) = subproc.communicate() | 861 (stdout, stderr) = subproc.communicate() |
781 results.append((subproc.returncode, stdout, stderr)) | 862 results.append((subproc.returncode, stdout, stderr)) |
782 | |
783 os.chdir(cwd) | 863 os.chdir(cwd) |
784 try: | 864 try: |
785 shutil.rmtree(tempdir, True) | 865 shutil.rmtree(tempdir, True) |
786 except Exception: | 866 except Exception: |
787 pass | 867 pass |
788 | 868 |
789 for (returncode, stdout, stderr) in results: | 869 for (returncode, stdout, stderr) in results: |
790 if returncode: | 870 if returncode: |
791 return (returncode, stdout, stderr) | 871 return (returncode, stdout, stderr) |
792 return results[0] | 872 return results[0] |
(...skipping 117 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
910 | 990 |
911 print 'Downloading list of known revisions...', | 991 print 'Downloading list of known revisions...', |
912 if not context.use_local_cache and not context.is_official: | 992 if not context.use_local_cache and not context.is_official: |
913 print '(use --use-local-cache to cache and re-use the list of revisions)' | 993 print '(use --use-local-cache to cache and re-use the list of revisions)' |
914 else: | 994 else: |
915 print | 995 print |
916 _GetDownloadPath = lambda rev: os.path.join(cwd, | 996 _GetDownloadPath = lambda rev: os.path.join(cwd, |
917 '%s-%s' % (str(rev), context.archive_name)) | 997 '%s-%s' % (str(rev), context.archive_name)) |
918 if context.is_official: | 998 if context.is_official: |
919 revlist = context.GetOfficialBuildsList() | 999 revlist = context.GetOfficialBuildsList() |
| 1000 elif context.is_android: # Android non-official |
| 1001 revlist = context.GetAndroidToTRevisions() |
920 else: | 1002 else: |
921 revlist = context.GetRevList() | 1003 revlist = context.GetRevList() |
922 | 1004 |
923 # Get a list of revisions to bisect across. | 1005 # Get a list of revisions to bisect across. |
924 if len(revlist) < 2: # Don't have enough builds to bisect. | 1006 if len(revlist) < 2: # Don't have enough builds to bisect. |
925 msg = 'We don\'t have enough builds to bisect. revlist: %s' % revlist | 1007 msg = 'We don\'t have enough builds to bisect. revlist: %s' % revlist |
926 raise RuntimeError(msg) | 1008 raise RuntimeError(msg) |
927 | 1009 |
928 # Figure out our bookends and first pivot point; fetch the pivot revision. | 1010 # Figure out our bookends and first pivot point; fetch the pivot revision. |
929 minrev = 0 | 1011 minrev = 0 |
(...skipping 362 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1292 elif opts.blink: | 1374 elif opts.blink: |
1293 base_url = WEBKIT_BASE_URL | 1375 base_url = WEBKIT_BASE_URL |
1294 else: | 1376 else: |
1295 base_url = CHROMIUM_BASE_URL | 1377 base_url = CHROMIUM_BASE_URL |
1296 | 1378 |
1297 # Create the context. Initialize 0 for the revisions as they are set below. | 1379 # Create the context. Initialize 0 for the revisions as they are set below. |
1298 context = PathContext(base_url, opts.archive, opts.good, opts.bad, | 1380 context = PathContext(base_url, opts.archive, opts.good, opts.bad, |
1299 opts.official_builds, opts.asan, opts.use_local_cache, | 1381 opts.official_builds, opts.asan, opts.use_local_cache, |
1300 opts.flash_path, opts.pdf_path, opts.apk) | 1382 opts.flash_path, opts.pdf_path, opts.apk) |
1301 | 1383 |
1302 # TODO(mikecase): Add support to bisect on nonofficial builds for Android. | 1384 if context.is_android and not opts.official_builds: |
1303 if context.android and not opts.official_builds: | 1385 if (context.platform != 'android-arm' or |
1304 sys.exit('Can only bisect on official builds for Android.') | 1386 context.android_apk != 'Chrome.apk'): |
| 1387 sys.exit('For non-official builds, can only bisect' |
| 1388 ' Chrome.apk arm builds.') |
1305 | 1389 |
1306 # If bisecting Android, we make sure we have ADB setup. | 1390 # If bisecting Android, we make sure we have ADB setup. |
1307 if context.android: | 1391 if context.is_android: |
1308 if opts.adb_path: | 1392 if opts.adb_path: |
1309 os.environ['PATH'] = '%s:%s' % (os.path.dirname(opts.adb_path), | 1393 os.environ['PATH'] = '%s:%s' % (os.path.dirname(opts.adb_path), |
1310 os.environ['PATH']) | 1394 os.environ['PATH']) |
1311 if not IsADBInstalled(): | 1395 if not IsADBInstalled(): |
1312 sys.exit('Please have "adb" in PATH or use adb_path command line option' | 1396 sys.exit('Please have "adb" in PATH or use adb_path command line option' |
1313 'to bisect Android builds.') | 1397 'to bisect Android builds.') |
1314 | 1398 |
1315 # Pick a starting point, try to get HEAD for this. | 1399 # Pick a starting point, try to get HEAD for this. |
1316 if not opts.bad: | 1400 if not opts.bad: |
1317 context.bad_revision = '999.0.0.0' | 1401 context.bad_revision = '999.0.0.0' |
1318 context.bad_revision = GetChromiumRevision( | 1402 context.bad_revision = GetChromiumRevision( |
1319 context, context.GetLastChangeURL()) | 1403 context, context.GetLastChangeURL()) |
1320 | 1404 |
1321 # Find out when we were good. | 1405 # Find out when we were good. |
1322 if not opts.good: | 1406 if not opts.good: |
1323 context.good_revision = '0.0.0.0' if opts.official_builds else 0 | 1407 context.good_revision = '0.0.0.0' if opts.official_builds else 0 |
1324 | 1408 |
1325 if opts.flash_path: | 1409 if opts.flash_path: |
1326 msg = 'Could not find Flash binary at %s' % opts.flash_path | 1410 msg = 'Could not find Flash binary at %s' % opts.flash_path |
1327 assert os.path.exists(opts.flash_path), msg | 1411 assert os.path.exists(opts.flash_path), msg |
1328 | 1412 |
1329 if opts.pdf_path: | 1413 if opts.pdf_path: |
1330 msg = 'Could not find PDF binary at %s' % opts.pdf_path | 1414 msg = 'Could not find PDF binary at %s' % opts.pdf_path |
1331 assert os.path.exists(opts.pdf_path), msg | 1415 assert os.path.exists(opts.pdf_path), msg |
1332 | 1416 |
1333 if opts.official_builds: | 1417 if opts.official_builds: |
1334 context.good_revision = LooseVersion(context.good_revision) | 1418 context.good_revision = LooseVersion(context.good_revision) |
1335 context.bad_revision = LooseVersion(context.bad_revision) | 1419 context.bad_revision = LooseVersion(context.bad_revision) |
| 1420 elif context.is_android: |
| 1421 # Revisions are git hashes and should be left as strings. |
| 1422 pass |
1336 else: | 1423 else: |
1337 context.good_revision = int(context.good_revision) | 1424 context.good_revision = int(context.good_revision) |
1338 context.bad_revision = int(context.bad_revision) | 1425 context.bad_revision = int(context.bad_revision) |
1339 | 1426 |
1340 if opts.times < 1: | 1427 if opts.times < 1: |
1341 print('Number of times to run (%d) must be greater than or equal to 1.' % | 1428 print('Number of times to run (%d) must be greater than or equal to 1.' % |
1342 opts.times) | 1429 opts.times) |
1343 parser.print_help() | 1430 parser.print_help() |
1344 return 1 | 1431 return 1 |
1345 | 1432 |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1391 | 1478 |
1392 print 'CHANGELOG URL:' | 1479 print 'CHANGELOG URL:' |
1393 if opts.official_builds: | 1480 if opts.official_builds: |
1394 print OFFICIAL_CHANGELOG_URL % (min_chromium_rev, max_chromium_rev) | 1481 print OFFICIAL_CHANGELOG_URL % (min_chromium_rev, max_chromium_rev) |
1395 else: | 1482 else: |
1396 PrintChangeLog(min_chromium_rev, max_chromium_rev) | 1483 PrintChangeLog(min_chromium_rev, max_chromium_rev) |
1397 | 1484 |
1398 | 1485 |
1399 if __name__ == '__main__': | 1486 if __name__ == '__main__': |
1400 sys.exit(main()) | 1487 sys.exit(main()) |
OLD | NEW |