Unregister from GCM when the only GCM app is removed
[chromium-blink-merge.git] / tools / git / move_source_file.py
blobb5496fdb0a256fb02ea715299285a786477db04b
1 #!/usr/bin/env python
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
4 # found in the LICENSE file.
6 """Moves C++ files to a new location, updating any include paths that point
7 to them, and re-ordering headers as needed. If multiple source files are
8 specified, the destination must be a directory. Updates include guards in
9 moved header files. Assumes Chromium coding style.
11 Attempts to update paths used in .gyp(i) files, but does not reorder
12 or restructure .gyp(i) files in any way.
14 Updates full-path references to files in // comments in source files.
16 Must run in a git checkout, as it relies on git grep for a fast way to
17 find files that reference the moved file.
18 """
21 import optparse
22 import os
23 import re
24 import subprocess
25 import sys
27 import mffr
29 if __name__ == '__main__':
30 # Need to add the directory containing sort-headers.py to the Python
31 # classpath.
32 sys.path.append(os.path.abspath(os.path.join(sys.path[0], '..')))
33 sort_headers = __import__('sort-headers')
36 HANDLED_EXTENSIONS = ['.cc', '.mm', '.h', '.hh', '.cpp']
39 def IsHandledFile(path):
40 return os.path.splitext(path)[1] in HANDLED_EXTENSIONS
43 def MakeDestinationPath(from_path, to_path):
44 """Given the from and to paths, return a correct destination path.
46 The initial destination path may either a full path or a directory.
47 Also does basic sanity checks.
48 """
49 if not IsHandledFile(from_path):
50 raise Exception('Only intended to move individual source files '
51 '(%s does not have a recognized extension).' %
52 from_path)
54 # Remove '.', '..', etc.
55 to_path = os.path.normpath(to_path)
57 if os.path.isdir(to_path):
58 to_path = os.path.join(to_path, os.path.basename(from_path))
59 else:
60 dest_extension = os.path.splitext(to_path)[1]
61 if dest_extension not in HANDLED_EXTENSIONS:
62 raise Exception('Destination must be either a full path with '
63 'a recognized extension or a directory.')
64 return to_path
67 def MoveFile(from_path, to_path):
68 """Performs a git mv command to move a file from |from_path| to |to_path|.
69 """
70 if not os.system('git mv %s %s' % (from_path, to_path)) == 0:
71 raise Exception('Fatal: Failed to run git mv command.')
74 def UpdatePostMove(from_path, to_path):
75 """Given a file that has moved from |from_path| to |to_path|,
76 updates the moved file's include guard to match the new path and
77 updates all references to the file in other source files. Also tries
78 to update references in .gyp(i) files using a heuristic.
79 """
80 # Include paths always use forward slashes.
81 from_path = from_path.replace('\\', '/')
82 to_path = to_path.replace('\\', '/')
84 if os.path.splitext(from_path)[1] in ['.h', '.hh']:
85 UpdateIncludeGuard(from_path, to_path)
87 # Update include/import references.
88 files_with_changed_includes = mffr.MultiFileFindReplace(
89 r'(#(include|import)\s*["<])%s([>"])' % re.escape(from_path),
90 r'\1%s\3' % to_path,
91 ['*.cc', '*.h', '*.m', '*.mm', '*.cpp'])
93 # Reorder headers in files that changed.
94 for changed_file in files_with_changed_includes:
95 def AlwaysConfirm(a, b): return True
96 sort_headers.FixFileWithConfirmFunction(changed_file, AlwaysConfirm, True)
98 # Update comments; only supports // comments, which are primarily
99 # used in our code.
101 # This work takes a bit of time. If this script starts feeling too
102 # slow, one good way to speed it up is to make the comment handling
103 # optional under a flag.
104 mffr.MultiFileFindReplace(
105 r'(//.*)%s' % re.escape(from_path),
106 r'\1%s' % to_path,
107 ['*.cc', '*.h', '*.m', '*.mm', '*.cpp'])
109 # Update references in .gyp(i) files.
110 def PathMinusFirstComponent(path):
111 """foo/bar/baz -> bar/baz"""
112 parts = re.split(r"[/\\]", path, 1)
113 if len(parts) == 2:
114 return parts[1]
115 else:
116 return parts[0]
117 mffr.MultiFileFindReplace(
118 r'([\'"])%s([\'"])' % re.escape(PathMinusFirstComponent(from_path)),
119 r'\1%s\2' % PathMinusFirstComponent(to_path),
120 ['*.gyp*'])
122 # Update references in BUILD.gn files.
124 # Unlike .gyp(i) files, BUILD.gn files can be placed in any directories,
125 # and paths in a BUILD.gn file are relative to the directory where the
126 # BUILD.gn file is placed.
128 # For instance, "chrome/browser/chromeos/device_uma.h" is listed as
129 # "browser/chromeos/device_uma.h" in "chrome/chrome_browser_chromeos.gypi",
130 # but it's listed as "device_uma.h" in "chrome/browser/chromeos/BUILD.gn".
132 # To handle this, the code here will visit directories from the top level
133 # src directory to the directory of |from_path| and try to update BUILD.gn
134 # in each directory.
136 # The code only handles files moved/renamed within the same BUILD.gn
137 # file. If files are moved beyond the same BUILD.gn file, the affected
138 # BUILD.gn files should be fixed manually.
139 def SplitByFirstComponent(path):
140 """'foo/bar/baz' -> ('foo', 'bar/baz')
141 'bar' -> ('bar', '')
142 '' -> ('', '')
144 parts = re.split(r"[/\\]", path, 1)
145 if len(parts) == 2:
146 return (parts[0], parts[1])
147 else:
148 return (parts[0], '')
150 visiting_directory = ''
151 from_rest = from_path
152 to_rest = to_path
153 while True:
154 mffr.MultiFileFindReplace(
155 r'([\'"])%s([\'"])' % from_rest,
156 r'\1%s\2' % to_rest,
157 [os.path.join(visiting_directory, 'BUILD.gn')])
158 from_first, from_rest = SplitByFirstComponent(from_rest)
159 to_first, to_rest = SplitByFirstComponent(to_rest)
160 visiting_directory = os.path.join(visiting_directory, from_first)
161 if not from_rest or not to_rest:
162 break
165 def MakeIncludeGuardName(path_from_root):
166 """Returns an include guard name given a path from root."""
167 guard = path_from_root.replace('/', '_')
168 guard = guard.replace('\\', '_')
169 guard = guard.replace('.', '_')
170 guard += '_'
171 return guard.upper()
174 def UpdateIncludeGuard(old_path, new_path):
175 """Updates the include guard in a file now residing at |new_path|,
176 previously residing at |old_path|, with an up-to-date include guard.
178 Prints a warning if the update could not be completed successfully (e.g.,
179 because the old include guard was not formatted correctly per Chromium style).
181 old_guard = MakeIncludeGuardName(old_path)
182 new_guard = MakeIncludeGuardName(new_path)
184 with open(new_path) as f:
185 contents = f.read()
187 new_contents = contents.replace(old_guard, new_guard)
188 # The file should now have three instances of the new guard: two at the top
189 # of the file plus one at the bottom for the comment on the #endif.
190 if new_contents.count(new_guard) != 3:
191 print ('WARNING: Could not successfully update include guard; perhaps '
192 'old guard is not per style guide? You will have to update the '
193 'include guard manually. (%s)' % new_path)
195 with open(new_path, 'w') as f:
196 f.write(new_contents)
198 def main():
199 if not os.path.isdir('.git'):
200 print 'Fatal: You must run from the root of a git checkout.'
201 return 1
203 parser = optparse.OptionParser(usage='%prog FROM_PATH... TO_PATH')
204 parser.add_option('--already_moved', action='store_true',
205 dest='already_moved',
206 help='Causes the script to skip moving the file.')
207 parser.add_option('--no_error_for_non_source_file', action='store_false',
208 default='True',
209 dest='error_for_non_source_file',
210 help='Causes the script to simply print a warning on '
211 'encountering a non-source file rather than raising an '
212 'error.')
213 opts, args = parser.parse_args()
215 if len(args) < 2:
216 parser.print_help()
217 return 1
219 from_paths = args[:len(args)-1]
220 orig_to_path = args[-1]
222 if len(from_paths) > 1 and not os.path.isdir(orig_to_path):
223 print 'Target %s is not a directory.' % orig_to_path
224 print
225 parser.print_help()
226 return 1
228 for from_path in from_paths:
229 if not opts.error_for_non_source_file and not IsHandledFile(from_path):
230 print '%s does not appear to be a source file, skipping' % (from_path)
231 continue
232 to_path = MakeDestinationPath(from_path, orig_to_path)
233 if not opts.already_moved:
234 MoveFile(from_path, to_path)
235 UpdatePostMove(from_path, to_path)
236 return 0
239 if __name__ == '__main__':
240 sys.exit(main())