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