clang_tidy.py 6.0 KB

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