diff options
author | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
---|---|---|
committer | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
commit | 679147eead574d186ebf3069647b4c23e8ccace6 (patch) | |
tree | fc247a0ac8ff119f7c8550879ebb6d3dd8d1ff69 /chromium/tools/git/move_source_file.py | |
download | qtwebengine-chromium-679147eead574d186ebf3069647b4c23e8ccace6.tar.gz |
Initial import.
Diffstat (limited to 'chromium/tools/git/move_source_file.py')
-rwxr-xr-x | chromium/tools/git/move_source_file.py | 191 |
1 files changed, 191 insertions, 0 deletions
diff --git a/chromium/tools/git/move_source_file.py b/chromium/tools/git/move_source_file.py new file mode 100755 index 00000000000..1a953f3c42b --- /dev/null +++ b/chromium/tools/git/move_source_file.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python +# Copyright (c) 2012 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. + +"""Moves C++ files to a new location, updating any include paths that +point to them, and re-ordering headers as needed. If multiple source +files are specified, the destination must be a directory (and must end +in a slash). Updates include guards in moved header files. Assumes +Chromium coding style. + +Attempts to update paths used in .gyp(i) files, but does not reorder +or restructure .gyp(i) files in any way. + +Updates full-path references to files in // comments in source files. + +Must run in a git checkout, as it relies on git grep for a fast way to +find files that reference the moved file. +""" + + +import optparse +import os +import re +import subprocess +import sys + +import mffr + +if __name__ == '__main__': + # Need to add the directory containing sort-headers.py to the Python + # classpath. + sys.path.append(os.path.abspath(os.path.join(sys.path[0], '..'))) +sort_headers = __import__('sort-headers') + + +HANDLED_EXTENSIONS = ['.cc', '.mm', '.h', '.hh'] + + +def IsHandledFile(path): + return os.path.splitext(path)[1] in HANDLED_EXTENSIONS + +def MakeDestinationPath(from_path, to_path): + """Given the from and to paths, return a correct destination path. + + The initial destination path may either a full path or a directory, + in which case the path must end with /. Also does basic sanity + checks. + """ + if not IsHandledFile(from_path): + raise Exception('Only intended to move individual source files. (%s)' % + from_path) + dest_extension = os.path.splitext(to_path)[1] + if dest_extension not in HANDLED_EXTENSIONS: + if to_path.endswith('/') or to_path.endswith('\\'): + to_path += os.path.basename(from_path) + else: + raise Exception('Destination must be either full path or end with /.') + return to_path + + +def MoveFile(from_path, to_path): + """Performs a git mv command to move a file from |from_path| to |to_path|. + """ + if not os.system('git mv %s %s' % (from_path, to_path)) == 0: + raise Exception('Fatal: Failed to run git mv command.') + + +def UpdatePostMove(from_path, to_path): + """Given a file that has moved from |from_path| to |to_path|, + updates the moved file's include guard to match the new path and + updates all references to the file in other source files. Also tries + to update references in .gyp(i) files using a heuristic. + """ + # Include paths always use forward slashes. + from_path = from_path.replace('\\', '/') + to_path = to_path.replace('\\', '/') + + if os.path.splitext(from_path)[1] in ['.h', '.hh']: + UpdateIncludeGuard(from_path, to_path) + + # Update include/import references. + files_with_changed_includes = mffr.MultiFileFindReplace( + r'(#(include|import)\s*["<])%s([>"])' % re.escape(from_path), + r'\1%s\3' % to_path, + ['*.cc', '*.h', '*.m', '*.mm']) + + # Reorder headers in files that changed. + for changed_file in files_with_changed_includes: + def AlwaysConfirm(a, b): return True + sort_headers.FixFileWithConfirmFunction(changed_file, AlwaysConfirm, True) + + # Update comments; only supports // comments, which are primarily + # used in our code. + # + # This work takes a bit of time. If this script starts feeling too + # slow, one good way to speed it up is to make the comment handling + # optional under a flag. + mffr.MultiFileFindReplace( + r'(//.*)%s' % re.escape(from_path), + r'\1%s' % to_path, + ['*.cc', '*.h', '*.m', '*.mm']) + + # Update references in .gyp(i) files. + def PathMinusFirstComponent(path): + """foo/bar/baz -> bar/baz""" + parts = re.split(r"[/\\]", path, 1) + if len(parts) == 2: + return parts[1] + else: + return parts[0] + mffr.MultiFileFindReplace( + r'([\'"])%s([\'"])' % re.escape(PathMinusFirstComponent(from_path)), + r'\1%s\2' % PathMinusFirstComponent(to_path), + ['*.gyp*']) + + +def MakeIncludeGuardName(path_from_root): + """Returns an include guard name given a path from root.""" + guard = path_from_root.replace('/', '_') + guard = guard.replace('\\', '_') + guard = guard.replace('.', '_') + guard += '_' + return guard.upper() + + +def UpdateIncludeGuard(old_path, new_path): + """Updates the include guard in a file now residing at |new_path|, + previously residing at |old_path|, with an up-to-date include guard. + + Prints a warning if the update could not be completed successfully (e.g., + because the old include guard was not formatted correctly per Chromium style). + """ + old_guard = MakeIncludeGuardName(old_path) + new_guard = MakeIncludeGuardName(new_path) + + with open(new_path) as f: + contents = f.read() + + new_contents = contents.replace(old_guard, new_guard) + # The file should now have three instances of the new guard: two at the top + # of the file plus one at the bottom for the comment on the #endif. + if new_contents.count(new_guard) != 3: + print ('WARNING: Could not successfully update include guard; perhaps ' + 'old guard is not per style guide? You will have to update the ' + 'include guard manually. (%s)' % new_path) + + with open(new_path, 'w') as f: + f.write(new_contents) + + +def main(): + if not os.path.isdir('.git'): + print 'Fatal: You must run from the root of a git checkout.' + return 1 + + parser = optparse.OptionParser(usage='%prog FROM_PATH... TO_PATH') + parser.add_option('--already_moved', action='store_true', + dest='already_moved', + help='Causes the script to skip moving the file.') + parser.add_option('--no_error_for_non_source_file', action='store_false', + default='True', + dest='error_for_non_source_file', + help='Causes the script to simply print a warning on ' + 'encountering a non-source file rather than raising an ' + 'error.') + opts, args = parser.parse_args() + + if len(args) < 2: + parser.print_help() + return 1 + + if len(args) > 2 and not args[-1].endswith('/'): + print 'Target %s is not a directory.' % args[-1] + print + parser.print_help() + return 1 + + for from_path in args[:len(args)-1]: + if not opts.error_for_non_source_file and not IsHandledFile(from_path): + print '%s does not appear to be a source file, skipping' % (from_path) + continue + to_path = MakeDestinationPath(from_path, args[-1]) + if not opts.already_moved: + MoveFile(from_path, to_path) + UpdatePostMove(from_path, to_path) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) |