diff --git a/.github/workflows/static_code_analysis.yml b/.github/workflows/static_code_analysis.yml index 37d9a7b2cba9be8a642dd2bdd880f3ebfdda8520..397e04ef1d2be0b38e5a839e44ec1690ed7a0539 100644 --- a/.github/workflows/static_code_analysis.yml +++ b/.github/workflows/static_code_analysis.yml @@ -1,6 +1,6 @@ name: Static code analysis -on: +on: pull_request: branches: - master @@ -21,16 +21,10 @@ jobs: run: | sudo apt-get update sudo apt-get -qq install cppcheck + pip install click PyYaml git remote -v git fetch origin cppcheck --version ls git branch -a - changed_files=$(git diff --name-only HEAD origin/master | grep -E '\.(c|cpp|cc|cxx)$' || true) - if [ -n "$changed_files" ];then - cppcheck --enable=warning,performance,portability --inline-suppr --error-exitcode=1 --force $changed_files - err=$? - if [ $err -ne 0 ]; then - echo "CPPCHECK REPORT, PLEASE CHECK THE WARNING !!!!!!!!!" - fi - fi + python tools/ci/cpp_check.py check diff --git a/tools/ci/cpp_check.py b/tools/ci/cpp_check.py new file mode 100644 index 0000000000000000000000000000000000000000..10906a3b7f710c25229160044e8b7489c18bc725 --- /dev/null +++ b/tools/ci/cpp_check.py @@ -0,0 +1,63 @@ +# +# Copyright (c) 2006-2023, RT-Thread Development Team +# +# SPDX-License-Identifier: Apache-2.0 +# +# Change Logs: +# Date Author Notes +# 2023-05-16 dejavudwh the first version +# + +import click +import logging +import subprocess +import sys +import format_ignore + +class CPPCheck: + def __init__(self, file_list): + self.file_list = file_list + + def check(self): + file_list_filtered = [file for file in self.file_list if file.endswith(('.c', '.cpp', '.cc', '.cxx'))] + logging.info("Start to static code analysis.") + check_result = True + for file in file_list_filtered: + result = subprocess.run(['cppcheck', '--enable=warning', 'performance', 'portability', '--inline-suppr', '--error-exitcode=1', '--force', file], stdout = subprocess.PIPE, stderr = subprocess.PIPE) + logging.info(result.stdout.decode()) + logging.info(result.stderr.decode()) + if result.stderr: + check_result = False + return check_result + +@click.group() +@click.pass_context +def cli(ctx): + pass + +@cli.command() +def check(): + """ + static code analysis(cppcheck). + """ + format_ignore.init_logger() + # get modified files list + checkout = format_ignore.CheckOut() + file_list = checkout.get_new_file() + if file_list is None: + logging.error("checkout files fail") + sys.exit(1) + + # use cppcheck + cpp_check = CPPCheck(file_list) + cpp_check_result = cpp_check.check() + + if not cpp_check_result: + logging.error("static code analysis(cppcheck) fail.") + sys.exit(1) + logging.info("check success.") + sys.exit(0) + + +if __name__ == '__main__': + cli() diff --git a/tools/ci/format_ignore.py b/tools/ci/format_ignore.py new file mode 100644 index 0000000000000000000000000000000000000000..a7f9be2cbfc30ca98584947b232f531fa833b2bd --- /dev/null +++ b/tools/ci/format_ignore.py @@ -0,0 +1,84 @@ +# +# Copyright (c) 2006-2023, RT-Thread Development Team +# +# SPDX-License-Identifier: Apache-2.0 +# +# Change Logs: +# Date Author Notes +# 2023-05-16 dejavudwh the first version +# + +import yaml +import logging +import os +import subprocess + +def init_logger(): + log_format = "[%(filename)s %(lineno)d %(levelname)s] %(message)s " + date_format = '%Y-%m-%d %H:%M:%S %a ' + logging.basicConfig(level=logging.INFO, + format=log_format, + datefmt=date_format, + ) + +class CheckOut: + def __init__(self): + pass + + def __exclude_file(self, file_path): + dir_number = file_path.split('/') + ignore_path = file_path + + # gets the file path depth. + for i in dir_number: + # current directory. + dir_name = os.path.dirname(ignore_path) + ignore_path = dir_name + # judge the ignore file exists in the current directory. + ignore_file_path = os.path.join(dir_name, ".ignore_format.yml") + if not os.path.exists(ignore_file_path): + continue + try: + with open(ignore_file_path) as f: + ignore_config = yaml.safe_load(f.read()) + file_ignore = ignore_config.get("file_path", []) + dir_ignore = ignore_config.get("dir_path", []) + except Exception as e: + logging.error(e) + continue + logging.debug("ignore file path: {}".format(ignore_file_path)) + logging.debug("file_ignore: {}".format(file_ignore)) + logging.debug("dir_ignore: {}".format(dir_ignore)) + try: + # judge file_path in the ignore file. + for file in file_ignore: + if file is not None: + file_real_path = os.path.join(dir_name, file) + if file_real_path == file_path: + logging.info("ignore file path: {}".format(file_real_path)) + return 0 + + file_dir_path = os.path.dirname(file_path) + for _dir in dir_ignore: + if _dir is not None: + dir_real_path = os.path.join(dir_name, _dir) + if file_dir_path.startswith(dir_real_path): + logging.info("ignore dir path: {}".format(dir_real_path)) + return 0 + except Exception as e: + logging.error(e) + continue + + return 1 + + def get_new_file(self): + result = subprocess.run(['git', 'diff', '--name-only', 'HEAD', 'origin/master', '--diff-filter=ACMR', '--no-renames', '--full-index'], stdout = subprocess.PIPE) + file_list = result.stdout.decode().strip().split('\n') + new_files = [] + for line in file_list: + logging.info("modified file -> {}".format(line)) + result = self.__exclude_file(line) + if result != 0: + new_files.append(line) + + return new_files \ No newline at end of file