]> git.proxmox.com Git - ceph.git/blob - ceph/src/arrow/cpp/build-support/iwyu/iwyu_tool.py
import quincy 17.2.0
[ceph.git] / ceph / src / arrow / cpp / build-support / iwyu / iwyu_tool.py
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()