Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(26)

Side by Side Diff: tools/bisect-builds.py

Issue 851333004: Add support to bisect nonofficial Android builds from tip of tree bot. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fixed mistake when rebasing. Created 5 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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
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
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
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
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
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
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())
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698