# Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights # reserved. Use of this source code is governed by a BSD-style license that # can be found in the LICENSE file. from optparse import Option, OptionParser, OptionValueError import os import re import sys from exec_util import exec_cmd from file_util import copy_file, move_file, read_file, remove_file import git_util as git backup_ext = '.cefbak' def msg(message): """ Output a message. """ sys.stdout.write('--> ' + message + "\n") def warn(message): """ Output a warning. """ sys.stdout.write('-' * 80 + "\n") sys.stdout.write('!!!! WARNING: ' + message + "\n") sys.stdout.write('-' * 80 + "\n") def extract_paths(file): """ Extract the list of modified paths from the patch file. """ paths = [] fp = open(file) for line in fp: if line[:4] != '+++ ': continue match = re.match('^([^\t]+)', line[4:]) if not match: continue paths.append(match.group(1).strip()) return paths # Cannot be loaded as a module. if __name__ != "__main__": sys.stderr.write('This file cannot be loaded as a module!') sys.exit() # Parse command-line options. disc = """ This utility updates existing patch files. """ # Support options with multiple arguments. class MultipleOption(Option): ACTIONS = Option.ACTIONS + ("extend",) STORE_ACTIONS = Option.STORE_ACTIONS + ("extend",) TYPED_ACTIONS = Option.TYPED_ACTIONS + ("extend",) ALWAYS_TYPED_ACTIONS = Option.ALWAYS_TYPED_ACTIONS + ("extend",) def take_action(self, action, dest, opt, value, values, parser): if action == "extend": values.ensure_value(dest, []).append(value) else: Option.take_action(self, action, dest, opt, value, values, parser) parser = OptionParser(option_class=MultipleOption, description=disc) parser.add_option( '--resave', action='store_true', dest='resave', default=False, help='resave existing patch files to pick up manual changes') parser.add_option( '--reapply', action='store_true', dest='reapply', default=False, help='reapply the patch without first reverting changes') parser.add_option( '--revert', action='store_true', dest='revert', default=False, help='revert all changes from existing patch files') parser.add_option( '--backup', action='store_true', dest='backup', default=False, help='backup patched files. Used in combination with --revert.') parser.add_option( '--restore', action='store_true', dest='restore', default=False, help='restore backup of patched files that have not changed. If a backup has ' +\ 'changed the patch file will be resaved. Used in combination with --reapply.') parser.add_option( '--patch', action='extend', dest='patch', type='string', default=[], help='optional patch name to process (multiples allowed)') (options, args) = parser.parse_args() if options.resave and options.revert: print 'Invalid combination of options.' parser.print_help(sys.stderr) sys.exit() # The CEF root directory is the parent directory of _this_ script. cef_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir)) src_dir = os.path.abspath(os.path.join(cef_dir, os.pardir)) # Determine the type of Chromium checkout. if not git.is_checkout(src_dir): raise Exception('Not a valid checkout: %s' % src_dir) patch_dir = os.path.join(cef_dir, 'patch') patch_cfg = os.path.join(patch_dir, 'patch.cfg') if not os.path.isfile(patch_cfg): raise Exception('File does not exist: %s' % patch_cfg) # Read the patch configuration file. msg('Reading patch config %s' % patch_cfg) scope = {} execfile(patch_cfg, scope) patches = scope["patches"] # Read each individual patch file. patches_dir = os.path.join(patch_dir, 'patches') for patch in patches: # If specific patch names are specified only process those patches. if options.patch and not patch['name'] in options.patch: continue sys.stdout.write('\n') patch_file = os.path.join(patches_dir, patch['name'] + '.patch') if os.path.isfile(patch_file): msg('Reading patch file %s' % patch_file) if 'path' in patch: patch_root_abs = os.path.abspath(os.path.join(src_dir, patch['path'])) else: patch_root_abs = src_dir # Retrieve the list of paths modified by the patch file. patch_paths = extract_paths(patch_file) # List of paths added by the patch file. added_paths = [] # True if any backed up files have changed. has_backup_changes = False if not options.resave: if not options.reapply: # Revert any changes to existing files in the patch. for patch_path in patch_paths: patch_path_abs = os.path.abspath(os.path.join(patch_root_abs, \ patch_path)) if os.path.exists(patch_path_abs): if options.backup: backup_path_abs = patch_path_abs + backup_ext if not os.path.exists(backup_path_abs): msg('Creating backup of %s' % patch_path_abs) copy_file(patch_path_abs, backup_path_abs) else: msg('Skipping backup of %s' % patch_path_abs) msg('Reverting changes to %s' % patch_path_abs) cmd = 'git checkout -- %s' % (patch_path_abs) result = exec_cmd(cmd, patch_root_abs) if result['err'] != '': msg('Failed to revert file: %s' % result['err']) msg('Deleting file %s' % patch_path_abs) os.remove(patch_path_abs) added_paths.append(patch_path_abs) if result['out'] != '': sys.stdout.write(result['out']) else: msg('Skipping non-existing file %s' % patch_path_abs) added_paths.append(patch_path_abs) if not options.revert: # Apply the patch file. msg('Applying patch to %s' % patch_root_abs) patch_string = open(patch_file, 'rb').read() result = exec_cmd('patch -p0', patch_root_abs, patch_string) if result['err'] != '': raise Exception('Failed to apply patch file: %s' % result['err']) sys.stdout.write(result['out']) if result['out'].find('FAILED') != -1: warn('Failed to apply %s, fix manually and run with --resave' % \ patch['name']) continue if options.restore: # Restore from backup if a backup exists. for patch_path in patch_paths: patch_path_abs = os.path.abspath(os.path.join(patch_root_abs, \ patch_path)) backup_path_abs = patch_path_abs + backup_ext if os.path.exists(backup_path_abs): if read_file(patch_path_abs) == read_file(backup_path_abs): msg('Restoring backup of %s' % patch_path_abs) remove_file(patch_path_abs) move_file(backup_path_abs, patch_path_abs) else: msg('Discarding backup of %s' % patch_path_abs) remove_file(backup_path_abs) has_backup_changes = True else: msg('No backup of %s' % patch_path_abs) if (not options.revert and not options.reapply) or has_backup_changes: msg('Saving changes to %s' % patch_file) if added_paths: # Inform git of the added paths so they appear in the patch file. cmd = 'git add -N %s' % ' '.join(added_paths) result = exec_cmd(cmd, patch_root_abs) if result['err'] != '' and result['err'].find('warning:') != 0: raise Exception('Failed to add paths: %s' % result['err']) # Re-create the patch file. patch_paths_str = ' '.join(patch_paths) cmd = 'git diff --no-prefix --relative --ignore-space-at-eol %s' % patch_paths_str result = exec_cmd(cmd, patch_root_abs) if result['err'] != '' and result['err'].find('warning:') != 0: raise Exception('Failed to create patch file: %s' % result['err']) f = open(patch_file, 'wb') f.write(result['out']) f.close() else: raise Exception('Patch file does not exist: %s' % patch_file)