Add a new option for a more tool-friendly output.

The standard output format of iwyu is not very friendly for tools like
Jenkins' warnings-plugin (https://github.com/jenkinsci/warnings-plugin) or
editors.  This change adds a new -o/--output-format command line option with
'iwyu' being the traditional format and 'clang' the new one for mangling the
output into something clang-like, enabling nice integration into
emacs/Jenkins/etc. out of the box.

Somehow this should probably be the default and be handled on the native
side (not as a post-processing step), but the current change is probably the
least controversial and easiest one for now.
This commit is contained in:
Sven Panne 2016-11-08 10:45:46 +01:00 committed by Kim Gräsman
parent eb7f5d1ef0
commit d53f86d8f5
1 changed files with 78 additions and 6 deletions

View File

@ -25,9 +25,77 @@ import sys
import json
import argparse
import subprocess
import re
def run_iwyu(cwd, compile_command, iwyu_args, verbose):
def iwyu_formatter(output):
""" Process iwyu's output, basically a no-op. """
print('\n'.join(output))
CORRECT_RE = re.compile(r'^\((.*?) has correct #includes/fwd-decls\)$')
SHOULD_ADD_RE = re.compile(r'^(.*?) should add these lines:$')
SHOULD_REMOVE_RE = re.compile(r'^(.*?) should remove these lines:$')
FULL_LIST_RE = re.compile(r'The full include-list for (.*?):$')
END_RE = re.compile(r'^---$')
LINES_RE = re.compile(r'^- (.*?) // lines ([0-9]+)-[0-9]+$')
GENERAL, ADD, REMOVE, LIST = range(4)
def clang_formatter(output):
""" Process iwyu's output into something clang-like. """
state = (GENERAL, None)
for line in output:
match = CORRECT_RE.match(line)
if match:
print('%s:1:1: note: #includes/fwd-decls are correct' % match.groups(1))
continue
match = SHOULD_ADD_RE.match(line)
if match:
state = (ADD, match.group(1))
continue
match = SHOULD_REMOVE_RE.match(line)
if match:
state = (REMOVE, match.group(1))
continue
match = FULL_LIST_RE.match(line)
if match:
state = (LIST, match.group(1))
elif END_RE.match(line):
state = (GENERAL, None)
elif not line.strip():
continue
elif state[0] == GENERAL:
print(line)
elif state[0] == ADD:
print('%s:1:1: error: add the following line' % state[1])
print(line)
elif state[0] == REMOVE:
match = LINES_RE.match(line)
line_no = match.group(2) if match else '1'
print('%s:%s:1: error: remove the following line' % (state[1], line_no))
print(match.group(1))
DEFAULT_FORMAT = 'iwyu'
FORMATTERS = {
'iwyu': iwyu_formatter,
'clang': clang_formatter
}
def get_output(cwd, command):
""" Run the given command and return its output as a string. """
process = subprocess.Popen(command,
cwd=cwd,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
return process.communicate()[0].decode("utf-8").splitlines()
def run_iwyu(cwd, compile_command, iwyu_args, verbose, formatter):
""" Rewrite compile_command to an IWYU command, and run it. """
compiler, _, args = compile_command.partition(' ')
if compiler.endswith('cl.exe'):
@ -43,10 +111,10 @@ def run_iwyu(cwd, compile_command, iwyu_args, verbose):
if verbose:
print('%s:' % command)
return subprocess.call(command, cwd=cwd, shell=True)
formatter(get_output(cwd, command))
def main(compilation_db_path, source_files, verbose, iwyu_args):
def main(compilation_db_path, source_files, verbose, formatter, iwyu_args):
""" Entry point. """
# Canonicalize compilation database path
if os.path.isdir(compilation_db_path):
@ -87,7 +155,7 @@ def main(compilation_db_path, source_files, verbose, iwyu_args):
try:
for entry in entries:
cwd, compile_command = entry['directory'], entry['command']
run_iwyu(cwd, compile_command, iwyu_args, verbose)
run_iwyu(cwd, compile_command, iwyu_args, verbose, formatter)
except OSError as why:
print('ERROR: Failed to launch include-what-you-use: %s' % why)
return 1
@ -127,6 +195,9 @@ def _bootstrap():
parser.add_argument('-v', '--verbose', action='store_true',
help='Print IWYU commands')
parser.add_argument('-o', '--output-format', type=str,
choices=FORMATTERS.keys(), default=DEFAULT_FORMAT,
help='Output format (default: %s)' % DEFAULT_FORMAT)
parser.add_argument('-p', metavar='<build-path>', required=True,
help='Compilation database path', dest='dbpath')
parser.add_argument('source', nargs='*',
@ -140,10 +211,11 @@ def _bootstrap():
return argv[:double_dash], argv[double_dash+1:]
except ValueError:
return argv, []
argv, iwyu_args = partition_args(sys.argv[1:])
args = parser.parse_args(argv)
sys.exit(main(args.dbpath, args.source, args.verbose, iwyu_args))
sys.exit(main(args.dbpath, args.source, args.verbose,
FORMATTERS[args.output_format], iwyu_args))
if __name__ == '__main__':