Roll src/third_party/WebKit b65f743:881586f8 (svn 202610:202611)
[chromium-blink-merge.git] / tools / sort-headers.py
blob88bbd555c3952a64037f738d231b1e2e4f647a7a
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 """Given a filename as an argument, sort the #include/#imports in that file.
8 Shows a diff and prompts for confirmation before doing the deed.
9 Works great with tools/git/for-all-touched-files.py.
10 """
12 import optparse
13 import os
14 import sys
16 from yes_no import YesNo
19 def IsInclude(line):
20 """Returns True if the line is an #include/#import/import line."""
21 return any([line.startswith('#include '), line.startswith('#import '),
22 line.startswith('import ')])
25 def IncludeCompareKey(line, for_blink):
26 """Sorting comparator key used for comparing two #include lines.
28 Returns an integer, optionally followed by a string. The integer is used
29 for coarse sorting of different categories of headers, and the string is
30 used for fine sorting of headers within categeries.
31 """
32 for prefix in ('#include ', '#import ', 'import '):
33 if line.startswith(prefix):
34 line = line[len(prefix):]
35 break
37 if for_blink:
38 # Blink likes to have its "config.h" include first.
39 if line.startswith('"config.h"'):
40 return '0'
42 # Blink sorts system headers after others. This is handled by sorting
43 # alphabetically so no need to do anything tricky.
44 return '1' + line
46 # The win32 api has all sorts of implicit include order dependencies :-/
47 # Give a few headers special sort keys that make sure they appear before all
48 # other headers.
49 if line.startswith('<windows.h>'): # Must be before e.g. shellapi.h
50 return '0'
51 if line.startswith('<atlbase.h>'): # Must be before atlapp.h.
52 return '1' + line
53 if line.startswith('<ole2.h>'): # Must be before e.g. intshcut.h
54 return '1' + line
55 if line.startswith('<unknwn.h>'): # Must be before e.g. intshcut.h
56 return '1' + line
58 # C++ system headers should come after C system headers.
59 if line.startswith('<'):
60 if line.find('.h>') != -1:
61 return '2' + line.lower()
62 else:
63 return '3' + line.lower()
65 return '4' + line
68 def SortHeader(infile, outfile, for_blink):
69 """Sorts the headers in infile, writing the sorted file to outfile."""
70 def CompareKey(line):
71 return IncludeCompareKey(line, for_blink)
73 for line in infile:
74 if IsInclude(line):
75 headerblock = []
76 while IsInclude(line):
77 infile_ended_on_include_line = False
78 headerblock.append(line)
79 # Ensure we don't die due to trying to read beyond the end of the file.
80 try:
81 line = infile.next()
82 except StopIteration:
83 infile_ended_on_include_line = True
84 break
85 for header in sorted(headerblock, key=CompareKey):
86 outfile.write(header)
87 if infile_ended_on_include_line:
88 # We already wrote the last line above; exit to ensure it isn't written
89 # again.
90 return
91 # Intentionally fall through, to write the line that caused
92 # the above while loop to exit.
93 outfile.write(line)
96 def FixFileWithConfirmFunction(filename, confirm_function,
97 perform_safety_checks, for_blink=False):
98 """Creates a fixed version of the file, invokes |confirm_function|
99 to decide whether to use the new file, and cleans up.
101 |confirm_function| takes two parameters, the original filename and
102 the fixed-up filename, and returns True to use the fixed-up file,
103 false to not use it.
105 If |perform_safety_checks| is True, then the function checks whether it is
106 unsafe to reorder headers in this file and skips the reorder with a warning
107 message in that case.
109 if perform_safety_checks and IsUnsafeToReorderHeaders(filename):
110 print ('Not reordering headers in %s as the script thinks that the '
111 'order of headers in this file is semantically significant.'
112 % (filename))
113 return
114 fixfilename = filename + '.new'
115 infile = open(filename, 'rb')
116 outfile = open(fixfilename, 'wb')
117 SortHeader(infile, outfile, for_blink)
118 infile.close()
119 outfile.close() # Important so the below diff gets the updated contents.
121 try:
122 if confirm_function(filename, fixfilename):
123 if sys.platform == 'win32':
124 os.unlink(filename)
125 os.rename(fixfilename, filename)
126 finally:
127 try:
128 os.remove(fixfilename)
129 except OSError:
130 # If the file isn't there, we don't care.
131 pass
134 def DiffAndConfirm(filename, should_confirm, perform_safety_checks, for_blink):
135 """Shows a diff of what the tool would change the file named
136 filename to. Shows a confirmation prompt if should_confirm is true.
137 Saves the resulting file if should_confirm is false or the user
138 answers Y to the confirmation prompt.
140 def ConfirmFunction(filename, fixfilename):
141 diff = os.system('diff -u %s %s' % (filename, fixfilename))
142 if sys.platform != 'win32':
143 diff >>= 8
144 if diff == 0: # Check exit code.
145 print '%s: no change' % filename
146 return False
148 return (not should_confirm or YesNo('Use new file (y/N)?'))
150 FixFileWithConfirmFunction(filename, ConfirmFunction, perform_safety_checks,
151 for_blink)
153 def IsUnsafeToReorderHeaders(filename):
154 # *_message_generator.cc is almost certainly a file that generates IPC
155 # definitions. Changes in include order in these files can result in them not
156 # building correctly.
157 if filename.find("message_generator.cc") != -1:
158 return True
159 return False
161 def main():
162 parser = optparse.OptionParser(usage='%prog filename1 filename2 ...')
163 parser.add_option('-f', '--force', action='store_false', default=True,
164 dest='should_confirm',
165 help='Turn off confirmation prompt.')
166 parser.add_option('--no_safety_checks',
167 action='store_false', default=True,
168 dest='perform_safety_checks',
169 help='Do not perform the safety checks via which this '
170 'script refuses to operate on files for which it thinks '
171 'the include ordering is semantically significant.')
172 parser.add_option('--for_blink', action='store_true', default=False,
173 dest='for_blink', help='Whether the blink header sorting '
174 'rules should be applied.')
175 opts, filenames = parser.parse_args()
177 if len(filenames) < 1:
178 parser.print_help()
179 return 1
181 for filename in filenames:
182 DiffAndConfirm(filename, opts.should_confirm, opts.perform_safety_checks,
183 opts.for_blink)
186 if __name__ == '__main__':
187 sys.exit(main())