clang_tidy.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. import argparse
  2. import json
  3. import os
  4. import re
  5. import shutil
  6. import sys
  7. import subprocess
  8. import yaml
  9. def setup_script(args):
  10. global tidy_config_validation
  11. sys.path.append(os.path.dirname(args.config_validation_script))
  12. import tidy_config_validation
  13. def parse_args():
  14. parser = argparse.ArgumentParser()
  15. parser.add_argument("--testing-src", required=True)
  16. parser.add_argument("--clang-tidy-bin", required=True)
  17. parser.add_argument("--config-validation-script", required=True)
  18. parser.add_argument("--ymake-python", required=True)
  19. parser.add_argument("--tidy-json", required=True)
  20. parser.add_argument("--source-root", required=True)
  21. parser.add_argument("--build-root", required=True)
  22. parser.add_argument("--default-config-file", required=True)
  23. parser.add_argument("--project-config-file", required=True)
  24. parser.add_argument("--export-fixes", required=True)
  25. parser.add_argument("--checks", required=False, default="")
  26. parser.add_argument("--header-filter", required=False, default=None)
  27. return parser.parse_known_args()
  28. def generate_compilation_database(clang_cmd, source_root, filename, path):
  29. compile_database = [
  30. {
  31. "file": filename,
  32. "command": subprocess.list2cmdline(clang_cmd),
  33. "directory": source_root,
  34. }
  35. ]
  36. compilation_database_json = os.path.join(path, "compile_commands.json")
  37. with open(compilation_database_json, "w") as afile:
  38. json.dump(compile_database, afile)
  39. return compilation_database_json
  40. def load_profile(path):
  41. if os.path.exists(path):
  42. files = os.listdir(path)
  43. if len(files) == 1:
  44. with open(os.path.join(path, files[0])) as afile:
  45. return json.load(afile)["profile"]
  46. elif len(files) > 1:
  47. return {
  48. "error": "found several profile files: {}".format(files),
  49. }
  50. return {
  51. "error": "profile file is missing",
  52. }
  53. def load_fixes(path):
  54. if os.path.exists(path):
  55. with open(path, 'r') as afile:
  56. return afile.read()
  57. else:
  58. return ""
  59. def is_generated(testing_src, build_root):
  60. return testing_src.startswith(build_root)
  61. def generate_outputs(output_json):
  62. output_obj = os.path.splitext(output_json)[0] + ".o"
  63. open(output_obj, "w").close()
  64. open(output_json, "w").close()
  65. def filter_configs(result_config, filtered_config):
  66. with open(result_config, 'r') as afile:
  67. input_config = yaml.safe_load(afile)
  68. result_config = tidy_config_validation.filter_config(input_config)
  69. with open(filtered_config, 'w') as afile:
  70. yaml.safe_dump(result_config, afile)
  71. def filter_cmd(cmd):
  72. skip = True
  73. for x in cmd:
  74. if not skip:
  75. yield x
  76. if '/wrapcc.py' in x:
  77. skip = False
  78. def walk(p):
  79. for a, b, c in os.walk(p):
  80. for x in c:
  81. yield os.path.join(a, x)
  82. def find_header(p, h):
  83. for x in walk(p):
  84. if x.endswith(h):
  85. return os.path.dirname(x)
  86. raise Exception('can not find inc dir')
  87. def main():
  88. args, clang_cmd = parse_args()
  89. if '/wrapcc.py' in str(clang_cmd):
  90. clang_cmd = list(filter_cmd(clang_cmd))
  91. setup_script(args)
  92. clang_tidy_bin = args.clang_tidy_bin
  93. output_json = args.tidy_json
  94. generate_outputs(output_json)
  95. if is_generated(args.testing_src, args.build_root):
  96. return
  97. if args.header_filter is None:
  98. # .pb.h files will be excluded because they are not in source_root
  99. header_filter = r"^" + re.escape(os.path.dirname(args.testing_src)) + r".*"
  100. else:
  101. header_filter = r"^(" + args.header_filter + r").*"
  102. def ensure_clean_dir(path):
  103. path = os.path.join(args.build_root, path)
  104. if os.path.exists(path):
  105. shutil.rmtree(path)
  106. os.makedirs(path)
  107. return path
  108. profile_tmpdir = ensure_clean_dir("profile_tmpdir")
  109. db_tmpdir = ensure_clean_dir("db_tmpdir")
  110. fixes_file = "fixes.txt"
  111. config_dir = ensure_clean_dir("config_dir")
  112. result_config_file = args.default_config_file
  113. if args.project_config_file != args.default_config_file:
  114. result_config = os.path.join(config_dir, "result_tidy_config.yaml")
  115. filtered_config = os.path.join(config_dir, "filtered_tidy_config.yaml")
  116. filter_configs(args.project_config_file, filtered_config)
  117. result_config_file = tidy_config_validation.merge_tidy_configs(
  118. base_config_path=args.default_config_file,
  119. additional_config_path=filtered_config,
  120. result_config_path=result_config,
  121. )
  122. compile_command_path = generate_compilation_database(clang_cmd, args.source_root, args.testing_src, db_tmpdir)
  123. cmd = [
  124. clang_tidy_bin,
  125. args.testing_src,
  126. "-p",
  127. compile_command_path,
  128. "--warnings-as-errors",
  129. "*,-clang-diagnostic-#pragma-messages",
  130. "--config-file",
  131. result_config_file,
  132. "--header-filter",
  133. header_filter,
  134. "--use-color",
  135. "--enable-check-profile",
  136. "--store-check-profile={}".format(profile_tmpdir),
  137. ]
  138. if args.export_fixes == "yes":
  139. cmd += ["--export-fixes", fixes_file]
  140. if args.checks:
  141. cmd += ["--checks", args.checks]
  142. print("cmd: {}".format(' '.join(cmd)))
  143. res = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  144. out, err = res.communicate()
  145. out = out.replace(args.source_root, "$(SOURCE_ROOT)")
  146. profile = load_profile(profile_tmpdir)
  147. testing_src = os.path.relpath(args.testing_src, args.source_root)
  148. tidy_fixes = load_fixes(fixes_file)
  149. with open(output_json, "wb") as afile:
  150. json.dump(
  151. {
  152. "file": testing_src,
  153. "exit_code": res.returncode,
  154. "profile": profile,
  155. "stderr": err,
  156. "stdout": out,
  157. "fixes": tidy_fixes,
  158. },
  159. afile,
  160. )
  161. if __name__ == "__main__":
  162. main()