Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 # Copyright (c) 2016 The Chromium Authors. All rights reserved. | |
| 2 # Use of this source code is governed by a BSD-style license that can be | |
| 3 # found in the LICENSE file. | |
| 4 | |
| 5 """ Bisect repackage tool for Linux | |
| 6 | |
| 7 This script repacakges chrome builds for manual bisect script. | |
| 8 """ | |
| 9 | |
| 10 #Declares required files to run manual bisect script on chrome Linux | |
|
prasadv
2016/08/30 22:12:20
nit: Need 1 space after #
miimnk
2016/09/01 00:49:11
Done.
| |
| 11 #builds in perf. Binary files that should be stripped to reduce zip file | |
| 12 #size are declared. The file list was gotten from the local chrome | |
| 13 #executable path. (This can be retrieved by typing 'chrome://version' | |
| 14 #in chrome and following the executable path. The list needs to be updated if | |
| 15 #future chrome versions require additional files. | |
| 16 CHROME_REQUIRED_FILES = { | |
| 17 'linux': [ | |
|
prasadv
2016/08/30 22:12:20
4 space on hanging indent.
repeat same wherever ap
miimnk
2016/09/01 00:49:11
Done.
| |
| 18 'chrome', | |
| 19 'chrome_100_percent.pak', | |
| 20 'chrome_200_percent.pak', | |
| 21 'default_apps', | |
| 22 'icudtl.dat', | |
| 23 'libwidevinecdm.so', | |
| 24 'locales', | |
| 25 'nacl_helper', | |
| 26 'nacl_helper_bootstrap', | |
| 27 'nacl_irt_x86_64.nexe', | |
| 28 'natives_blob.bin', | |
| 29 'PepperFlash', | |
| 30 'product_logo_48.png', | |
| 31 'resources.pak', | |
| 32 'snapshot_blob.bin', | |
| 33 'xdg-mime', | |
| 34 'xdg-settings' | |
| 35 ], | |
| 36 'win64': [ | |
| 37 'chrome.dll', | |
| 38 'chrome.exe', | |
| 39 'chrome_100_percent.pak', | |
| 40 'chrome_200_percent.pak', | |
| 41 'chrome_child.dll', | |
| 42 'chrome_elf.dll', | |
| 43 'chrome_watcher.dll', | |
| 44 'default_apps', | |
| 45 'd3dcompiler_47.dll', | |
| 46 'icudtl.dat', | |
| 47 'libEGL.dll', | |
| 48 'libGLESv2.dll', | |
| 49 'locales', | |
| 50 'nacl_irt_x86_64.nexe', | |
| 51 'natives_blob.bin', | |
| 52 'PepperFlash', | |
| 53 'resources.pak', | |
| 54 'SecondaryTile.png', | |
| 55 'snapshot_blob.bin' | |
| 56 ], | |
| 57 'mac': [ | |
| 58 'Google Chrome.app' | |
| 59 ] | |
| 60 } | |
| 61 | |
| 62 CHROME_WHITELIST_FILES = { | |
| 63 # ^$ meanse not to include any files from whitelist | |
| 64 'linux': '^$', | |
| 65 'win64': '^\d+\.\d+\.\d+\.\d+\.manifest$', | |
| 66 'mac': '^$' | |
| 67 } | |
| 68 | |
| 69 CHROME_STRIP_LIST = { | |
| 70 'linux': [ | |
| 71 'chrome', | |
| 72 'nacl_helper' | |
| 73 ], | |
| 74 'win64': [ | |
| 75 # No stripping symbols from win64 archives | |
| 76 ], | |
| 77 'mac': [ | |
| 78 # No stripping symbols from mac archives | |
| 79 ] | |
| 80 } | |
| 81 | |
| 82 # API to convert Githash to Commit position number. | |
| 83 CHROMIUM_GITHASH_TO_SVN_URL = ( | |
| 84 'https://cr-rev.appspot.com/_ah/api/crrev/v1/commit/%s') | |
| 85 | |
| 86 REVISION_MAP_FILE = 'revision_map.json' | |
| 87 | |
| 88 BUILDER_NAME = { | |
| 89 'linux': 'Linux Builder', | |
| 90 'mac': 'Mac Builder', | |
| 91 'win32': 'Win Builder', | |
| 92 'win64': 'Win x64 Builder' | |
| 93 } | |
| 94 | |
| 95 ARCHIVE_PREFIX = { | |
| 96 'linux': 'full-build-linux', | |
| 97 'mac': 'full-build-mac', | |
| 98 'win32': 'full-build-win32', | |
| 99 'win64': 'full-build-win32' | |
| 100 } | |
| 101 | |
| 102 | |
| 103 ################################################################################ | |
|
prasadv
2016/08/30 22:12:20
Remove this # line
miimnk
2016/09/01 00:49:11
Done.
| |
| 104 import os | |
|
prasadv
2016/08/30 22:12:20
Ordering of import is not correct.
refer Python st
miimnk
2016/09/01 00:49:11
Done.
| |
| 105 import bisect_repackage_utils | |
| 106 import zipfile | |
| 107 import zlib | |
| 108 import json | |
| 109 import sys | |
| 110 import optparse | |
| 111 import re | |
| 112 import urllib | |
| 113 import threading | |
| 114 from multiprocessing import Pool | |
| 115 from functools import partial | |
| 116 import tempfile | |
| 117 import logging | |
| 118 | |
| 119 class PathContext(object): | |
| 120 """A PathContext is used to carry the information used to construct URLs and | |
|
prasadv
2016/08/30 22:12:21
Single line doc string.
miimnk
2016/09/01 00:49:11
Done.
| |
| 121 paths when dealing with the storage server and archives.""" | |
| 122 def __init__(self, original_gs_url, repackage_gs_url, | |
| 123 archive, revision_file=REVISION_MAP_FILE): | |
| 124 super(PathContext, self).__init__() | |
| 125 self.original_gs_url = original_gs_url | |
| 126 self.repackage_gs_url = repackage_gs_url | |
| 127 self.archive = archive | |
| 128 self.builder_name = BUILDER_NAME[archive] | |
| 129 self.file_prefix = ARCHIVE_PREFIX[archive] | |
| 130 self.revision_file = revision_file | |
| 131 | |
| 132 | |
| 133 def get_cp_from_hash(git_hash): | |
| 134 """Converts a GitHash to Commit position number""" | |
| 135 json_url = CHROMIUM_GITHASH_TO_SVN_URL % git_hash | |
| 136 response = urllib.urlopen(json_url) | |
| 137 if response.getcode() == 200: | |
| 138 try: | |
| 139 data = json.loads(response.read()) | |
| 140 except ValueError: | |
| 141 logging.warning('ValueError for JSON URL: %s' % json_url) | |
| 142 raise ValueError('Failed to extract commit position') | |
| 143 else: | |
| 144 logging.warning('ValueError for JSON URL: %s' % json_url) | |
| 145 raise ValueError('Failed to extract commit position') | |
| 146 if 'number' in data: | |
| 147 return data['number'] | |
| 148 logging.warning('Failed to get svn revision number for %s' % git_hash) | |
| 149 raise ValueError('Failed to extract commit position') | |
| 150 | |
| 151 def create_cp_from_hash_map(hash_list): | |
| 152 """Creates a dictionary that maps from Commit positio number | |
| 153 to corresponding GitHash""" | |
| 154 hash_map = {} | |
| 155 count = 0 | |
| 156 for git_hash in hash_list: | |
| 157 try: | |
| 158 cp_num = get_cp_from_hash(git_hash) | |
| 159 hash_map[cp_num] = git_hash | |
| 160 print "Downloaded %s / %s git hash" %(count, len(hash_list)) | |
| 161 count += 1 | |
| 162 except ValueError: | |
| 163 pass | |
| 164 return hash_map | |
| 165 | |
| 166 def get_list_of_suffix(bucket_address, prefix, filter_function): | |
| 167 """Gets the list of suffixes in files in a google storage bucket. | |
| 168 For example, a google storage bucket containing one file | |
| 169 'full-build-linux_20983' will return ['20983'] if prefix is | |
| 170 provided as 'full-build-linux'. Google Storage bucket | |
| 171 containing multiple files will return multiple suffixes. """ | |
| 172 file_list = bisect_repackage_utils.GSutilList(bucket_address) | |
| 173 suffix_list = [] | |
| 174 extract_suffix = '.*?%s_(.*?)\.zip' %(prefix) | |
| 175 for file in file_list: | |
| 176 match = re.match(extract_suffix, file) | |
| 177 if match and filter_function(match.groups()[0]): | |
| 178 suffix_list.append(match.groups()[0]) | |
| 179 return suffix_list | |
| 180 | |
| 181 def download_build(cp_num, revision_map, zip_file_name, context): | |
| 182 """ Download a single build corresponding to the cp_num and context.""" | |
| 183 file_url = '%s/%s/%s_%s.zip' %(context.original_gs_url, context.builder_name, | |
| 184 context.file_prefix, revision_map[cp_num]) | |
| 185 bisect_repackage_utils.GSUtilDownloadFile(file_url, zip_file_name) | |
| 186 | |
| 187 def upload_build(zip_file, context): | |
| 188 """Uploads a single build in zip_file to the repackage_gs_url in context.""" | |
| 189 gs_base_url = '%s/%s' %(context.repackage_gs_url, context.builder_name) | |
| 190 upload_url = gs_base_url + '/' | |
| 191 bisect_repackage_utils.GSUtilCopy(zip_file, upload_url) | |
| 192 | |
| 193 def download_revision_map(context): | |
| 194 """Downloads the revision map in original_gs_url in context.""" | |
| 195 gs_base_url = '%s/%s' %(context.repackage_gs_url, context.builder_name) | |
| 196 download_url = gs_base_url + '/' + context.revision_file | |
| 197 bisect_repackage_utils.GSUtilDownloadFile(download_url, | |
| 198 context.revision_file) | |
| 199 | |
| 200 def get_revision_map(context): | |
| 201 """Downloads and returns the revision map in repackage_gs_url in context.""" | |
| 202 bisect_repackage_utils.RemoveFile(context.revision_file) | |
| 203 download_revision_map(context) | |
| 204 with open(context.revision_file, 'r') as revision_file: | |
| 205 revision_map = json.load(revision_file) | |
| 206 bisect_repackage_utils.RemoveFile(context.revision_file) | |
| 207 return revision_map | |
| 208 | |
| 209 def upload_revision_map(revision_map, context): | |
| 210 """Upload the given revision_map to the repackage_gs_url in context.""" | |
| 211 with open(context.revision_file, 'w') as revision_file: | |
| 212 json.dump(revision_map, revision_file) | |
| 213 gs_base_url = '%s/%s' %(context.repackage_gs_url, context.builder_name) | |
| 214 upload_url = gs_base_url + '/' | |
| 215 bisect_repackage_utils.GSUtilCopy(context.revision_file, upload_url) | |
| 216 bisect_repackage_utils.RemoveFile(context.revision_file) | |
| 217 | |
| 218 def create_upload_revision_map(context): | |
| 219 """ Creates and uploads a dictionary that maps from GitHash to CP number.""" | |
| 220 gs_base_url = '%s/%s' %(context.original_gs_url, context.builder_name) | |
| 221 hash_list = get_list_of_suffix(gs_base_url, context.file_prefix, | |
| 222 bisect_repackage_utils.isGitCommitHash) | |
| 223 cp_num_to_hash_map = create_cp_from_hash_map(hash_list) | |
| 224 upload_revision_map(cp_num_to_hash_map, context) | |
| 225 | |
| 226 def update_upload_revision_map(context): | |
| 227 """ Updates and uploads a dictionary that maps from GitHash to CP number.""" | |
| 228 gs_base_url = '%s/%s' %(context.original_gs_url, context.builder_name) | |
| 229 revision_map = get_revision_map(context) | |
| 230 hash_list = get_list_of_suffix(gs_base_url, context.file_prefix, | |
| 231 bisect_repackage_utils.isGitCommitHash) | |
| 232 hash_list = list(set(hash_list)-set(revision_map.values())) | |
| 233 cp_num_to_hash_map = create_cp_from_hash_map(hash_list) | |
| 234 merged_dict = dict(cp_num_to_hash_map.items() + revision_map.items()) | |
| 235 upload_revision_map(merged_dict, context) | |
| 236 | |
| 237 def make_lightweight_archive(file_archive, archive_name, files_to_archive, | |
| 238 context, staging_dir): | |
| 239 """Repackages and strips the archive according to FILES_TO_ARCHIVE and | |
| 240 STRIP_LIST defined on the top.""" | |
| 241 strip_list = CHROME_STRIP_LIST[context.archive] | |
| 242 tmp_archive = os.path.join(staging_dir, 'tmp_%s' % archive_name) | |
| 243 (zip_file, zip_dir) = bisect_repackage_utils.MakeZip(tmp_archive, | |
| 244 archive_name, | |
| 245 files_to_archive, | |
| 246 file_archive, | |
| 247 raise_error=False, | |
| 248 strip_files=strip_list) | |
| 249 return (zip_file, zip_dir, tmp_archive) | |
| 250 | |
| 251 def remove_created_files_and_path(files, paths): | |
| 252 """ Removes all the files and paths passed in.""" | |
| 253 for file in files: | |
| 254 bisect_repackage_utils.RemoveFile(file) | |
| 255 for path in paths: | |
| 256 bisect_repackage_utils.RemovePath(path) | |
| 257 | |
| 258 def verify_chrome_run(zip_dir): | |
| 259 """This function executes chrome executable in zip_dir. Raises error if the | |
| 260 execution fails for any reason.""" | |
| 261 try: | |
| 262 command = [os.path.join(zip_dir, 'chrome')] | |
| 263 code = bisect_repackage_utils.RunCommand(command) | |
| 264 if code != 0: | |
| 265 raise ValueError("Executing Chrome Failed: Need to check ") | |
| 266 except Exception, e: | |
| 267 raise ValueError("Executing Chrome Failed: Need to check ") | |
| 268 | |
| 269 | |
| 270 def get_whitelist_files(extracted_folder, archive): | |
| 271 """Gets all the files & directories matching whitelisted regex.""" | |
| 272 whitelist_files = [] | |
| 273 all_files = os.listdir(extracted_folder) | |
| 274 for file in all_files: | |
| 275 if re.match(CHROME_WHITELIST_FILES[archive], file): | |
| 276 whitelist_files.append(file) | |
| 277 return whitelist_files | |
| 278 | |
| 279 | |
| 280 def repackage_single_revision(revision_map, isForwardArchive, verify_run, | |
| 281 staging_dir, context, cp_num): | |
| 282 """Repackages a single Chrome build for manual bisect.""" | |
| 283 archive_name = '%s_%s' %(context.file_prefix, cp_num) | |
| 284 file_archive = os.path.join(staging_dir, archive_name) | |
| 285 zip_file_name = '%s.zip' % (file_archive) | |
| 286 download_build(cp_num, revision_map, zip_file_name, context) | |
| 287 extract_dir = os.path.join(staging_dir, archive_name) | |
| 288 bisect_repackage_utils.ExtractZip(zip_file_name, extract_dir) | |
| 289 extracted_folder = os.path.join(extract_dir, context.file_prefix) | |
| 290 if CHROME_WHITELIST_FILES[context.archive]: | |
| 291 whitelist_files = get_whitelist_files(extracted_folder, context.archive) | |
| 292 files_to_include = whitelist_files + CHROME_REQUIRED_FILES[context.archive] | |
| 293 else: | |
| 294 files_to_include = CHROME_REQUIRED_FILES[context.archive] | |
| 295 (zip_dir, zip_file, tmp_archive) = make_lightweight_archive(extracted_folder, | |
| 296 archive_name, | |
| 297 files_to_include, context, | |
| 298 staging_dir) | |
| 299 | |
| 300 if verify_run: | |
| 301 verify_chrome_run(zip_dir) | |
| 302 | |
| 303 upload_build(zip_file, context) | |
| 304 # Removed temporary files created during repackaging process. | |
| 305 remove_created_files_and_path([zip_file_name], | |
| 306 [zip_dir, extract_dir, tmp_archive]) | |
| 307 | |
| 308 def repackage_revisions(revisions, revision_map, isForwardArchive, verify_run, | |
| 309 staging_dir, context, quit_event=None, | |
| 310 progress_event=None): | |
| 311 """ Repackages all Chrome builds listed in revisions. This function calls | |
| 312 'repackage_single_revision' with multithreading pool.'""" | |
| 313 p = Pool(3) | |
| 314 func = partial(repackage_single_revision, revision_map, isForwardArchive, | |
| 315 verify_run, staging_dir, context) | |
| 316 p.imap(func, revisions) | |
| 317 p.close() | |
| 318 p.join() | |
| 319 | |
| 320 def get_uploaded_builds(context): | |
| 321 """Returns already uploaded commit positions numbers in | |
| 322 context.repackage_gs_url""" | |
| 323 gs_base_url = '%s/%s' %(context.repackage_gs_url, context.builder_name) | |
| 324 return get_list_of_suffix(gs_base_url, context.file_prefix, | |
| 325 bisect_repackage_utils.isCommitPosition) | |
| 326 | |
| 327 def get_revisions_to_package(revision_map, context): | |
| 328 """ Returns revisions that need to be repackaged. The first half of | |
| 329 the revisions will be sorted in ascending order and the second half of | |
| 330 the revisions will be sorted in desending order. | |
| 331 | |
| 332 The first half will be used for repackaging revisions in forward direction, | |
| 333 and the second half will be used for repackaging revisions in backward | |
| 334 direction.""" | |
| 335 already_packaged = get_uploaded_builds(context) | |
| 336 not_already_packaged = list(set(revision_map.keys())-set(already_packaged)) | |
| 337 revisions_to_package = sorted(not_already_packaged) | |
| 338 revisions_to_package = filter(lambda a: a != 'null', revisions_to_package) | |
| 339 | |
| 340 forward_rev = revisions_to_package[:len(revisions_to_package)/2] | |
| 341 backward_rev = sorted(revisions_to_package[len(revisions_to_package)/2:], | |
| 342 reverse=True) | |
| 343 return (forward_rev, backward_rev) | |
| 344 | |
| 345 class RepackageJob(object): | |
| 346 def __init__(self, name, revisions_to_package, revision_map, isForwardArchive, | |
| 347 verify_run, staging_dir, context): | |
| 348 super(RepackageJob, self).__init__() | |
| 349 self.name = name | |
| 350 self.revisions_to_package = revisions_to_package | |
| 351 self.revision_map = revision_map | |
| 352 self.isForwardArchive = isForwardArchive | |
| 353 self.verify_run = verify_run | |
| 354 self.staging_dir = staging_dir | |
| 355 self.context = context | |
| 356 self.quit_event = threading.Event() | |
| 357 self.progress_event = threading.Event() | |
| 358 self.thread = None | |
| 359 | |
| 360 def Start(self): | |
| 361 """Starts the download.""" | |
| 362 fetchargs = (self.revisions_to_package, | |
| 363 self.revision_map, | |
| 364 self.isForwardArchive, | |
| 365 self.verify_run, | |
| 366 self.staging_dir, | |
| 367 self.context, | |
| 368 self.quit_event, | |
| 369 self.progress_event) | |
| 370 self.thread = threading.Thread(target=repackage_revisions, | |
| 371 name=self.name, | |
| 372 args=fetchargs) | |
| 373 self.thread.start() | |
| 374 | |
| 375 def Stop(self): | |
| 376 """Stops the download which must have been started previously.""" | |
| 377 assert self.thread, 'DownloadJob must be started before Stop is called.' | |
| 378 self.quit_event.set() | |
| 379 self.thread.join() | |
| 380 | |
| 381 def WaitFor(self): | |
| 382 """Prints a message and waits for the download to complete. The download | |
| 383 must have been started previously.""" | |
| 384 assert self.thread, 'DownloadJob must be started before WaitFor is called.' | |
| 385 self.progress_event.set() # Display progress of download. def Stop(self): | |
| 386 assert self.thread, 'DownloadJob must be started before Stop is called.' | |
| 387 self.quit_event.set() | |
| 388 self.thread.join() | |
| 389 | |
| 390 def main(argv): | |
| 391 option_parser = optparse.OptionParser() | |
| 392 | |
| 393 choices = ['mac', 'win32', 'win64', 'linux'] | |
| 394 | |
| 395 option_parser.add_option('-a', '--archive', | |
| 396 choices=choices, | |
| 397 help='Builders to repacakge from [%s].' % | |
| 398 '|'.join(choices)) | |
| 399 | |
| 400 # Verifies that the chrome executable runs | |
| 401 option_parser.add_option('-v', '--verify', | |
| 402 action='store_true', | |
| 403 help='Verifies that the Chrome executes normally' | |
| 404 'without errors') | |
| 405 | |
| 406 # This option will update the revision map. | |
| 407 option_parser.add_option('-u', '--update', | |
| 408 action='store_true', | |
| 409 help='Updates the list of revisions to repackage') | |
| 410 | |
| 411 # This option will creates the revision map. | |
| 412 option_parser.add_option('-c', '--create', | |
| 413 action='store_true', | |
| 414 help='Creates the list of revisions to repackage') | |
| 415 | |
| 416 # Original bucket that contains perf builds | |
| 417 option_parser.add_option('-o', '--original', | |
| 418 type='str', | |
| 419 help='Google storage url containing original Chrome builds') | |
| 420 | |
| 421 # Bucket that should archive lightweight perf builds | |
| 422 option_parser.add_option('-r', '--repackage', | |
| 423 type='str', | |
| 424 help='Google storage url to re-archive Chrome builds') | |
| 425 | |
| 426 verify_run = False | |
| 427 (opts, args) = option_parser.parse_args() | |
| 428 if opts.archive is None: | |
| 429 print 'Error: missing required parameter: --archive' | |
| 430 parser.print_help() | |
| 431 return 1 | |
| 432 if not opts.original or not opts.repackage: | |
| 433 raise ValueError('Need to specify original gs bucket url and' | |
| 434 'repackage gs bucket url') | |
| 435 context = PathContext(opts.original, opts.repackage, opts.archive) | |
| 436 | |
| 437 if opts.create: | |
| 438 create_upload_revision_map(context) | |
| 439 | |
| 440 if opts.update: | |
| 441 update_upload_revision_map(context) | |
| 442 | |
| 443 if opts.verify: | |
| 444 verify_run = True | |
| 445 | |
| 446 revision_map = get_revision_map(context) | |
| 447 (forward_rev, backward_rev) = get_revisions_to_package(revision_map, context) | |
| 448 base_dir = os.path.join('.', context.archive) | |
| 449 # Clears any uncleared staging directories and create one | |
| 450 bisect_repackage_utils.RemovePath(base_dir) | |
| 451 bisect_repackage_utils.MaybeMakeDirectory(base_dir) | |
| 452 staging_dir = os.path.abspath(tempfile.mkdtemp(prefix='staging_fwd', | |
| 453 dir=base_dir)) | |
| 454 staging_dir_bwd = os.path.abspath(tempfile.mkdtemp(prefix='staging_bwd', | |
| 455 dir=base_dir)) | |
| 456 backward_fetch = RepackageJob('backward_fetch', backward_rev, revision_map, | |
| 457 False, verify_run, staging_dir_bwd, | |
| 458 context) | |
| 459 try: | |
| 460 # Only run backward fetch | |
| 461 backward_fetch.Start() | |
| 462 backward_fetch.WaitFor() | |
| 463 except (KeyboardInterrupt, SystemExit): | |
| 464 print 'Cleaning up...' | |
| 465 bisect_repackage_utils.RemovePath(staging_dir) | |
| 466 bisect_repackage_utils.RemovePath(staging_dir_bwd) | |
| 467 print 'Cleaning up...' | |
| 468 bisect_repackage_utils.RemovePath(staging_dir) | |
| 469 bisect_repackage_utils.RemovePath(staging_dir_bwd) | |
| 470 | |
| 471 if '__main__' == __name__: | |
| 472 sys.exit(main(sys.argv)) | |
| OLD | NEW |