OLD | NEW |
---|---|
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 """Moves a C++ file to a new location, updating any include paths that | 6 """Moves a C++ file to a new location, updating any include paths that |
7 point to it. Updates include guards in moved header files. Assumes | 7 point to it, and re-ordering headers as needed. Updates include |
8 Chromium coding style. | 8 guards in moved header files. Assumes Chromium coding style. |
9 | 9 |
10 Does not reorder headers; instead, use this after committing all of | 10 Attempts to update paths used in .gyp(i) files, but does not reorder |
11 your moves: | 11 or restructure .gyp(i) files in any way. |
12 ./tools/git/for-all-touched-files.py -c "tools/sort-headers.py [[FILENAME]]" | |
13 | 12 |
14 Updates paths used in .gyp(i) files, but does not reorder or | 13 Updates full-path references to files in // comments in source files. |
15 restructure .gyp(i) files in any way. | |
16 | 14 |
17 Must run in a git checkout, as it relies on git for a fast way to find | 15 Must run in a git checkout, as it relies on git grep for a fast way to |
18 files that reference the moved file. | 16 find files that reference the moved file. |
19 """ | 17 """ |
20 | 18 |
21 | 19 |
22 import os | 20 import os |
23 import re | 21 import re |
24 import subprocess | 22 import subprocess |
25 import sys | 23 import sys |
26 | 24 |
25 if __name__ == '__main__': | |
26 # Need to add the directory containing sort-headers.py to the Python | |
27 # classpath. | |
28 sys.path.append(os.path.abspath(os.path.join(sys.path[0], '..'))) | |
29 sort_headers = __import__('sort-headers') | |
30 | |
31 | |
27 HANDLED_EXTENSIONS = ['.cc', '.mm', '.h', '.hh'] | 32 HANDLED_EXTENSIONS = ['.cc', '.mm', '.h', '.hh'] |
28 | 33 |
29 | 34 |
30 def MakeDestinationPath(from_path, to_path): | 35 def MakeDestinationPath(from_path, to_path): |
31 """Given the from and to paths, return a correct destination path. | 36 """Given the from and to paths, return a correct destination path. |
32 | 37 |
33 The initial destination path may either a full path or a directory, | 38 The initial destination path may either a full path or a directory, |
34 in which case the path must end with /. Also does basic sanity | 39 in which case the path must end with /. Also does basic sanity |
35 checks. | 40 checks. |
36 """ | 41 """ |
(...skipping 10 matching lines...) Expand all Loading... | |
47 | 52 |
48 def MoveFile(from_path, to_path): | 53 def MoveFile(from_path, to_path): |
49 """Performs a git mv command to move a file from |from_path| to |to_path|. | 54 """Performs a git mv command to move a file from |from_path| to |to_path|. |
50 """ | 55 """ |
51 if not os.system('git mv %s %s' % (from_path, to_path)) == 0: | 56 if not os.system('git mv %s %s' % (from_path, to_path)) == 0: |
52 raise Exception('Fatal: Failed to run git mv command.') | 57 raise Exception('Fatal: Failed to run git mv command.') |
53 | 58 |
54 | 59 |
55 def MultiFileFindReplace(original, | 60 def MultiFileFindReplace(original, |
56 replacement, | 61 replacement, |
57 grep_pattern, | 62 file_globs): |
58 file_globs, | 63 """Implements fast multi-file find and replace. |
59 guard_formats): | |
60 """Implements fast multi-file find and replace with optional guards. | |
61 | 64 |
62 Given an |original| string and a |replacement| string, search for | 65 Given an |original| string and a |replacement| string, find matching |
63 them by formatting |grep_pattern| with |original| and running | 66 files by running git grep on |original| in files matching any |
64 git grep on the result, for files matching any of |file_globs|. | 67 pattern in |file_globs|. |
65 | 68 |
66 Once files are found, the function searches for any of | 69 Once files are found, |re.sub| is run to replace |original| with |
67 |guard_formats| formatted with |original| and replaces each match | 70 |replacement|. |replacement| may use capture group back-references. |
68 with the same guard format as matched, formatted with |replacement|. | |
69 | 71 |
70 Args: | 72 Args: |
71 original: 'chrome/browser/ui/browser.h' | 73 original: '(#(include|import)\s*["<])chrome/browser/ui/browser.h([>"])' |
72 replacement: 'chrome/browser/ui/browser/browser.h' | 74 replacement: '\1chrome/browser/ui/browser/browser.h\3' |
73 grep_pattern: r'#(include|import)\s*["<]%s[>"]' | |
74 file_globs: ['*.cc', '*.h', '*.m', '*.mm'] | 75 file_globs: ['*.cc', '*.h', '*.m', '*.mm'] |
75 guard_formats: None or ('"%s"', '<%s>') | 76 |
77 Returns the list of files modified. | |
76 | 78 |
77 Raises an exception on error. | 79 Raises an exception on error. |
78 """ | 80 """ |
79 out, err = subprocess.Popen( | 81 out, err = subprocess.Popen( |
80 ['git', 'grep', '-E', '--name-only', | 82 ['git', 'grep', '-E', '--name-only', original, '--'] + file_globs, |
81 grep_pattern % re.escape(original), '--'] + file_globs, | 83 stdout=subprocess.PIPE).communicate() |
82 stdout=subprocess.PIPE).communicate() | |
83 referees = out.splitlines() | 84 referees = out.splitlines() |
84 | 85 |
85 for referee in referees: | 86 for referee in referees: |
86 with open(referee) as f: | 87 with open(referee) as f: |
87 original_contents = f.read() | 88 original_contents = f.read() |
88 contents = original_contents | 89 contents = re.sub(original, replacement, original_contents) |
89 for guard_format in guard_formats or []: | |
90 contents = contents.replace(guard_format % original, | |
91 guard_format % replacement) | |
92 if contents == original_contents: | 90 if contents == original_contents: |
93 raise Exception('No change in file %s although matched in grep' % | 91 raise Exception('No change in file %s although matched in grep' % |
94 referee) | 92 referee) |
95 with open(referee, 'w') as f: | 93 with open(referee, 'w') as f: |
96 f.write(contents) | 94 f.write(contents) |
97 | 95 |
96 return referees | |
97 | |
98 | 98 |
99 def UpdatePostMove(from_path, to_path): | 99 def UpdatePostMove(from_path, to_path): |
100 """Given a file that has moved from |from_path| to |to_path|, | 100 """Given a file that has moved from |from_path| to |to_path|, |
101 updates the moved file's include guard to match the new path and | 101 updates the moved file's include guard to match the new path and |
102 updates all references to the file in other source files. Also tries | 102 updates all references to the file in other source files. Also tries |
103 to update references in .gyp(i) files using a heuristic. | 103 to update references in .gyp(i) files using a heuristic. |
104 """ | 104 """ |
105 # Include paths always use forward slashes. | 105 # Include paths always use forward slashes. |
106 from_path = from_path.replace('\\', '/') | 106 from_path = from_path.replace('\\', '/') |
107 to_path = to_path.replace('\\', '/') | 107 to_path = to_path.replace('\\', '/') |
108 | 108 |
109 if os.path.splitext(from_path)[1] in ['.h', '.hh']: | 109 if os.path.splitext(from_path)[1] in ['.h', '.hh']: |
110 UpdateIncludeGuard(from_path, to_path) | 110 UpdateIncludeGuard(from_path, to_path) |
111 | 111 |
112 # Update include/import references. | 112 # Update include/import references. |
113 MultiFileFindReplace( | 113 files_with_changed_includes = MultiFileFindReplace( |
114 from_path, | 114 r'(#(include|import)\s*["<])%s([>"])' % re.escape(from_path), |
115 to_path, | 115 r'\1%s\3' % to_path, |
116 r'#(include|import)\s*["<]%s[>"]', | 116 ['*.cc', '*.h', '*.m', '*.mm']) |
117 ['*.cc', '*.h', '*.m', '*.mm'], | 117 |
118 ['"%s"', '<%s>']) | 118 # Reorder headers in files that changed. |
119 for changed_file in files_with_changed_includes: | |
Nico
2012/11/21 18:35:36
I'd do `def auto_confirm(a, b): return true; FixFi
Jói
2012/11/22 09:42:50
Done.
| |
120 sort_headers.FixFileWithConfirmFunction(changed_file, lambda a, b: True) | |
121 | |
122 # Update comments; only supports // comments, which are primarily | |
123 # used in our code. | |
124 MultiFileFindReplace( | |
125 r'(//.*)%s' % re.escape(from_path), | |
126 r'\1%s' % to_path, | |
127 ['*.cc', '*.h', '*.m', '*.mm']) | |
Nico
2012/11/21 18:35:36
I'd omit this as I said. It only finds comments th
Jói
2012/11/22 09:42:50
I'll leave it in since it's safe and convenient an
| |
119 | 128 |
120 # Update references in .gyp(i) files. | 129 # Update references in .gyp(i) files. |
121 def PathMinusFirstComponent(path): | 130 def PathMinusFirstComponent(path): |
122 """foo/bar/baz -> bar/baz""" | 131 """foo/bar/baz -> bar/baz""" |
123 parts = re.split(r"[/\\]", path, 1) | 132 parts = re.split(r"[/\\]", path, 1) |
124 if len(parts) == 2: | 133 if len(parts) == 2: |
125 return parts[1] | 134 return parts[1] |
126 else: | 135 else: |
127 return parts[0] | 136 return parts[0] |
128 MultiFileFindReplace(PathMinusFirstComponent(from_path), | 137 MultiFileFindReplace( |
129 PathMinusFirstComponent(to_path), | 138 r'([\'"])%s([\'"])' % re.escape(PathMinusFirstComponent(from_path)), |
130 r'[\'"]%s[\'"]', | 139 r'\1%s\2' % PathMinusFirstComponent(to_path), |
131 ['*.gyp*'], | 140 ['*.gyp*']) |
132 ["'%s'", '"%s"']) | |
133 | 141 |
134 | 142 |
135 def MakeIncludeGuardName(path_from_root): | 143 def MakeIncludeGuardName(path_from_root): |
136 """Returns an include guard name given a path from root.""" | 144 """Returns an include guard name given a path from root.""" |
137 guard = path_from_root.replace('/', '_') | 145 guard = path_from_root.replace('/', '_') |
138 guard = guard.replace('\\', '_') | 146 guard = guard.replace('\\', '_') |
139 guard = guard.replace('.', '_') | 147 guard = guard.replace('.', '_') |
140 guard += '_' | 148 guard += '_' |
141 return guard.upper() | 149 return guard.upper() |
142 | 150 |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
183 | 191 |
184 to_path = MakeDestinationPath(from_path, to_path) | 192 to_path = MakeDestinationPath(from_path, to_path) |
185 if not already_moved: | 193 if not already_moved: |
186 MoveFile(from_path, to_path) | 194 MoveFile(from_path, to_path) |
187 UpdatePostMove(from_path, to_path) | 195 UpdatePostMove(from_path, to_path) |
188 return 0 | 196 return 0 |
189 | 197 |
190 | 198 |
191 if __name__ == '__main__': | 199 if __name__ == '__main__': |
192 sys.exit(main()) | 200 sys.exit(main()) |
OLD | NEW |