| Index: tools/bisect_repackage/bisect_repackage_linux.py
|
| diff --git a/tools/bisect_repackage/bisect_repackage_linux.py b/tools/bisect_repackage/bisect_repackage_linux.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..0c1af87fad9eaebef026c6b8dd4292c1c8b0fe81
|
| --- /dev/null
|
| +++ b/tools/bisect_repackage/bisect_repackage_linux.py
|
| @@ -0,0 +1,386 @@
|
| +# Copyright (c) 2016 The Chromium Authors. All rights reserved.
|
| +# Use of this source code is governed by a BSD-style license that can be
|
| +# found in the LICENSE file.
|
| +
|
| +""" Bisect repackage tool for Linux
|
| +
|
| +This script repacakges chrome builds for manual bisect script.
|
| +"""
|
| +
|
| +#Declares required files to run manual bisect script on chrome Linux
|
| +#builds in perf. Binary files that should be stripped to reduce zip file
|
| +#size are declared. The file list was gotten from the local chrome
|
| +#executable path in Linux. (This can be retrieved by typing 'chrome://version'
|
| +#in chrome and following the executable path. The list needs to be updated if
|
| +#future chrome versions require additional files.
|
| +FILES_TO_ARCHIVE = [
|
| + 'chrome',
|
| + 'chrome_100_percent.pak',
|
| + 'chrome_200_percent.pak',
|
| + 'default_apps',
|
| + 'icudtl.dat',
|
| + 'libwidevinecdm.so',
|
| + 'locales',
|
| + 'nacl_helper',
|
| + 'nacl_helper_bootstrap',
|
| + 'nacl_irt_x86_64.nexe',
|
| + 'natives_blob.bin',
|
| + 'PepperFlash',
|
| + 'product_logo_48.png',
|
| + 'resources.pak',
|
| + 'snapshot_blob.bin',
|
| + 'xdg-mime',
|
| + 'xdg-settings',
|
| +]
|
| +
|
| +# Declares what files should be stripped of symbols to reduce the archive size.
|
| +STRIP_LIST = [
|
| + 'chrome',
|
| + 'nacl_helper'
|
| +]
|
| +
|
| +# API to convert Githash to Commit position number.
|
| +CHROMIUM_GITHASH_TO_SVN_URL = (
|
| + 'https://cr-rev.appspot.com/_ah/api/crrev/v1/commit/%s')
|
| +
|
| +BUILDER_NAME = 'Linux Builder'
|
| +ARCHIVE_PREFIX = 'full-build-linux'
|
| +REVISION_MAP_FILE = 'revision_map.json'
|
| +
|
| +################################################################################
|
| +import os
|
| +import bisect_repackage_utils
|
| +import zipfile
|
| +import zlib
|
| +import json
|
| +import sys
|
| +import optparse
|
| +import re
|
| +import urllib
|
| +import threading
|
| +from multiprocessing import Pool
|
| +from functools import partial
|
| +import tempfile
|
| +import logging
|
| +
|
| +class PathContext(object):
|
| + """A PathContext is used to carry the information used to construct URLs and
|
| + paths when dealing with the storage server and archives."""
|
| + def __init__(self, original_gs_url, repackage_gs_url,
|
| + builder_name=BUILDER_NAME, file_prefix=ARCHIVE_PREFIX,
|
| + revision_file=REVISION_MAP_FILE):
|
| + super(PathContext, self).__init__()
|
| + self.original_gs_url = original_gs_url
|
| + self.repackage_gs_url = repackage_gs_url
|
| + self.builder_name = builder_name
|
| + self.file_prefix = file_prefix
|
| + self.revision_file = revision_file
|
| +
|
| +
|
| +def get_cp_from_hash(git_hash):
|
| + """Converts a GitHash to Commit position number"""
|
| + json_url = CHROMIUM_GITHASH_TO_SVN_URL % git_hash
|
| + response = urllib.urlopen(json_url)
|
| + if response.getcode() == 200:
|
| + try:
|
| + data = json.loads(response.read())
|
| + except ValueError:
|
| + logging.warning('ValueError for JSON URL: %s' % json_url)
|
| + raise ValueError('Failed to extract commit position')
|
| + else:
|
| + logging.warning('ValueError for JSON URL: %s' % json_url)
|
| + raise ValueError('Failed to extract commit position')
|
| + if 'number' in data:
|
| + return data['number']
|
| + logging.warning('Failed to get svn revision number for %s' % git_hash)
|
| + raise ValueError('Failed to extract commit position')
|
| +
|
| +def create_cp_from_hash_map(hash_list):
|
| + """Creates a dictionary that maps from Commit positio number
|
| + to corresponding GitHash"""
|
| + hash_map = {}
|
| + count = 0
|
| + for git_hash in hash_list:
|
| + try:
|
| + cp_num = get_cp_from_hash(git_hash)
|
| + hash_map[cp_num] = git_hash
|
| + print "Downloaded %s / %s git hash" %(count, len(hash_list))
|
| + count += 1
|
| + except ValueError:
|
| + pass
|
| + return hash_map
|
| +
|
| +def get_list_of_suffix(bucket_address, prefix, filter_function):
|
| + """Gets the list of suffixes in files in a google storage bucket.
|
| + For example, a google storage bucket containing one file
|
| + 'full-build-linux_20983' will return ['20983'] if prefix is
|
| + provided as 'full-build-linux'. Google Storage bucket
|
| + containing multiple files will return multiple suffixes. """
|
| + file_list = bisect_repackage_utils.GSutilList(bucket_address)
|
| + suffix_list = []
|
| + extract_suffix = '.*?%s_(.*?)\.zip' %(prefix)
|
| + for file in file_list:
|
| + match = re.match(extract_suffix, file)
|
| + if match and filter_function(match.groups()[0]):
|
| + suffix_list.append(match.groups()[0])
|
| + return suffix_list
|
| +
|
| +def download_build(cp_num, revision_map, zip_file_name, context):
|
| + """ Download a single build corresponding to the cp_num and context."""
|
| + file_url = '%s/%s/%s_%s.zip' %(context.original_gs_url, context.builder_name,
|
| + context.file_prefix, revision_map[cp_num])
|
| + bisect_repackage_utils.GSUtilDownloadFile(file_url, zip_file_name)
|
| +
|
| +def upload_build(zip_file, context):
|
| + """Uploads a single build in zip_file to the repackage_gs_url in context."""
|
| + gs_base_url = '%s/%s' %(context.repackage_gs_url, context.builder_name)
|
| + upload_url = gs_base_url + '/'
|
| + bisect_repackage_utils.GSUtilCopy(zip_file, upload_url)
|
| +
|
| +def download_revision_map(context):
|
| + """Downloads the revision map in original_gs_url in context."""
|
| + gs_base_url = '%s/%s' %(context.repackage_gs_url, context.builder_name)
|
| + download_url = gs_base_url + '/' + context.revision_file
|
| + try:
|
| + bisect_repackage_utils.GSUtilDownloadFile(download_url,
|
| + context.revision_file)
|
| + except Exception, e:
|
| + raise ValueError('Revision map does not exist. Re run the program with'
|
| + '-c option to upload a revision map to google storage')
|
| +
|
| +def get_revision_map(context):
|
| + """Downloads and returns the revision map in repackage_gs_url in context."""
|
| + bisect_repackage_utils.RemoveFile(context.revision_file)
|
| + download_revision_map(context)
|
| + with open(context.revision_file, 'r') as f:
|
| + revision_map = json.load(f)
|
| + bisect_repackage_utils.RemoveFile(context.revision_file)
|
| + return revision_map
|
| +
|
| +def upload_revision_map(revision_map, context):
|
| + """Upload the given revision_map to the repackage_gs_url in context."""
|
| + with open(context.revision_file, 'w') as fp:
|
| + json.dump(revision_map, fp)
|
| + gs_base_url = '%s/%s' %(context.repackage_gs_url, context.builder_name)
|
| + upload_url = gs_base_url + '/'
|
| + bisect_repackage_utils.GSUtilCopy(context.revision_file, upload_url)
|
| + bisect_repackage_utils.RemoveFile(context.revision_file)
|
| +
|
| +def create_upload_revision_map(context):
|
| + """ Creates and uploads a dictionary that maps from GitHash to CP number."""
|
| + gs_base_url = '%s/%s' %(context.original_gs_url, context.builder_name)
|
| + hash_list = get_list_of_suffix(gs_base_url, context.file_prefix,
|
| + bisect_repackage_utils.isGitCommitHash)
|
| + cp_num_to_hash_map = create_cp_from_hash_map(hash_list)
|
| + upload_revision_map(cp_num_to_hash_map, context)
|
| +
|
| +def update_upload_revision_map(context):
|
| + """ Updates and uploads a dictionary that maps from GitHash to CP number."""
|
| + gs_base_url = '%s/%s' %(context.original_gs_url, context.builder_name)
|
| + revision_map = get_revision_map(context)
|
| + hash_list = get_list_of_suffix(gs_base_url, context.file_prefix,
|
| + bisect_repackage_utils.isGitCommitHash)
|
| + hash_list = list(set(hash_list)-set(revision_map.values()))
|
| + cp_num_to_hash_map = create_cp_from_hash_map(hash_list)
|
| + merged_dict = dict(cp.num_to_hash_map.items() + revision_map.items())
|
| + upload_revision_map(cp_num_to_hash_map, context)
|
| +
|
| +def make_lightweight_archive(file_archive, archive_name):
|
| + """Repackages and strips the archive according to FILES_TO_ARCHIVE and
|
| + STRIP_LIST defined on the top."""
|
| + return bisect_repackage_utils.MakeZip('.',
|
| + archive_name,
|
| + FILES_TO_ARCHIVE,
|
| + file_archive,
|
| + raise_error=False,
|
| + strip_files=STRIP_LIST)
|
| +
|
| +def remove_created_files_and_path(files, paths):
|
| + """ Removes all the files and paths passed in."""
|
| + for file in files:
|
| + bisect_repackage_utils.RemoveFile(file)
|
| + for path in paths:
|
| + bisect_repackage_utils.RemovePath(path)
|
| +
|
| +def verify_chrome_run(zip_dir):
|
| + """This function executes chrome executable in zip_dir. Raises error if the
|
| + execution fails for any reason."""
|
| + try:
|
| + command = [os.path.join(zip_dir, 'chrome')]
|
| + code = bisect_repackage_utils.RunCommand(command)
|
| + if code != 0:
|
| + raise ValueError("Executing Chrome Failed: Need to check ")
|
| + except Exception, e:
|
| + raise ValueError("Executing Chrome Failed: Need to check ")
|
| +
|
| +def repackage_single_revision(revision_map, isForwardArchive, verify_run,
|
| + staging_dir, context, cp_num):
|
| + """Repackages a single Chrome build for manual bisect."""
|
| + archive_name = '%s_%s' %(context.file_prefix, cp_num)
|
| + file_archive = os.path.join(staging_dir, archive_name)
|
| + zip_file_name = '%s.zip' %file_archive
|
| +
|
| + bisect_repackage_utils.RemoveFile(zip_file_name)
|
| + download_build(cp_num, revision_map, zip_file_name, context)
|
| + extract_dir = os.path.join(staging_dir, archive_name)
|
| + bisect_repackage_utils.ExtractZip(zip_file_name, extract_dir)
|
| + extracted_folder = os.path.join(extract_dir, context.file_prefix)
|
| +
|
| + (zip_dir, zip_file) = make_lightweight_archive(extracted_folder, archive_name)
|
| +
|
| + if verify_run:
|
| + verify_chrome_run(zip_dir)
|
| +
|
| + upload_build(zip_file, context)
|
| + # Removed temporary files created during repackaging process.
|
| + remove_created_files_and_path([zip_file, zip_file_name],
|
| + [zip_dir, extract_dir])
|
| +
|
| +def repackage_revisions(revisions, revision_map, isForwardArchive, verify_run,
|
| + staging_dir, context, quit_event=None,
|
| + progress_event=None):
|
| + """ Repackages all Chrome builds listed in revisions. This function calls
|
| + 'repackage_single_revision' with multithreading pool.'"""
|
| + p = Pool(3)
|
| + func = partial(repackage_single_revision, revision_map, isForwardArchive,
|
| + verify_run, staging_dir, context)
|
| + p.imap(func, revisions)
|
| + p.close()
|
| + p.join()
|
| +
|
| +def get_uploaded_builds(context):
|
| + """Returns already uploaded commit positions numbers in
|
| + context.repackage_gs_url"""
|
| + gs_base_url = '%s/%s' %(context.repackage_gs_url, context.builder_name)
|
| + return get_list_of_suffix(gs_base_url, context.file_prefix,
|
| + bisect_repackage_utils.isCommitPosition)
|
| +
|
| +def get_revisions_to_package(revision_map, context):
|
| + """ Returns revisions that need to be repackaged. The first half of
|
| + the revisions will be sorted in ascending order and the second half of
|
| + the revisions will be sorted in desending order.
|
| +
|
| + The first half will be used for repackaging revisions in forward direction,
|
| + and the second half will be used for repackaging revisions in backward
|
| + direction."""
|
| + already_packaged = get_uploaded_builds(context)
|
| + not_already_packaged = list(set(revision_map.keys())-set(already_packaged))
|
| + revisions_to_package = sorted(not_already_packaged)
|
| + revisions_to_package = filter(lambda a: a != 'null', revisions_to_package)
|
| +
|
| + forward_rev = revisions_to_package[:len(revisions_to_package)/2]
|
| + backward_rev = sorted(revisions_to_package[len(revisions_to_package)/2:],
|
| + reverse=True)
|
| + return (forward_rev, backward_rev)
|
| +
|
| +class RepackageJob(object):
|
| + def __init__(self, name, revisions_to_package, revision_map, isForwardArchive,
|
| + verify_run, staging_dir, context):
|
| + super(RepackageJob, self).__init__()
|
| + self.name = name
|
| + self.revisions_to_package = revisions_to_package
|
| + self.revision_map = revision_map
|
| + self.isForwardArchive = isForwardArchive
|
| + self.verify_run = verify_run
|
| + self.staging_dir = staging_dir
|
| + self.context = context
|
| + self.quit_event = threading.Event()
|
| + self.progress_event = threading.Event()
|
| + self.thread = None
|
| +
|
| + def Start(self):
|
| + """Starts the download."""
|
| + fetchargs = (self.revisions_to_package,
|
| + self.revision_map,
|
| + self.isForwardArchive,
|
| + self.verify_run,
|
| + self.staging_dir,
|
| + self.context,
|
| + self.quit_event,
|
| + self.progress_event)
|
| + self.thread = threading.Thread(target=repackage_revisions,
|
| + name=self.name,
|
| + args=fetchargs)
|
| + self.thread.start()
|
| +
|
| + def Stop(self):
|
| + """Stops the download which must have been started previously."""
|
| + assert self.thread, 'DownloadJob must be started before Stop is called.'
|
| + self.quit_event.set()
|
| + self.thread.join()
|
| +
|
| + def WaitFor(self):
|
| + """Prints a message and waits for the download to complete. The download
|
| + must have been started previously."""
|
| + assert self.thread, 'DownloadJob must be started before WaitFor is called.'
|
| + self.progress_event.set() # Display progress of download. def Stop(self):
|
| + assert self.thread, 'DownloadJob must be started before Stop is called.'
|
| + self.quit_event.set()
|
| + self.thread.join()
|
| +
|
| +def main(argv):
|
| + option_parser = optparse.OptionParser()
|
| +
|
| + # Verifies that the chrome executable runs
|
| + option_parser.add_option('-v', '--verify',
|
| + action='store_true',
|
| + help='Verifies that the Chrome executes normally'
|
| + 'without errors')
|
| +
|
| + # This option will update the revision map.
|
| + option_parser.add_option('-u', '--update',
|
| + action='store_true',
|
| + help='Updates the list of revisions to repackage')
|
| +
|
| + # This option will creates the revision map.
|
| + option_parser.add_option('-c', '--create',
|
| + action='store_true',
|
| + help='Creates the list of revisions to repackage')
|
| +
|
| + # Original bucket that contains perf builds
|
| + option_parser.add_option('-o', '--original',
|
| + type='str',
|
| + help='Google storage url containing original Chrome builds')
|
| +
|
| + # Bucket that should archive lightweight perf builds
|
| + option_parser.add_option('-r', '--repackage',
|
| + type='str',
|
| + help='Google storage url to re-archive Chrome builds')
|
| +
|
| + verify_run = False
|
| + (opts, args) = option_parser.parse_args()
|
| + if not opts.original or not opts.repackage:
|
| + raise ValueError('Need to specify original gs bucket url and'
|
| + 'repackage gs bucket url')
|
| + context = PathContext(opts.original, opts.repackage)
|
| +
|
| + if opts.create:
|
| + create_upload_revision_map(context)
|
| +
|
| + if opts.update:
|
| + update_upload_revision_map(context)
|
| +
|
| + if opts.verify:
|
| + verify_run = True
|
| +
|
| + revision_map = get_revision_map(context)
|
| + (forward_rev, backward_rev) = get_revisions_to_package(revision_map, context)
|
| + bisect_repackage_utils.MaybeMakeDirectory(STAGING_DIR)
|
| + bisect_repackage_utils.MaybeMakeDirectory(STAGING_DIR_BACKWARD)
|
| + staging_dir = tempfile.mkdtemp(prefix='staging_fwd')
|
| + staging_dir_bwd = tempfile.mkdtemp(prefix='staging_bwd')
|
| +
|
| + # Spwan two threads: One repackaging from oldest builds. The other repackaging
|
| + # from the newest builds.
|
| + forward_fetch = RepackageJob('forward_fetch', forward_rev, revision_map, True,
|
| + verify_run, staging_dir, context)
|
| + backward_fetch = RepackageJob('backward_fetch', backward_rev, revision_map,
|
| + False, verify_run, staging_dir_bwd,
|
| + context)
|
| + forward_fetch.Start()
|
| + backward_fetch.Start()
|
| + forward_fetch.WaitFor()
|
| + backward_fetch.WaitFor()
|
| +
|
| +if '__main__' == __name__:
|
| + sys.exit(main(sys.argv))
|
|
|