blob: 09427aef2c25b26ce938927566004c07c86b2694 [file] [log] [blame]
[email protected]cb155a82011-11-29 17:25:341#!/usr/bin/env python
[email protected]08079092012-01-05 18:24:382# Copyright (c) 2012 The Chromium Authors. All rights reserved.
[email protected]377bf4a2011-05-19 20:17:113# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
[email protected]50e5a3d2010-08-26 00:23:265
6"""Given a filename as an argument, sort the #include/#imports in that file.
7
8Shows a diff and prompts for confirmation before doing the deed.
[email protected]10ab0ed52011-11-01 11:46:529Works great with tools/git/for-all-touched-files.py.
[email protected]50e5a3d2010-08-26 00:23:2610"""
11
Raul Tambre3c5b5292019-09-26 18:35:3112from __future__ import print_function
13
[email protected]50e5a3d2010-08-26 00:23:2614import optparse
15import os
16import sys
[email protected]50e5a3d2010-08-26 00:23:2617
satorux3d23ca02015-02-10 08:04:5118from yes_no import YesNo
[email protected]50e5a3d2010-08-26 00:23:2619
20
benwells5c5d6f92015-07-15 06:08:0221def IsInclude(line):
22 """Returns True if the line is an #include/#import/import line."""
23 return any([line.startswith('#include '), line.startswith('#import '),
24 line.startswith('import ')])
25
26
27def IncludeCompareKey(line, for_blink):
[email protected]50e5a3d2010-08-26 00:23:2628 """Sorting comparator key used for comparing two #include lines.
benwells5c5d6f92015-07-15 06:08:0229
30 Returns an integer, optionally followed by a string. The integer is used
31 for coarse sorting of different categories of headers, and the string is
32 used for fine sorting of headers within categeries.
[email protected]50e5a3d2010-08-26 00:23:2633 """
[email protected]5650b4a42014-04-09 00:52:1534 for prefix in ('#include ', '#import ', 'import '):
[email protected]50e5a3d2010-08-26 00:23:2635 if line.startswith(prefix):
[email protected]d5c49322011-05-19 20:08:5736 line = line[len(prefix):]
37 break
[email protected]51e3da52011-05-20 01:53:0638
benwells5c5d6f92015-07-15 06:08:0239 if for_blink:
40 # Blink likes to have its "config.h" include first.
41 if line.startswith('"config.h"'):
42 return '0'
43
44 # Blink sorts system headers after others. This is handled by sorting
45 # alphabetically so no need to do anything tricky.
46 return '1' + line
47
[email protected]51e3da52011-05-20 01:53:0648 # The win32 api has all sorts of implicit include order dependencies :-/
49 # Give a few headers special sort keys that make sure they appear before all
50 # other headers.
51 if line.startswith('<windows.h>'): # Must be before e.g. shellapi.h
52 return '0'
[email protected]d5d71052013-02-25 21:01:3553 if line.startswith('<atlbase.h>'): # Must be before atlapp.h.
54 return '1' + line
thestigc5d32952014-08-25 22:32:3555 if line.startswith('<ole2.h>'): # Must be before e.g. intshcut.h
56 return '1' + line
[email protected]51e3da52011-05-20 01:53:0657 if line.startswith('<unknwn.h>'): # Must be before e.g. intshcut.h
[email protected]d5d71052013-02-25 21:01:3558 return '1' + line
[email protected]51e3da52011-05-20 01:53:0659
[email protected]08079092012-01-05 18:24:3860 # C++ system headers should come after C system headers.
61 if line.startswith('<'):
62 if line.find('.h>') != -1:
[email protected]2661bb92013-03-13 21:24:4063 return '2' + line.lower()
[email protected]08079092012-01-05 18:24:3864 else:
[email protected]2661bb92013-03-13 21:24:4065 return '3' + line.lower()
[email protected]08079092012-01-05 18:24:3866
67 return '4' + line
[email protected]50e5a3d2010-08-26 00:23:2668
69
benwells5c5d6f92015-07-15 06:08:0270def SortHeader(infile, outfile, for_blink):
[email protected]50e5a3d2010-08-26 00:23:2671 """Sorts the headers in infile, writing the sorted file to outfile."""
benwells5c5d6f92015-07-15 06:08:0272 def CompareKey(line):
73 return IncludeCompareKey(line, for_blink)
74
[email protected]50e5a3d2010-08-26 00:23:2675 for line in infile:
76 if IsInclude(line):
77 headerblock = []
78 while IsInclude(line):
[email protected]1590a8e2013-06-18 14:25:5879 infile_ended_on_include_line = False
[email protected]50e5a3d2010-08-26 00:23:2680 headerblock.append(line)
[email protected]1590a8e2013-06-18 14:25:5881 # Ensure we don't die due to trying to read beyond the end of the file.
82 try:
83 line = infile.next()
84 except StopIteration:
85 infile_ended_on_include_line = True
86 break
benwells5c5d6f92015-07-15 06:08:0287 for header in sorted(headerblock, key=CompareKey):
[email protected]50e5a3d2010-08-26 00:23:2688 outfile.write(header)
[email protected]1590a8e2013-06-18 14:25:5889 if infile_ended_on_include_line:
90 # We already wrote the last line above; exit to ensure it isn't written
91 # again.
92 return
[email protected]50e5a3d2010-08-26 00:23:2693 # Intentionally fall through, to write the line that caused
94 # the above while loop to exit.
95 outfile.write(line)
96
97
[email protected]1590a8e2013-06-18 14:25:5898def FixFileWithConfirmFunction(filename, confirm_function,
benwells5c5d6f92015-07-15 06:08:0299 perform_safety_checks, for_blink=False):
[email protected]18367222012-11-22 11:28:57100 """Creates a fixed version of the file, invokes |confirm_function|
101 to decide whether to use the new file, and cleans up.
102
103 |confirm_function| takes two parameters, the original filename and
104 the fixed-up filename, and returns True to use the fixed-up file,
105 false to not use it.
[email protected]1590a8e2013-06-18 14:25:58106
107 If |perform_safety_checks| is True, then the function checks whether it is
108 unsafe to reorder headers in this file and skips the reorder with a warning
109 message in that case.
[email protected]10ab0ed52011-11-01 11:46:52110 """
[email protected]1590a8e2013-06-18 14:25:58111 if perform_safety_checks and IsUnsafeToReorderHeaders(filename):
Raul Tambre3c5b5292019-09-26 18:35:31112 print(
113 'Not reordering headers in %s as the script thinks that the '
114 'order of headers in this file is semantically significant.' % filename)
[email protected]1590a8e2013-06-18 14:25:58115 return
[email protected]10ab0ed52011-11-01 11:46:52116 fixfilename = filename + '.new'
[email protected]4a2a50cb2013-06-04 06:27:38117 infile = open(filename, 'rb')
118 outfile = open(fixfilename, 'wb')
benwells5c5d6f92015-07-15 06:08:02119 SortHeader(infile, outfile, for_blink)
[email protected]10ab0ed52011-11-01 11:46:52120 infile.close()
121 outfile.close() # Important so the below diff gets the updated contents.
122
123 try:
[email protected]18367222012-11-22 11:28:57124 if confirm_function(filename, fixfilename):
[email protected]4a2a50cb2013-06-04 06:27:38125 if sys.platform == 'win32':
126 os.unlink(filename)
[email protected]10ab0ed52011-11-01 11:46:52127 os.rename(fixfilename, filename)
128 finally:
129 try:
130 os.remove(fixfilename)
131 except OSError:
132 # If the file isn't there, we don't care.
133 pass
134
135
benwells5c5d6f92015-07-15 06:08:02136def DiffAndConfirm(filename, should_confirm, perform_safety_checks, for_blink):
[email protected]18367222012-11-22 11:28:57137 """Shows a diff of what the tool would change the file named
138 filename to. Shows a confirmation prompt if should_confirm is true.
139 Saves the resulting file if should_confirm is false or the user
140 answers Y to the confirmation prompt.
141 """
142 def ConfirmFunction(filename, fixfilename):
143 diff = os.system('diff -u %s %s' % (filename, fixfilename))
[email protected]4a2a50cb2013-06-04 06:27:38144 if sys.platform != 'win32':
145 diff >>= 8
146 if diff == 0: # Check exit code.
Raul Tambre3c5b5292019-09-26 18:35:31147 print('%s: no change' % filename)
[email protected]18367222012-11-22 11:28:57148 return False
149
150 return (not should_confirm or YesNo('Use new file (y/N)?'))
151
benwells5c5d6f92015-07-15 06:08:02152 FixFileWithConfirmFunction(filename, ConfirmFunction, perform_safety_checks,
153 for_blink)
[email protected]18367222012-11-22 11:28:57154
[email protected]1590a8e2013-06-18 14:25:58155def IsUnsafeToReorderHeaders(filename):
156 # *_message_generator.cc is almost certainly a file that generates IPC
157 # definitions. Changes in include order in these files can result in them not
158 # building correctly.
159 if filename.find("message_generator.cc") != -1:
160 return True
161 return False
[email protected]18367222012-11-22 11:28:57162
[email protected]50e5a3d2010-08-26 00:23:26163def main():
164 parser = optparse.OptionParser(usage='%prog filename1 filename2 ...')
[email protected]10ab0ed52011-11-01 11:46:52165 parser.add_option('-f', '--force', action='store_false', default=True,
166 dest='should_confirm',
167 help='Turn off confirmation prompt.')
[email protected]1590a8e2013-06-18 14:25:58168 parser.add_option('--no_safety_checks',
169 action='store_false', default=True,
170 dest='perform_safety_checks',
171 help='Do not perform the safety checks via which this '
172 'script refuses to operate on files for which it thinks '
173 'the include ordering is semantically significant.')
benwells5c5d6f92015-07-15 06:08:02174 parser.add_option('--for_blink', action='store_true', default=False,
175 dest='for_blink', help='Whether the blink header sorting '
176 'rules should be applied.')
[email protected]10ab0ed52011-11-01 11:46:52177 opts, filenames = parser.parse_args()
[email protected]50e5a3d2010-08-26 00:23:26178
[email protected]10ab0ed52011-11-01 11:46:52179 if len(filenames) < 1:
[email protected]50e5a3d2010-08-26 00:23:26180 parser.print_help()
[email protected]cb155a82011-11-29 17:25:34181 return 1
[email protected]50e5a3d2010-08-26 00:23:26182
[email protected]10ab0ed52011-11-01 11:46:52183 for filename in filenames:
benwells5c5d6f92015-07-15 06:08:02184 DiffAndConfirm(filename, opts.should_confirm, opts.perform_safety_checks,
185 opts.for_blink)
[email protected]50e5a3d2010-08-26 00:23:26186
187
188if __name__ == '__main__':
[email protected]cb155a82011-11-29 17:25:34189 sys.exit(main())