file_check.py 9.9 KB

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