office-gobmx/bin/find-unneeded-includes
Gabor Kelemen ae245cdde9 find-unneeded-includes: sort the output
Default IWYU output is sorted alphabetically by filename

A more friendly way to present f-u-i information would be
to sort by line numbers: so that a developer can go through the removal
proposals in the editor top to bottom, without too much cursor positioning
in the list of 100 header lines to delete 20 of them.

The result is not perfect, because numbers in text are not naturally sorted:
line 205 will be listed between 20 and 21; but that's a really rare occasion

Change-Id: I8011321a299a76f5a32469a9d77eb8fcc4521034
Reviewed-on: https://gerrit.libreoffice.org/63144
Tested-by: Jenkins
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
2018-11-13 09:03:45 +01:00

280 lines
9.2 KiB
Python
Executable file

#!/usr/bin/env python3
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This parses the output of 'include-what-you-use', focusing on just removing
# not needed includes and providing a relatively conservative output by
# filtering out a number of LibreOffice-specific false positives.
#
# It assumes you have a 'compile_commands.json' around (similar to clang-tidy),
# you can generate one with 'make vim-ide-integration'.
#
# Design goals:
# - blacklist mechanism, so a warning is either fixed or blacklisted
# - works in a plugins-enabled clang build
# - no custom configure options required
# - no need to generate a dummy library to build a header
import glob
import json
import multiprocessing
import os
import queue
import re
import subprocess
import sys
import threading
import yaml
def ignoreRemoval(include, toAdd, absFileName, moduleRules):
# global rules
# Avoid replacing .hpp with .hdl in the com::sun::star namespace.
if include.startswith("com/sun/star") and include.endswith(".hpp"):
hdl = include.replace(".hpp", ".hdl")
if hdl in toAdd:
return True
# Avoid debug STL.
debugStl = {
"array": "debug/array",
"deque": "debug/deque",
"list": "debug/list",
"map": "debug/map.h",
"set": "debug/set.h",
"unordered_map": "debug/unordered_map",
"unordered_set": "debug/unordered_set",
"vector": "debug/vector",
}
for k, v in debugStl.items():
if include == k and v in toAdd:
return True
# Avoid proposing to use libstdc++ internal headers.
bits = {
"exception": "bits/exception.h",
"memory": "bits/shared_ptr.h",
"functional": "bits/std_function.h",
}
for k, v in bits.items():
if include == k and v in toAdd:
return True
# Follow boost documentation.
if include == "boost/optional.hpp" and "boost/optional/optional.hpp" in toAdd:
return True
if include == "boost/intrusive_ptr.hpp" and "boost/smart_ptr/intrusive_ptr.hpp" in toAdd:
return True
# Avoid .hxx to .h proposals in basic css/uno/* API
unoapi = {
"com/sun/star/uno/Any.hxx": "com/sun/star/uno/Any.h",
"com/sun/star/uno/Reference.hxx": "com/sun/star/uno/Reference.h",
"com/sun/star/uno/Sequence.hxx": "com/sun/star/uno/Sequence.h",
"com/sun/star/uno/Type.hxx": "com/sun/star/uno/Type.h"
}
for k, v in unoapi.items():
if include == k and v in toAdd:
return True
# 3rd-party, non-self-contained headers.
if include == "libepubgen/libepubgen.h" and "libepubgen/libepubgen-decls.h" in toAdd:
return True
if include == "librevenge/librevenge.h" and "librevenge/RVNGPropertyList.h" in toAdd:
return True
noRemove = (
# <https://www.openoffice.org/tools/CodingGuidelines.sxw> insists on not
# removing this.
"sal/config.h",
# Works around a build breakage specific to the broken Android
# toolchain.
"android/compatibility.hxx",
)
if include in noRemove:
return True
# Ignore when <foo> is to be replaced with "foo".
if include in toAdd:
return True
fileName = os.path.relpath(absFileName, os.getcwd())
# yaml rules
if "blacklist" in moduleRules.keys():
blacklistRules = moduleRules["blacklist"]
if fileName in blacklistRules.keys():
if include in blacklistRules[fileName]:
return True
return False
def unwrapInclude(include):
# Drop <> or "" around the include.
return include[1:-1]
def processIWYUOutput(iwyuOutput, moduleRules):
inAdd = False
toAdd = []
inRemove = False
toRemove = []
currentFileName = None
for line in iwyuOutput:
line = line.strip()
if len(line) == 0:
if inRemove:
inRemove = False
continue
if inAdd:
inAdd = False
continue
match = re.match("(.*) should add these lines:$", line)
if match:
currentFileName = match.group(1)
inAdd = True
continue
match = re.match("(.*) should remove these lines:$", line)
if match:
currentFileName = match.group(1)
inRemove = True
continue
if inAdd:
match = re.match('#include ([^ ]+)', line)
if match:
include = unwrapInclude(match.group(1))
toAdd.append(include)
else:
# Forward declaration.
toAdd.append(line)
if inRemove:
match = re.match("- #include (.*) // lines (.*)-.*", line)
if match:
include = unwrapInclude(match.group(1))
lineno = match.group(2)
if not ignoreRemoval(include, toAdd, currentFileName, moduleRules):
toRemove.append("%s:%s: %s" % (currentFileName, lineno, include))
continue
match = re.match("- (.*;(?: })*)* // lines (.*)-.*", line)
if match:
fwdDecl = match.group(1)
if fwdDecl.endswith(";"):
# Remove trailing semicolon.
fwdDecl = fwdDecl[:-1]
lineno = match.group(2)
if not ignoreRemoval(fwdDecl, toAdd, currentFileName, moduleRules):
toRemove.append("%s:%s: %s" % (currentFileName, lineno, fwdDecl))
for remove in sorted(toRemove):
print("ERROR: %s: remove not needed include / forward declaration" % remove)
return len(toRemove)
def run_tool(task_queue, failed_files):
while True:
invocation, moduleRules = task_queue.get()
if not len(failed_files):
print("[IWYU] " + invocation.split(' ')[-1])
p = subprocess.Popen(invocation, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
retcode = processIWYUOutput(p.communicate()[0].decode('utf-8').splitlines(), moduleRules)
if retcode != 0:
print("ERROR: The following command found unused includes:\n" + invocation)
failed_files.append(invocation)
task_queue.task_done()
def isInUnoIncludeFile(path):
return path.startswith("include/com/") \
or path.startswith("include/cppu/") \
or path.startswith("include/cppuhelper/") \
or path.startswith("include/osl/") \
or path.startswith("include/rtl/") \
or path.startswith("include/sal/") \
or path.startswith("include/salhelper/") \
or path.startswith("include/systools/") \
or path.startswith("include/typelib/") \
or path.startswith("include/uno/")
def tidy(compileCommands, paths):
return_code = 0
try:
max_task = multiprocessing.cpu_count()
task_queue = queue.Queue(max_task)
failed_files = []
for _ in range(max_task):
t = threading.Thread(target=run_tool, args=(task_queue, failed_files))
t.daemon = True
t.start()
for path in sorted(paths):
if isInUnoIncludeFile(path):
continue
moduleName = path.split("/")[0]
rulePath = os.path.join(moduleName, "IwyuFilter_" + moduleName + ".yaml")
moduleRules = {}
if os.path.exists(rulePath):
moduleRules = yaml.load(open(rulePath))
assume = None
pathAbs = os.path.abspath(path)
compileFile = pathAbs
matches = [i for i in compileCommands if i["file"] == compileFile]
if not len(matches):
if "assumeFilename" in moduleRules.keys():
assume = moduleRules["assumeFilename"]
if assume:
assumeAbs = os.path.abspath(assume)
compileFile = assumeAbs
matches = [i for i in compileCommands if i["file"] == compileFile]
if not len(matches):
print("WARNING: no compile commands for '" + path + "' (assumed filename: '" + assume + "'")
continue
else:
print("WARNING: no compile commands for '" + path + "'")
continue
_, _, args = matches[0]["command"].partition(" ")
if assume:
args = args.replace(assumeAbs, "-x c++ " + pathAbs)
invocation = "include-what-you-use " + args
task_queue.put((invocation, moduleRules))
task_queue.join()
if len(failed_files):
return_code = 1
except KeyboardInterrupt:
print('\nCtrl-C detected, goodbye.')
os.kill(0, 9)
sys.exit(return_code)
def main(argv):
if not len(argv):
print("usage: find-unneeded-includes [FILE]...")
return
with open("compile_commands.json", 'r') as compileCommandsSock:
compileCommands = json.load(compileCommandsSock)
tidy(compileCommands, paths=argv)
if __name__ == '__main__':
main(sys.argv[1:])
# vim:set shiftwidth=4 softtabstop=4 expandtab: