file_check.py 9.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
#
# Copyright (c) 2006-2021, RT-Thread Development Team
#
# SPDX-License-Identifier: Apache-2.0
#
# Change Logs:
# Date           Author       Notes
# 2021-04-01     LiuKang      the first version
#

import os
import re
import sys
import click
15
import yaml
16 17 18 19 20 21 22 23 24 25 26 27 28
import chardet
import logging
import datetime


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,
                        )

29

30 31 32 33 34 35
class CheckOut:
    def __init__(self, rtt_repo, rtt_branch):
        self.root = os.getcwd()
        self.rtt_repo = rtt_repo
        self.rtt_branch = rtt_branch

36
    def __exclude_file(self, file_path):
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
        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
T
thread-liu 已提交
57 58 59
            logging.debug("ignore file path: {}".format(ignore_file_path))
            logging.debug("file_ignore: {}".format(file_ignore))
            logging.debug("dir_ignore: {}".format(dir_ignore))
60 61 62 63 64 65 66 67 68 69 70 71 72
            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)
T
thread-liu 已提交
73
                        if file_dir_path.startswith(dir_real_path):
74 75 76 77 78
                            logging.info("ignore dir path: {}".format(dir_real_path))
                            return 0
            except Exception as e:
                logging.error(e)
                continue
79 80 81

        return 1

82 83 84
    def get_new_file(self):
        file_list = list()
        try:
85 86 87
            os.system('git remote add rtt_repo {}'.format(self.rtt_repo))
            os.system('git fetch rtt_repo')
            os.system('git reset rtt_repo/{} --soft'.format(self.rtt_branch))
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
            os.system('git status > git.txt')
        except Exception as e:
            logging.error(e)
            return None
        try:
            with open('git.txt', 'r') as f:
                file_lines = f.readlines()
        except Exception as e:
            logging.error(e)
            return None
        file_path = ''
        for line in file_lines:
            if 'new file' in line:
                file_path = line.split('new file:')[1].strip()
                logging.info('new file -> {}'.format(file_path))
            elif 'deleted' in line:
                logging.info('deleted file -> {}'.format(line.split('deleted:')[1].strip()))
            elif 'modified' in line:
                file_path = line.split('modified:')[1].strip()
                logging.info('modified file -> {}'.format(file_path))
            else:
                continue

111 112 113
            result = self.__exclude_file(file_path)
            if result != 0:
                file_list.append(file_path)
114 115 116 117 118 119 120 121

        return file_list


class FormatCheck:
    def __init__(self, file_list):
        self.file_list = file_list

122
    def __check_file(self, file_lines, file_path):
123
        line_num = 1
124
        check_result = True
125 126 127 128 129
        for line in file_lines:
            # check line start
            line_start = line.replace(' ', '')
            # find tab
            if line_start.startswith('\t'):
130
                logging.error("{} line[{}]: please use space replace tab at the start of this line.".format(file_path, line_num))
131 132 133 134
                check_result = False
            # check line end
            lin_end = line.split('\n')[0]
            if lin_end.endswith(' ') or lin_end.endswith('\t'):
135
                logging.error("{} line[{}]: please delete extra space at the end of this line.".format(file_path, line_num))
136 137 138 139 140 141 142 143
                check_result = False
            line_num += 1

        return check_result

    def check(self):
        logging.info("Start to check files format.")
        if len(self.file_list) == 0:
T
thread-liu 已提交
144
            logging.warning("There are no files to check format.")
145
            return True
146 147 148 149 150 151
        encoding_check_result = True
        format_check_result = True
        for file_path in self.file_list:
            code = ''
            if file_path.endswith(".c") or file_path.endswith(".h"):
                try:
152
                    with open(file_path, 'rb') as f:
153 154 155 156 157 158 159 160
                        file = f.read()
                        # get file encoding
                        code = chardet.detect(file)['encoding']
                except Exception as e:
                    logging.error(e)
            else:
                continue

161
            if code != 'utf-8' and code != 'ascii':
162 163 164 165 166
                logging.error("[{0}]: encoding not utf-8, please format it.".format(file_path))
                encoding_check_result = False
            else:
                logging.info('[{0}]: encoding check success.'.format(file_path))

167
            with open(file_path, 'r', encoding = "utf-8") as f:
168
                file_lines = f.readlines()
169
            format_check_result = self.__check_file(file_lines, file_path)    
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207

        if not encoding_check_result or not format_check_result:
            logging.error("files format check fail.")
            return False

        logging.info("files format check success.")

        return True


class LicenseCheck:
    def __init__(self, file_list):
        self.file_list = file_list

    def check(self):
        current_year = datetime.date.today().year
        logging.info("current year: {}".format(current_year))
        if len(self.file_list) == 0:
            logging.warning("There are no files to check license.")
            return 0
        logging.info("Start to check files license.")
        check_result = True
        for file_path in self.file_list:
            if file_path.endswith(".c") or file_path.endswith(".h"):
                try:
                    with open(file_path, 'r') as f:
                        file = f.readlines()
                except Exception as e:
                    logging.error(e)
            else:
                continue

            if 'Copyright' in file[1] and 'SPDX-License-Identifier: Apache-2.0' in file[3]:
                try:
                    license_year = re.search(r'2006-\d{4}', file[1]).group()
                    true_year = '2006-{}'.format(current_year)
                    if license_year != true_year:
                        logging.warning("[{0}]: license year: {} is not true: {}, please update.".format(file_path,
208 209
                                                                                                         license_year,
                                                                                                         true_year))
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
                                                                                                
                    else:
                        logging.info("[{0}]: license check success.".format(file_path))
                except Exception as e:
                    logging.error(e)
            
            else:
                logging.error("[{0}]: license check fail.".format(file_path))
                check_result = False

        return check_result


@click.group()
@click.pass_context
def cli(ctx):
    pass


@cli.command()
@click.option(
    '--license',
    "check_license",
    required=False,
    type=click.BOOL,
    flag_value=True,
    help="Enable File license check.",
)
@click.argument(
    'repo',
    nargs=1,
    type=click.STRING,
    default='https://github.com/RT-Thread/rt-thread',
)
@click.argument(
    'branch',
    nargs=1,
    type=click.STRING,
    default='master',
)
def check(check_license, repo, branch):
    """
    check files license and format.
    """
    init_logger()
    # get modified files list
    checkout = CheckOut(repo, branch)
    file_list = checkout.get_new_file()
    if file_list is None:
        logging.error("checkout files fail")
        sys.exit(1)

    # check modified files format
    format_check = FormatCheck(file_list)
    format_check_result = format_check.check()
    license_check_result = True
    if check_license:
        license_check = LicenseCheck(file_list)
        license_check_result = license_check.check()

    if not format_check_result or not license_check_result:
        logging.error("file format check or license check fail.")
        sys.exit(1)
    logging.info("check success.")
    sys.exit(0)


if __name__ == '__main__':
    cli()