]>
Commit | Line | Data |
---|---|---|
1d09f67e TL |
1 | #!/usr/bin/env python |
2 | ||
3 | # This file has been imported into the apache source tree from | |
4 | # the IWYU source tree as of version 0.8 | |
5 | # https://github.com/include-what-you-use/include-what-you-use/blob/master/iwyu_tool.py | |
6 | # and corresponding license has been added: | |
7 | # https://github.com/include-what-you-use/include-what-you-use/blob/master/LICENSE.TXT | |
8 | # | |
9 | # ============================================================================== | |
10 | # LLVM Release License | |
11 | # ============================================================================== | |
12 | # University of Illinois/NCSA | |
13 | # Open Source License | |
14 | # | |
15 | # Copyright (c) 2003-2010 University of Illinois at Urbana-Champaign. | |
16 | # All rights reserved. | |
17 | # | |
18 | # Developed by: | |
19 | # | |
20 | # LLVM Team | |
21 | # | |
22 | # University of Illinois at Urbana-Champaign | |
23 | # | |
24 | # http://llvm.org | |
25 | # | |
26 | # Permission is hereby granted, free of charge, to any person obtaining a copy of | |
27 | # this software and associated documentation files (the "Software"), to deal with | |
28 | # the Software without restriction, including without limitation the rights to | |
29 | # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies | |
30 | # of the Software, and to permit persons to whom the Software is furnished to do | |
31 | # so, subject to the following conditions: | |
32 | # | |
33 | # * Redistributions of source code must retain the above copyright notice, | |
34 | # this list of conditions and the following disclaimers. | |
35 | # | |
36 | # * Redistributions in binary form must reproduce the above copyright notice, | |
37 | # this list of conditions and the following disclaimers in the | |
38 | # documentation and/or other materials provided with the distribution. | |
39 | # | |
40 | # * Neither the names of the LLVM Team, University of Illinois at | |
41 | # Urbana-Champaign, nor the names of its contributors may be used to | |
42 | # endorse or promote products derived from this Software without specific | |
43 | # prior written permission. | |
44 | # | |
45 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
46 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS | |
47 | # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
48 | # CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
49 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
50 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE | |
51 | # SOFTWARE. | |
52 | ||
53 | """ Driver to consume a Clang compilation database and invoke IWYU. | |
54 | ||
55 | Example usage with CMake: | |
56 | ||
57 | # Unix systems | |
58 | $ mkdir build && cd build | |
59 | $ CC="clang" CXX="clang++" cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON ... | |
60 | $ iwyu_tool.py -p . | |
61 | ||
62 | # Windows systems | |
63 | $ mkdir build && cd build | |
64 | $ cmake -DCMAKE_CXX_COMPILER="%VCINSTALLDIR%/bin/cl.exe" \ | |
65 | -DCMAKE_C_COMPILER="%VCINSTALLDIR%/VC/bin/cl.exe" \ | |
66 | -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ | |
67 | -G Ninja ... | |
68 | $ python iwyu_tool.py -p . | |
69 | ||
70 | See iwyu_tool.py -h for more details on command-line arguments. | |
71 | """ | |
72 | ||
73 | import os | |
74 | import sys | |
75 | import json | |
76 | import argparse | |
77 | import subprocess | |
78 | import re | |
79 | ||
80 | import logging | |
81 | ||
82 | logging.basicConfig(filename='iwyu.log') | |
83 | LOGGER = logging.getLogger("iwyu") | |
84 | ||
85 | ||
86 | def iwyu_formatter(output): | |
87 | """ Process iwyu's output, basically a no-op. """ | |
88 | print('\n'.join(output)) | |
89 | ||
90 | ||
91 | CORRECT_RE = re.compile(r'^\((.*?) has correct #includes/fwd-decls\)$') | |
92 | SHOULD_ADD_RE = re.compile(r'^(.*?) should add these lines:$') | |
93 | SHOULD_REMOVE_RE = re.compile(r'^(.*?) should remove these lines:$') | |
94 | FULL_LIST_RE = re.compile(r'The full include-list for (.*?):$') | |
95 | END_RE = re.compile(r'^---$') | |
96 | LINES_RE = re.compile(r'^- (.*?) // lines ([0-9]+)-[0-9]+$') | |
97 | ||
98 | ||
99 | GENERAL, ADD, REMOVE, LIST = range(4) | |
100 | ||
101 | ||
102 | def clang_formatter(output): | |
103 | """ Process iwyu's output into something clang-like. """ | |
104 | state = (GENERAL, None) | |
105 | for line in output: | |
106 | match = CORRECT_RE.match(line) | |
107 | if match: | |
108 | print('%s:1:1: note: #includes/fwd-decls are correct', match.groups(1)) | |
109 | continue | |
110 | match = SHOULD_ADD_RE.match(line) | |
111 | if match: | |
112 | state = (ADD, match.group(1)) | |
113 | continue | |
114 | match = SHOULD_REMOVE_RE.match(line) | |
115 | if match: | |
116 | state = (REMOVE, match.group(1)) | |
117 | continue | |
118 | match = FULL_LIST_RE.match(line) | |
119 | if match: | |
120 | state = (LIST, match.group(1)) | |
121 | elif END_RE.match(line): | |
122 | state = (GENERAL, None) | |
123 | elif not line.strip(): | |
124 | continue | |
125 | elif state[0] == GENERAL: | |
126 | print(line) | |
127 | elif state[0] == ADD: | |
128 | print('%s:1:1: error: add the following line', state[1]) | |
129 | print(line) | |
130 | elif state[0] == REMOVE: | |
131 | match = LINES_RE.match(line) | |
132 | line_no = match.group(2) if match else '1' | |
133 | print('%s:%s:1: error: remove the following line', state[1], line_no) | |
134 | print(match.group(1)) | |
135 | ||
136 | ||
137 | DEFAULT_FORMAT = 'iwyu' | |
138 | FORMATTERS = { | |
139 | 'iwyu': iwyu_formatter, | |
140 | 'clang': clang_formatter | |
141 | } | |
142 | ||
143 | ||
144 | def get_output(cwd, command): | |
145 | """ Run the given command and return its output as a string. """ | |
146 | process = subprocess.Popen(command, | |
147 | cwd=cwd, | |
148 | shell=True, | |
149 | stdout=subprocess.PIPE, | |
150 | stderr=subprocess.STDOUT) | |
151 | return process.communicate()[0].decode("utf-8").splitlines() | |
152 | ||
153 | ||
154 | def run_iwyu(cwd, compile_command, iwyu_args, verbose, formatter): | |
155 | """ Rewrite compile_command to an IWYU command, and run it. """ | |
156 | compiler, _, args = compile_command.partition(' ') | |
157 | if compiler.endswith('cl.exe'): | |
158 | # If the compiler name is cl.exe, let IWYU be cl-compatible | |
159 | clang_args = ['--driver-mode=cl'] | |
160 | else: | |
161 | clang_args = [] | |
162 | ||
163 | iwyu_args = ['-Xiwyu ' + a for a in iwyu_args] | |
164 | command = ['include-what-you-use'] + clang_args + iwyu_args | |
165 | command = '%s %s' % (' '.join(command), args.strip()) | |
166 | ||
167 | if verbose: | |
168 | print('%s:', command) | |
169 | ||
170 | formatter(get_output(cwd, command)) | |
171 | ||
172 | ||
173 | def main(compilation_db_path, source_files, verbose, formatter, iwyu_args): | |
174 | """ Entry point. """ | |
175 | # Canonicalize compilation database path | |
176 | if os.path.isdir(compilation_db_path): | |
177 | compilation_db_path = os.path.join(compilation_db_path, | |
178 | 'compile_commands.json') | |
179 | ||
180 | compilation_db_path = os.path.realpath(compilation_db_path) | |
181 | if not os.path.isfile(compilation_db_path): | |
182 | print('ERROR: No such file or directory: \'%s\'', compilation_db_path) | |
183 | return 1 | |
184 | ||
185 | # Read compilation db from disk | |
186 | with open(compilation_db_path, 'r') as fileobj: | |
187 | compilation_db = json.load(fileobj) | |
188 | ||
189 | # expand symlinks | |
190 | for entry in compilation_db: | |
191 | entry['file'] = os.path.realpath(entry['file']) | |
192 | ||
193 | # Cross-reference source files with compilation database | |
194 | source_files = [os.path.realpath(s) for s in source_files] | |
195 | if not source_files: | |
196 | # No source files specified, analyze entire compilation database | |
197 | entries = compilation_db | |
198 | else: | |
199 | # Source files specified, analyze the ones appearing in compilation db, | |
200 | # warn for the rest. | |
201 | entries = [] | |
202 | for source in source_files: | |
203 | matches = [e for e in compilation_db if e['file'] == source] | |
204 | if matches: | |
205 | entries.extend(matches) | |
206 | else: | |
207 | print("{} not in compilation database".format(source)) | |
208 | # TODO: As long as there is no complete compilation database available this check cannot be performed | |
209 | pass | |
210 | #print('WARNING: \'%s\' not found in compilation database.', source) | |
211 | ||
212 | # Run analysis | |
213 | try: | |
214 | for entry in entries: | |
215 | cwd, compile_command = entry['directory'], entry['command'] | |
216 | run_iwyu(cwd, compile_command, iwyu_args, verbose, formatter) | |
217 | except OSError as why: | |
218 | print('ERROR: Failed to launch include-what-you-use: %s', why) | |
219 | return 1 | |
220 | ||
221 | return 0 | |
222 | ||
223 | ||
224 | def _bootstrap(): | |
225 | """ Parse arguments and dispatch to main(). """ | |
226 | # This hackery is necessary to add the forwarded IWYU args to the | |
227 | # usage and help strings. | |
228 | def customize_usage(parser): | |
229 | """ Rewrite the parser's format_usage. """ | |
230 | original_format_usage = parser.format_usage | |
231 | parser.format_usage = lambda: original_format_usage().rstrip() + \ | |
232 | ' -- [<IWYU args>]' + os.linesep | |
233 | ||
234 | def customize_help(parser): | |
235 | """ Rewrite the parser's format_help. """ | |
236 | original_format_help = parser.format_help | |
237 | ||
238 | def custom_help(): | |
239 | """ Customized help string, calls the adjusted format_usage. """ | |
240 | helpmsg = original_format_help() | |
241 | helplines = helpmsg.splitlines() | |
242 | helplines[0] = parser.format_usage().rstrip() | |
243 | return os.linesep.join(helplines) + os.linesep | |
244 | ||
245 | parser.format_help = custom_help | |
246 | ||
247 | # Parse arguments | |
248 | parser = argparse.ArgumentParser( | |
249 | description='Include-what-you-use compilation database driver.', | |
250 | epilog='Assumes include-what-you-use is available on the PATH.') | |
251 | customize_usage(parser) | |
252 | customize_help(parser) | |
253 | ||
254 | parser.add_argument('-v', '--verbose', action='store_true', | |
255 | help='Print IWYU commands') | |
256 | parser.add_argument('-o', '--output-format', type=str, | |
257 | choices=FORMATTERS.keys(), default=DEFAULT_FORMAT, | |
258 | help='Output format (default: %s)' % DEFAULT_FORMAT) | |
259 | parser.add_argument('-p', metavar='<build-path>', required=True, | |
260 | help='Compilation database path', dest='dbpath') | |
261 | parser.add_argument('source', nargs='*', | |
262 | help='Zero or more source files to run IWYU on. ' | |
263 | 'Defaults to all in compilation database.') | |
264 | ||
265 | def partition_args(argv): | |
266 | """ Split around '--' into driver args and IWYU args. """ | |
267 | try: | |
268 | double_dash = argv.index('--') | |
269 | return argv[:double_dash], argv[double_dash+1:] | |
270 | except ValueError: | |
271 | return argv, [] | |
272 | argv, iwyu_args = partition_args(sys.argv[1:]) | |
273 | args = parser.parse_args(argv) | |
274 | ||
275 | sys.exit(main(args.dbpath, args.source, args.verbose, | |
276 | FORMATTERS[args.output_format], iwyu_args)) | |
277 | ||
278 | ||
279 | if __name__ == '__main__': | |
280 | _bootstrap() |