1# 2# Copyright (c) 2006-2022, RT-Thread Development Team 3# 4# SPDX-License-Identifier: Apache-2.0 5# 6# Change Logs: 7# Date Author Notes 8# 2021-04-01 LiuKang the first version 9# 10 11import os 12import re 13import sys 14import click 15import yaml 16import chardet 17import logging 18import datetime 19 20 21def init_logger(): 22 log_format = "[%(filename)s %(lineno)d %(levelname)s] %(message)s " 23 date_format = '%Y-%m-%d %H:%M:%S %a ' 24 logging.basicConfig(level=logging.INFO, 25 format=log_format, 26 datefmt=date_format, 27 ) 28 29 30class CheckOut: 31 def __init__(self, rtt_repo, rtt_branch): 32 self.root = os.getcwd() 33 self.rtt_repo = rtt_repo 34 self.rtt_branch = rtt_branch 35 36 def __exclude_file(self, file_path): 37 dir_number = file_path.split('/') 38 ignore_path = file_path 39 40 # gets the file path depth. 41 for i in dir_number: 42 # current directory. 43 dir_name = os.path.dirname(ignore_path) 44 ignore_path = dir_name 45 # judge the ignore file exists in the current directory. 46 ignore_file_path = os.path.join(dir_name, ".ignore_format.yml") 47 if not os.path.exists(ignore_file_path): 48 continue 49 try: 50 with open(ignore_file_path) as f: 51 ignore_config = yaml.safe_load(f.read()) 52 file_ignore = ignore_config.get("file_path", []) 53 dir_ignore = ignore_config.get("dir_path", []) 54 except Exception as e: 55 logging.error(e) 56 continue 57 logging.debug("ignore file path: {}".format(ignore_file_path)) 58 logging.debug("file_ignore: {}".format(file_ignore)) 59 logging.debug("dir_ignore: {}".format(dir_ignore)) 60 try: 61 # judge file_path in the ignore file. 62 for file in file_ignore: 63 if file is not None: 64 file_real_path = os.path.join(dir_name, file) 65 if file_real_path == file_path: 66 logging.info("ignore file path: {}".format(file_real_path)) 67 return 0 68 69 file_dir_path = os.path.dirname(file_path) 70 for _dir in dir_ignore: 71 if _dir is not None: 72 dir_real_path = os.path.join(dir_name, _dir) 73 if file_dir_path.startswith(dir_real_path): 74 logging.info("ignore dir path: {}".format(dir_real_path)) 75 return 0 76 except Exception as e: 77 logging.error(e) 78 continue 79 80 return 1 81 82 def get_new_file(self): 83 file_list = list() 84 try: 85 os.system('git remote add rtt_repo {}'.format(self.rtt_repo)) 86 os.system('git fetch rtt_repo') 87 os.system('git merge rtt_repo/{}'.format(self.rtt_branch)) 88 os.system('git reset rtt_repo/{} --soft'.format(self.rtt_branch)) 89 os.system('git status > git.txt') 90 except Exception as e: 91 logging.error(e) 92 return None 93 try: 94 with open('git.txt', 'r') as f: 95 file_lines = f.readlines() 96 except Exception as e: 97 logging.error(e) 98 return None 99 file_path = '' 100 for line in file_lines: 101 if 'new file' in line: 102 file_path = line.split('new file:')[1].strip() 103 logging.info('new file -> {}'.format(file_path)) 104 elif 'deleted' in line: 105 logging.info('deleted file -> {}'.format(line.split('deleted:')[1].strip())) 106 elif 'modified' in line: 107 file_path = line.split('modified:')[1].strip() 108 logging.info('modified file -> {}'.format(file_path)) 109 else: 110 continue 111 112 result = self.__exclude_file(file_path) 113 if result != 0: 114 file_list.append(file_path) 115 116 return file_list 117 118 119class FormatCheck: 120 def __init__(self, file_list): 121 self.file_list = file_list 122 123 def __check_rt_errorcode(self, line): 124 pattern = re.compile(r'return\s+(RT_ERROR|RT_ETIMEOUT|RT_EFULL|RT_EEMPTY|RT_ENOMEM|RT_ENOSYS|RT_EBUSY|RT_EIO|RT_EINTR|RT_EINVAL|RT_ENOENT|RT_ENOSPC|RT_EPERM|RT_ETRAP|RT_EFAULT)') 125 match = pattern.search(line) 126 if match: 127 return False 128 else: 129 return True 130 131 def __check_file(self, file_lines, file_path): 132 line_num = 0 133 check_result = True 134 for line in file_lines: 135 line_num += 1 136 # check line start 137 line_start = line.replace(' ', '') 138 # find tab 139 if line_start.startswith('\t'): 140 logging.error("{} line[{}]: please use space replace tab at the start of this line.".format(file_path, line_num)) 141 check_result = False 142 # check line end 143 line_end = line.split('\n')[0] 144 if line_end.endswith(' ') or line_end.endswith('\t'): 145 logging.error("{} line[{}]: please delete extra space at the end of this line.".format(file_path, line_num)) 146 check_result = False 147 if self.__check_rt_errorcode(line) == False: 148 logging.error("{} line[{}]: the RT-Thread error code should return negative value. e.g. return -RT_ERROR".format(file_path, line_num)) 149 check_result = False 150 return check_result 151 152 def check(self): 153 logging.info("Start to check files format.") 154 if len(self.file_list) == 0: 155 logging.warning("There are no files to check format.") 156 return True 157 encoding_check_result = True 158 format_check_fail_files = 0 159 for file_path in self.file_list: 160 code = '' 161 if file_path.endswith(".c") or file_path.endswith(".h"): 162 try: 163 with open(file_path, 'rb') as f: 164 file = f.read() 165 # get file encoding 166 chardet_report = chardet.detect(file) 167 code = chardet_report['encoding'] 168 confidence = chardet_report['confidence'] 169 except Exception as e: 170 logging.error(e) 171 else: 172 continue 173 174 if code != 'utf-8' and code != 'ascii' and confidence > 0.8: 175 logging.error("[{0}]: encoding {1} not utf-8, please format it.".format(file_path, code)) 176 encoding_check_result = False 177 else: 178 logging.info('[{0}]: encoding check success.'.format(file_path)) 179 180 with open(file_path, 'r', encoding = "utf-8") as f: 181 file_lines = f.readlines() 182 if not self.__check_file(file_lines, file_path): 183 format_check_fail_files += 1 184 185 if (not encoding_check_result) or (format_check_fail_files != 0): 186 logging.error("files format check fail.") 187 return False 188 189 logging.info("files format check success.") 190 191 return True 192 193 194class LicenseCheck: 195 def __init__(self, file_list): 196 self.file_list = file_list 197 198 def check(self): 199 current_year = datetime.date.today().year 200 logging.info("current year: {}".format(current_year)) 201 if len(self.file_list) == 0: 202 logging.warning("There are no files to check license.") 203 return 0 204 logging.info("Start to check files license.") 205 check_result = True 206 for file_path in self.file_list: 207 if file_path.endswith(".c") or file_path.endswith(".h"): 208 try: 209 with open(file_path, 'r') as f: 210 file = f.readlines() 211 except Exception as e: 212 logging.error(e) 213 else: 214 continue 215 216 if 'Copyright' in file[1] and 'SPDX-License-Identifier: Apache-2.0' in file[3]: 217 try: 218 license_year = re.search(r'2006-\d{4}', file[1]).group() 219 true_year = '2006-{}'.format(current_year) 220 if license_year != true_year: 221 logging.warning("[{0}]: license year: {} is not true: {}, please update.".format(file_path, 222 license_year, 223 true_year)) 224 225 else: 226 logging.info("[{0}]: license check success.".format(file_path)) 227 except Exception as e: 228 logging.error(e) 229 230 else: 231 logging.error("[{0}]: license check fail.".format(file_path)) 232 check_result = False 233 234 return check_result 235 236 237@click.group() 238@click.pass_context 239def cli(ctx): 240 pass 241 242 243@cli.command() 244@click.option( 245 '--license', 246 "check_license", 247 required=False, 248 type=click.BOOL, 249 flag_value=True, 250 help="Enable File license check.", 251) 252@click.argument( 253 'repo', 254 nargs=1, 255 type=click.STRING, 256 default='https://github.com/RT-Thread/rt-thread', 257) 258@click.argument( 259 'branch', 260 nargs=1, 261 type=click.STRING, 262 default='master', 263) 264def check(check_license, repo, branch): 265 """ 266 check files license and format. 267 """ 268 init_logger() 269 # get modified files list 270 checkout = CheckOut(repo, branch) 271 file_list = checkout.get_new_file() 272 if file_list is None: 273 logging.error("checkout files fail") 274 sys.exit(1) 275 276 # check modified files format 277 format_check = FormatCheck(file_list) 278 format_check_result = format_check.check() 279 license_check_result = True 280 if check_license: 281 license_check = LicenseCheck(file_list) 282 license_check_result = license_check.check() 283 284 if not format_check_result or not license_check_result: 285 logging.error("file format check or license check fail.") 286 sys.exit(1) 287 logging.info("check success.") 288 sys.exit(0) 289 290 291if __name__ == '__main__': 292 cli() 293