generate-header-include-checks.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. #!/usr/bin/env python
  2. # vim:fenc=utf-8:shiftwidth=2
  3. # Copyright 2018 the V8 project authors. All rights reserved.
  4. # Use of this source code is governed by a BSD-style license that can be
  5. # found in the LICENSE file.
  6. """Check that each header can be included in isolation.
  7. For each header we generate one .cc file which only includes this one header.
  8. All these .cc files are then added to a sources.gni file which is included in
  9. BUILD.gn. Just compile to check whether there are any violations to the rule
  10. that each header must be includable in isolation.
  11. """
  12. # for py2/py3 compatibility
  13. from __future__ import print_function
  14. import argparse
  15. import os
  16. import os.path
  17. import re
  18. import sys
  19. # TODO(clemensb): Extend to tests.
  20. DEFAULT_INPUT = ['base', 'include', 'src']
  21. DEFAULT_GN_FILE = 'BUILD.gn'
  22. MY_DIR = os.path.dirname(os.path.realpath(__file__))
  23. V8_DIR = os.path.dirname(MY_DIR)
  24. OUT_DIR = os.path.join(V8_DIR, 'check-header-includes')
  25. AUTO_EXCLUDE = [
  26. # flag-definitions.h needs a mode set for being included.
  27. 'src/flags/flag-definitions.h',
  28. # recorder.h should only be included conditionally.
  29. 'src/libplatform/tracing/recorder.h',
  30. # trap-handler-simulator.h can only be included in simulator builds.
  31. 'src/trap-handler/trap-handler-simulator.h',
  32. ]
  33. AUTO_EXCLUDE_PATTERNS = [
  34. 'src/base/atomicops_internals_.*',
  35. # TODO(petermarshall): Enable once Perfetto is built by default.
  36. 'src/libplatform/tracing/perfetto*',
  37. # TODO(v8:7700): Enable once Maglev is built by default.
  38. 'src/maglev/.*',
  39. ] + [
  40. # platform-specific headers
  41. '\\b{}\\b'.format(p)
  42. for p in ('win', 'win32', 'ia32', 'x64', 'arm', 'arm64', 'mips', 'mips64',
  43. 's390', 'ppc', 'riscv64', 'loong64')
  44. ]
  45. args = None
  46. def parse_args():
  47. global args
  48. parser = argparse.ArgumentParser()
  49. parser.add_argument('-i', '--input', type=str, action='append',
  50. help='Headers or directories to check (directories '
  51. 'are scanned for headers recursively); default: ' +
  52. ','.join(DEFAULT_INPUT))
  53. parser.add_argument('-x', '--exclude', type=str, action='append',
  54. help='Add an exclude pattern (regex)')
  55. parser.add_argument('-v', '--verbose', action='store_true',
  56. help='Be verbose')
  57. args = parser.parse_args()
  58. args.exclude = (args.exclude or []) + AUTO_EXCLUDE_PATTERNS
  59. args.exclude += ['^' + re.escape(x) + '$' for x in AUTO_EXCLUDE]
  60. if not args.input:
  61. args.input=DEFAULT_INPUT
  62. def printv(line):
  63. if args.verbose:
  64. print(line)
  65. def find_all_headers():
  66. printv('Searching for headers...')
  67. header_files = []
  68. exclude_patterns = [re.compile(x) for x in args.exclude]
  69. def add_recursively(filename):
  70. full_name = os.path.join(V8_DIR, filename)
  71. if not os.path.exists(full_name):
  72. sys.exit('File does not exist: {}'.format(full_name))
  73. if os.path.isdir(full_name):
  74. for subfile in os.listdir(full_name):
  75. full_name = os.path.join(filename, subfile)
  76. printv('Scanning {}'.format(full_name))
  77. add_recursively(full_name)
  78. elif filename.endswith('.h'):
  79. printv('--> Found header file {}'.format(filename))
  80. for p in exclude_patterns:
  81. if p.search(filename):
  82. printv('--> EXCLUDED (matches {})'.format(p.pattern))
  83. return
  84. header_files.append(filename)
  85. for filename in args.input:
  86. add_recursively(filename)
  87. return header_files
  88. def get_cc_file_name(header):
  89. split = os.path.split(header)
  90. header_dir = os.path.relpath(split[0], V8_DIR)
  91. # Prefix with the directory name, to avoid collisions in the object files.
  92. prefix = header_dir.replace(os.path.sep, '-')
  93. cc_file_name = 'test-include-' + prefix + '-' + split[1][:-1] + 'cc'
  94. return os.path.join(OUT_DIR, cc_file_name)
  95. def create_including_cc_files(header_files):
  96. comment = 'check including this header in isolation'
  97. for header in header_files:
  98. cc_file_name = get_cc_file_name(header)
  99. rel_cc_file_name = os.path.relpath(cc_file_name, V8_DIR)
  100. content = '#include "{}" // {}\n'.format(header, comment)
  101. if os.path.exists(cc_file_name):
  102. with open(cc_file_name) as cc_file:
  103. if cc_file.read() == content:
  104. printv('File {} is up to date'.format(rel_cc_file_name))
  105. continue
  106. printv('Creating file {}'.format(rel_cc_file_name))
  107. with open(cc_file_name, 'w') as cc_file:
  108. cc_file.write(content)
  109. def generate_gni(header_files):
  110. gni_file = os.path.join(OUT_DIR, 'sources.gni')
  111. printv('Generating file "{}"'.format(os.path.relpath(gni_file, V8_DIR)))
  112. with open(gni_file, 'w') as gn:
  113. gn.write("""\
  114. # Copyright 2018 The Chromium Authors. All rights reserved.
  115. # Use of this source code is governed by a BSD-style license that can be
  116. # found in the LICENSE file.
  117. # This list is filled automatically by tools/check_header_includes.py.
  118. check_header_includes_sources = [
  119. """);
  120. for header in header_files:
  121. cc_file_name = get_cc_file_name(header)
  122. gn.write(' "{}",\n'.format(os.path.relpath(cc_file_name, V8_DIR)))
  123. gn.write(']\n')
  124. def main():
  125. parse_args()
  126. header_files = find_all_headers()
  127. if not os.path.exists(OUT_DIR):
  128. os.mkdir(OUT_DIR)
  129. create_including_cc_files(header_files)
  130. generate_gni(header_files)
  131. if __name__ == '__main__':
  132. main()