check_include_guards.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. #!/usr/bin/env python3
  2. # Copyright 2016 gRPC authors.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. import argparse
  16. import os
  17. import os.path
  18. import re
  19. import subprocess
  20. import sys
  21. def build_valid_guard(fpath):
  22. prefix = 'GRPC_' if not fpath.startswith('include/') else ''
  23. return prefix + '_'.join(
  24. fpath.replace('++', 'XX').replace('.', '_').upper().split('/')[1:])
  25. def load(fpath):
  26. with open(fpath, 'r') as f:
  27. return f.read()
  28. def save(fpath, contents):
  29. with open(fpath, 'w') as f:
  30. f.write(contents)
  31. class GuardValidator(object):
  32. def __init__(self):
  33. self.ifndef_re = re.compile(r'#ifndef ([A-Z][A-Z_1-9]*)')
  34. self.define_re = re.compile(r'#define ([A-Z][A-Z_1-9]*)')
  35. self.endif_c_core_re = re.compile(
  36. r'#endif /\* (?: *\\\n *)?([A-Z][A-Z_1-9]*) (?:\\\n *)?\*/$')
  37. self.endif_re = re.compile(r'#endif // ([A-Z][A-Z_1-9]*)')
  38. self.failed = False
  39. def _is_c_core_header(self, fpath):
  40. return 'include' in fpath and not ('grpc++' in fpath or 'grpcpp'
  41. in fpath or 'event_engine' in fpath)
  42. def fail(self, fpath, regexp, fcontents, match_txt, correct, fix):
  43. c_core_header = self._is_c_core_header(fpath)
  44. self.failed = True
  45. invalid_guards_msg_template = (
  46. '{0}: Missing preprocessor guards (RE {1}). '
  47. 'Please wrap your code around the following guards:\n'
  48. '#ifndef {2}\n'
  49. '#define {2}\n'
  50. '...\n'
  51. '... epic code ...\n'
  52. '...\n') + ('#endif /* {2} */'
  53. if c_core_header else '#endif // {2}')
  54. if not match_txt:
  55. print(
  56. (invalid_guards_msg_template.format(fpath, regexp.pattern,
  57. build_valid_guard(fpath))))
  58. return fcontents
  59. print((('{}: Wrong preprocessor guards (RE {}):'
  60. '\n\tFound {}, expected {}').format(fpath, regexp.pattern,
  61. match_txt, correct)))
  62. if fix:
  63. print(('Fixing {}...\n'.format(fpath)))
  64. fixed_fcontents = re.sub(match_txt, correct, fcontents)
  65. if fixed_fcontents:
  66. self.failed = False
  67. return fixed_fcontents
  68. else:
  69. print()
  70. return fcontents
  71. def check(self, fpath, fix):
  72. c_core_header = self._is_c_core_header(fpath)
  73. valid_guard = build_valid_guard(fpath)
  74. fcontents = load(fpath)
  75. match = self.ifndef_re.search(fcontents)
  76. if not match:
  77. print(('something drastically wrong with: %s' % fpath))
  78. return False # failed
  79. if match.lastindex is None:
  80. # No ifndef. Request manual addition with hints
  81. self.fail(fpath, match.re, match.string, '', '', False)
  82. return False # failed
  83. # Does the guard end with a '_H'?
  84. running_guard = match.group(1)
  85. if not running_guard.endswith('_H'):
  86. fcontents = self.fail(fpath, match.re, match.string, match.group(1),
  87. valid_guard, fix)
  88. if fix:
  89. save(fpath, fcontents)
  90. # Is it the expected one based on the file path?
  91. if running_guard != valid_guard:
  92. fcontents = self.fail(fpath, match.re, match.string, match.group(1),
  93. valid_guard, fix)
  94. if fix:
  95. save(fpath, fcontents)
  96. # Is there a #define? Is it the same as the #ifndef one?
  97. match = self.define_re.search(fcontents)
  98. if match.lastindex is None:
  99. # No define. Request manual addition with hints
  100. self.fail(fpath, match.re, match.string, '', '', False)
  101. return False # failed
  102. # Is the #define guard the same as the #ifndef guard?
  103. if match.group(1) != running_guard:
  104. fcontents = self.fail(fpath, match.re, match.string, match.group(1),
  105. valid_guard, fix)
  106. if fix:
  107. save(fpath, fcontents)
  108. # Is there a properly commented #endif?
  109. flines = fcontents.rstrip().splitlines()
  110. match = self.endif_c_core_re.search('\n'.join(flines[-3:]))
  111. if not match and not c_core_header:
  112. match = self.endif_re.search('\n'.join(flines[-3:]))
  113. if not match:
  114. # No endif. Check if we have the last line as just '#endif' and if so
  115. # replace it with a properly commented one.
  116. if flines[-1] == '#endif':
  117. flines[-1] = (
  118. '#endif' +
  119. (' /* {} */\n'.format(valid_guard)
  120. if c_core_header else ' // {}\n'.format(valid_guard)))
  121. if fix:
  122. fcontents = '\n'.join(flines)
  123. save(fpath, fcontents)
  124. else:
  125. # something else is wrong, bail out
  126. self.fail(
  127. fpath,
  128. self.endif_c_core_re if c_core_header else self.endif_re,
  129. flines[-1], '', '', False)
  130. elif match.group(1) != running_guard:
  131. # Is the #endif guard the same as the #ifndef and #define guards?
  132. fcontents = self.fail(fpath, self.endif_re, fcontents,
  133. match.group(1), valid_guard, fix)
  134. if fix:
  135. save(fpath, fcontents)
  136. return not self.failed # Did the check succeed? (ie, not failed)
  137. # find our home
  138. ROOT = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '../..'))
  139. os.chdir(ROOT)
  140. # parse command line
  141. argp = argparse.ArgumentParser(description='include guard checker')
  142. argp.add_argument('-f', '--fix', default=False, action='store_true')
  143. argp.add_argument('--precommit', default=False, action='store_true')
  144. args = argp.parse_args()
  145. grep_filter = r"grep -E '^(include|src/core)/.*\.h$'"
  146. if args.precommit:
  147. git_command = 'git diff --name-only HEAD'
  148. else:
  149. git_command = 'git ls-tree -r --name-only -r HEAD'
  150. FILE_LIST_COMMAND = ' | '.join((git_command, grep_filter))
  151. # scan files
  152. ok = True
  153. filename_list = []
  154. try:
  155. filename_list = subprocess.check_output(FILE_LIST_COMMAND,
  156. shell=True).decode().splitlines()
  157. # Filter out non-existent files (ie, file removed or renamed)
  158. filename_list = (f for f in filename_list if os.path.isfile(f))
  159. except subprocess.CalledProcessError:
  160. sys.exit(0)
  161. validator = GuardValidator()
  162. for filename in filename_list:
  163. # Skip check for upb generated code.
  164. if (filename.endswith('.upb.h') or filename.endswith('.upb.c') or
  165. filename.endswith('.upbdefs.h') or filename.endswith('.upbdefs.c')):
  166. continue
  167. ok = ok and validator.check(filename, args.fix)
  168. sys.exit(0 if ok else 1)