blob: 261544a3ecaad9dd37e15fc4cf80bd5380047f0e [file] [log] [blame]
#!/usr/bin/env -S python3 -B
# Copyright (c) 2021 Project CHIP Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import contextlib
import glob
import json
import logging
import os
import re
import shutil
import subprocess
import sys
from pathlib import Path
import click
import coloredlogs
# Supported log levels, mapping string values required for argument
# parsing into logging constants
__LOG_LEVELS__ = {
'debug': logging.DEBUG,
'info': logging.INFO,
'warn': logging.WARN,
'fatal': logging.FATAL,
}
root_dir = os.path.dirname(os.path.realpath(__file__))
proj_root_dir = os.path.join(Path(root_dir).parent.parent)
def find_program(names):
for name in names:
found = shutil.which(name)
if found is not None:
return found
@click.command()
@click.option(
'--log-level',
default='INFO',
type=click.Choice(__LOG_LEVELS__.keys(), case_sensitive=False),
help='Determines the verbosity of script output.')
@click.option(
'--no-log-timestamps',
default=False,
is_flag=True,
help='Skip timestamps in log output')
@click.option(
'--compile-commands-glob',
show_default=True,
default=os.path.join(proj_root_dir, "out", "debug", "compile_commands*.json"),
help='Set global pattern for compile_commands.json files'
)
@click.option(
'--scanning-destination',
show_default=True,
default=os.path.join(proj_root_dir, "src", "platform"),
help='Set scanning destination file(s) or directory /ies in project'
)
@click.option(
'--mapping-file-dir',
help='Set mapping file directory /ies manually. File should have name iwyu.imp'
)
@click.option(
'--iwyu-args',
show_default=True,
default="-Xiwyu --no_fwd_decls",
help='Set custom arg(s) for include what you use'
)
@click.option(
'--clang-args',
default="",
help='Set custom arg(s) for clang'
)
def main(compile_commands_glob, scanning_destination, mapping_file_dir,
iwyu_args, clang_args, log_level, no_log_timestamps):
# Ensures somewhat pretty logging of what is going on
log_fmt = '%(asctime)s %(levelname)-7s %(message)s'
if no_log_timestamps:
log_fmt = '%(levelname)-7s %(message)s'
coloredlogs.install(level=__LOG_LEVELS__[log_level], fmt=log_fmt)
# checking if a program IWYU exists
iwyu = find_program(('iwyu_tool', 'iwyu_tool.py'))
if iwyu is None:
logging.error("Can't find IWYU")
sys.exit(1)
# For iterating how many files had problems with includes
warning_in_files = 0
platform = ""
compile_commands_glob = glob.glob(compile_commands_glob)
if not compile_commands_glob:
logging.error("Can't find compile_commands.json file(s)")
sys.exit(1)
for compile_commands in compile_commands_glob:
compile_commands_path = os.path.dirname(compile_commands)
compile_commands_file = os.path.join(compile_commands_path, "compile_commands.json")
logging.debug("Copy compile command file %s to %s", compile_commands, compile_commands_file)
with contextlib.suppress(shutil.SameFileError):
shutil.copyfile(compile_commands, compile_commands_file)
# Prase json file for find target name
with open(compile_commands, 'r') as json_data:
json_data = json.load(json_data)
for key in json_data:
find_re = re.search(r'^.*/src/platform*?\/(.*)/.*$', key['file'])
if find_re is not None:
platform = find_re.group(1)
break
if not platform:
logging.error("Can't find platform")
sys.exit(1)
if not mapping_file_dir:
mapping_file_dir = os.path.join(root_dir, "platforms", platform)
# Platform specific clang arguments, as some platform
# may needed some hacks for clang compiler for work properly.
platform_clang_args = []
if platform == "Tizen":
platform_clang_args = [
"--target=arm-linux-gnueabi",
"-I$TIZEN_SDK_TOOLCHAIN/arm-tizen-linux-gnueabi/include/c++/9.2.0",
"-I$TIZEN_SDK_TOOLCHAIN/arm-tizen-linux-gnueabi/include/c++/9.2.0/arm-tizen-linux-gnueabi",
"-I$TIZEN_SDK_TOOLCHAIN/lib/gcc/arm-tizen-linux-gnueabi/9.2.0/include",
]
# TODO: Add another platform for easy scanning
# Actually works scanning for platform: tizen, darwin, linux other not tested yet.
command_arr = [
iwyu,
"-p", compile_commands_path, scanning_destination,
"--", iwyu_args,
"-Xiwyu", "--mapping_file=" + mapping_file_dir + "/iwyu.imp",
] + platform_clang_args + [clang_args]
logging.info("Used compile commands: %s", compile_commands)
logging.info("Scanning includes for platform: %s", platform)
logging.info("Scanning destination: %s", scanning_destination)
logging.debug("Command: %s", " ".join(command_arr))
status = subprocess.Popen(" ".join(command_arr),
shell=True,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
logging.info("============== IWYU output start ================")
logger = logging.info
while status.poll() is None:
line = status.stdout.readline().rstrip()
if re.match(r"^warning:.*$", line):
logger = logging.warning
elif re.match(r"^.*([A-Za-z0-9]+(/[A-Za-z0-9]+)+)\.cpp should [a-zA-Z]+ these lines:$", line):
logger = logging.warning
elif re.match(r"^.*([A-Za-z0-9]+(/[A-Za-z0-9]+)+)\.[a-zA-Z]+ has correct #includes/fwd-decls\)$", line):
logger = logging.info
elif re.match(r"^The full include-list for .*$", line):
logger = logging.warning
warning_in_files += 1
logger("%s", line)
logging.info("============== IWYU output end ================")
if warning_in_files:
logging.error("Number of files with include issues: %d", warning_in_files)
sys.exit(2)
else:
logging.info("Every include looks good!")
if __name__ == '__main__':
main()