| #!/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() |