run_pre_commit_check.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. #!/usr/bin/env python3
  2. #
  3. # Copyright (C) 2019 Intel Corporation. All rights reserved.
  4. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  5. #
  6. import json
  7. import os
  8. import pathlib
  9. import queue
  10. import re
  11. import shlex
  12. import shutil
  13. import subprocess
  14. import sys
  15. CLANG_CMD = "clang-13"
  16. CLANG_CPP_CMD = "clang-cpp-13"
  17. CLANG_FORMAT_CMD = "clang-format-13"
  18. CLANG_TIDY_CMD = "clang-tidy-13"
  19. CMAKE_CMD = "cmake"
  20. # glob style patterns
  21. EXCLUDE_PATHS = [
  22. "**/.git/",
  23. "**/.github/",
  24. "**/.vscode/",
  25. "**/assembly-script/",
  26. "**/build/",
  27. "**/build-scripts/",
  28. "**/ci/",
  29. "**/core/deps/",
  30. "**/doc/",
  31. "**/samples/workload/",
  32. "**/test-tools/",
  33. "**/wamr-sdk/",
  34. "**/wamr-dev/",
  35. "**/wamr-dev-simd/",
  36. ]
  37. C_SUFFIXES = [".c", ".h"]
  38. VALID_DIR_NAME = r"([a-zA-Z0-9]+\-*)+[a-zA-Z0-9]*"
  39. VALID_FILE_NAME = r"\.?([a-zA-Z0-9]+\_*)+[a-zA-Z0-9]*\.*\w*"
  40. def locate_command(command):
  41. if not shutil.which(command):
  42. print(f"Command '{command}'' not found")
  43. return False
  44. return True
  45. def is_excluded(path):
  46. for exclude_path in EXCLUDE_PATHS:
  47. if path.match(exclude_path):
  48. return True
  49. return False
  50. def pre_flight_check(root):
  51. def check_clang_foramt(root):
  52. if not locate_command(CLANG_FORMAT_CMD):
  53. return False
  54. # Quick syntax check for .clang-format
  55. try:
  56. subprocess.check_call(
  57. shlex.split(f"{CLANG_FORMAT_CMD} --dump-config"), cwd=root
  58. )
  59. except subprocess.CalledProcessError:
  60. print(f"Might have a typo in .clang-format")
  61. return False
  62. return True
  63. def check_clang_tidy(root):
  64. if not locate_command(CLANG_TIDY_CMD):
  65. return False
  66. if (
  67. not locate_command(CLANG_CMD)
  68. or not locate_command(CLANG_CPP_CMD)
  69. or not locate_command(CMAKE_CMD)
  70. ):
  71. return False
  72. # Quick syntax check for .clang-format
  73. try:
  74. subprocess.check_call(
  75. shlex.split("{CLANG_TIDY_CMD} --dump-config"), cwd=root
  76. )
  77. except subprocess.CalledProcessError:
  78. print(f"Might have a typo in .clang-tidy")
  79. return False
  80. # looking for compile command database
  81. return True
  82. def check_aspell(root):
  83. return True
  84. return check_clang_foramt(root) and check_clang_tidy(root) and check_aspell(root)
  85. def run_clang_format(file_path, root):
  86. try:
  87. subprocess.check_call(
  88. shlex.split(
  89. f"{CLANG_FORMAT_CMD} --style=file --Werror --dry-run {file_path}"
  90. ),
  91. cwd=root,
  92. )
  93. return True
  94. except subprocess.CalledProcessError:
  95. print(f"{file_path} failed the check of {CLANG_FORMAT_CMD}")
  96. return False
  97. def generate_compile_commands(compile_command_database, root):
  98. CMD = f"{CMAKE_CMD} -DCMAKE_C_COMPILER={shutil.which(CLANG_CMD)} -DCMAKE_CXX_COMPILER={shutil.which(CLANG_CPP_CMD)} -DCMAKE_EXPORT_COMPILE_COMMANDS=ON .."
  99. try:
  100. linux_mini_build = root.joinpath("product-mini/platforms/linux/build").resolve()
  101. linux_mini_build.mkdir(exist_ok=True)
  102. if subprocess.check_call(shlex.split(CMD), cwd=linux_mini_build):
  103. return False
  104. wamrc_build = root.joinpath("wamr-compiler/build").resolve()
  105. wamrc_build.mkdir(exist_ok=True)
  106. if subprocess.check_call(shlex.split(CMD), cwd=wamrc_build):
  107. return False
  108. with open(linux_mini_build.joinpath("compile_commands.json"), "r") as f:
  109. iwasm_compile_commands = json.load(f)
  110. with open(wamrc_build.joinpath("compile_commands.json"), "r") as f:
  111. wamrc_compile_commands = json.load(f)
  112. all_compile_commands = iwasm_compile_commands + wamrc_compile_commands
  113. # TODO: duplication items ?
  114. with open(compile_command_database, "w") as f:
  115. json.dump(all_compile_commands, f)
  116. return True
  117. except subprocess.CalledProcessError:
  118. return False
  119. def run_clang_tidy(file_path, root):
  120. # preparatoin
  121. compile_command_database = pathlib.Path("/tmp/compile_commands.json")
  122. if not compile_command_database.exists() and not generate_compile_commands(
  123. compile_command_database, root
  124. ):
  125. return False
  126. try:
  127. if subprocess.check_call(
  128. shlex.split(f"{CLANG_TIDY_CMD} -p={compile_command_database} {file_path}"),
  129. cwd=root,
  130. ):
  131. print(f"{file_path} failed the check of {CLANG_TIDY_CMD}")
  132. except subprocess.CalledProcessError:
  133. print(f"{file_path} failed the check of {CLANG_TIDY_CMD}")
  134. return False
  135. return True
  136. def run_aspell(file_path, root):
  137. return True
  138. def check_dir_name(path, root):
  139. # since we don't want to check the path beyond root.
  140. # we hope "-" only will be used in a dir name as separators
  141. return all(
  142. [
  143. re.match(VALID_DIR_NAME, path_part)
  144. for path_part in path.relative_to(root).parts
  145. ]
  146. )
  147. def check_file_name(path):
  148. # since we don't want to check the path beyond root.
  149. # we hope "_" only will be used in a file name as separators
  150. return re.match(VALID_FILE_NAME, path.name) is not None
  151. def run_pre_commit_check(path, root=None):
  152. path = path.resolve()
  153. if path.is_dir():
  154. if not check_dir_name(path, root):
  155. print(f"{path} is not a valid directory name")
  156. return False
  157. else:
  158. return True
  159. if path.is_file():
  160. if not check_file_name(path):
  161. print(f"{path} is not a valid file name")
  162. return False
  163. if not path.suffix in C_SUFFIXES:
  164. return True
  165. return (
  166. run_clang_format(path, root)
  167. and run_clang_tidy(path, root)
  168. and run_aspell(path, root)
  169. )
  170. print(f"{path} neither a file nor a directory")
  171. return False
  172. def main():
  173. wamr_root = pathlib.Path(__file__).parent.joinpath("..").resolve()
  174. if not pre_flight_check(wamr_root):
  175. return False
  176. invalid_file, invalid_directory = 0, 0
  177. # in order to skip exclude directories ASAP,
  178. # will not yield Path.
  179. # since we will create every object
  180. dirs = queue.Queue()
  181. dirs.put(wamr_root)
  182. while not dirs.empty():
  183. qsize = dirs.qsize()
  184. while qsize:
  185. current_dir = dirs.get()
  186. for path in current_dir.iterdir():
  187. path = path.resolve()
  188. if path.is_symlink():
  189. continue
  190. if path.is_dir() and not is_excluded(path):
  191. invalid_directory += (
  192. 0 if run_pre_commit_check(path, wamr_root) else 1
  193. )
  194. dirs.put(path)
  195. if not path.is_file():
  196. continue
  197. invalid_file += 0 if run_pre_commit_check(path) else 1
  198. else:
  199. qsize -= 1
  200. print(f"invalid_directory={invalid_directory}, invalid_file={invalid_file}")
  201. return True
  202. if __name__ == "__main__":
  203. main()