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
7 point to them, and re-ordering headers as needed. If multiple source
8 files are specified, the destination must be a directory (and must end
9 in a slash). Updates include guards in moved header files. Assumes
10 Chromium coding style.
12 Attempts to update paths used in .gyp(i) files, but does not reorder
13 or restructure .gyp(i) files in any way.
15 Updates full-path references to files in // comments in source files.
17 Must run in a git checkout, as it relies on git grep for a fast way to
18 find files that reference the moved file.
29 if __name__
== '__main__':
30 # Need to add the directory containing sort-headers.py to the Python
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']
39 def MakeDestinationPath(from_path
, to_path
):
40 """Given the from and to paths, return a correct destination path.
42 The initial destination path may either a full path or a directory,
43 in which case the path must end with /. Also does basic sanity
46 if os
.path
.splitext(from_path
)[1] not in HANDLED_EXTENSIONS
:
47 raise Exception('Only intended to move individual source files.')
48 dest_extension
= os
.path
.splitext(to_path
)[1]
49 if dest_extension
not in HANDLED_EXTENSIONS
:
50 if to_path
.endswith('/') or to_path
.endswith('\\'):
51 to_path
+= os
.path
.basename(from_path
)
53 raise Exception('Destination must be either full path or end with /.')
57 def MoveFile(from_path
, to_path
):
58 """Performs a git mv command to move a file from |from_path| to |to_path|.
60 if not os
.system('git mv %s %s' % (from_path
, to_path
)) == 0:
61 raise Exception('Fatal: Failed to run git mv command.')
64 def UpdatePostMove(from_path
, to_path
):
65 """Given a file that has moved from |from_path| to |to_path|,
66 updates the moved file's include guard to match the new path and
67 updates all references to the file in other source files. Also tries
68 to update references in .gyp(i) files using a heuristic.
70 # Include paths always use forward slashes.
71 from_path
= from_path
.replace('\\', '/')
72 to_path
= to_path
.replace('\\', '/')
74 if os
.path
.splitext(from_path
)[1] in ['.h', '.hh']:
75 UpdateIncludeGuard(from_path
, to_path
)
77 # Update include/import references.
78 files_with_changed_includes
= mffr
.MultiFileFindReplace(
79 r
'(#(include|import)\s*["<])%s([>"])' % re
.escape(from_path
),
81 ['*.cc', '*.h', '*.m', '*.mm'])
83 # Reorder headers in files that changed.
84 for changed_file
in files_with_changed_includes
:
85 def AlwaysConfirm(a
, b
): return True
86 sort_headers
.FixFileWithConfirmFunction(changed_file
, AlwaysConfirm
)
88 # Update comments; only supports // comments, which are primarily
91 # This work takes a bit of time. If this script starts feeling too
92 # slow, one good way to speed it up is to make the comment handling
93 # optional under a flag.
94 mffr
.MultiFileFindReplace(
95 r
'(//.*)%s' % re
.escape(from_path
),
97 ['*.cc', '*.h', '*.m', '*.mm'])
99 # Update references in .gyp(i) files.
100 def PathMinusFirstComponent(path
):
101 """foo/bar/baz -> bar/baz"""
102 parts
= re
.split(r
"[/\\]", path
, 1)
107 mffr
.MultiFileFindReplace(
108 r
'([\'"])%s([\'"])' % re.escape(PathMinusFirstComponent(from_path)),
109 r'\
1%s\
2' % PathMinusFirstComponent(to_path),
113 def MakeIncludeGuardName(path_from_root):
114 """Returns an include guard name given a path from root."""
115 guard = path_from_root.replace('/', '_
')
116 guard = guard.replace('\\', '_
')
117 guard = guard.replace('.', '_
')
122 def UpdateIncludeGuard(old_path, new_path):
123 """Updates the include guard in a file now residing at |new_path|,
124 previously residing at |old_path|, with an up-to-date include guard.
126 Prints a warning if the update could not be completed successfully (e.g.,
127 because the old include guard was not formatted correctly per Chromium style).
129 old_guard = MakeIncludeGuardName(old_path)
130 new_guard = MakeIncludeGuardName(new_path)
132 with open(new_path) as f:
135 new_contents = contents.replace(old_guard, new_guard)
136 # The file should now have three instances of the new guard: two at the top
137 # of the file plus one at the bottom for the comment on the #endif.
138 if new_contents.count(new_guard) != 3:
139 print ('WARNING
: Could
not not successfully update include guard
; perhaps
'
140 'old guard
is not per style guide? You will have to update the
'
141 'include guard manually
.')
143 with open(new_path, 'w
') as f:
144 f.write(new_contents)
148 if not os.path.isdir('.git
'):
149 print 'Fatal
: You must run
from the root of a git checkout
.'
153 already_moved = False
154 if len(args) > 0 and args[0] == '--already
-moved
':
159 print ('Usage
: move_source_file
.py
[--already
-moved
] FROM_PATH
... TO_PATH
'
163 if len(args) > 2 and not args[-1].endswith('/'):
164 print 'Target
%s is not a directory
.' % args[-1]
167 for from_path in args[:len(args)-1]:
168 to_path = MakeDestinationPath(from_path, args[-1])
169 if not already_moved:
170 MoveFile(from_path, to_path)
171 UpdatePostMove(from_path, to_path)
175 if __name__ == '__main__
':