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 10For each header we generate one .cc file which only includes this one header. 11All these .cc files are then added to a sources.gni file which is included in 12BUILD.gn. Just compile to check whether there are any violations to the rule 13that each header must be includable in isolation. 14""" 15 16import argparse 17import os 18import os.path 19import re 20import sys 21 22# TODO(clemensh): Extend to tests. 23DEFAULT_INPUT = ['base', 'src'] 24DEFAULT_GN_FILE = 'BUILD.gn' 25MY_DIR = os.path.dirname(os.path.realpath(__file__)) 26V8_DIR = os.path.dirname(MY_DIR) 27OUT_DIR = os.path.join(V8_DIR, 'check-header-includes') 28AUTO_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] 46AUTO_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 53args = None 54def 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 72def printv(line): 73 if args.verbose: 74 print line 75 76 77def 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 104def 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 113def 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 129def 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. 139check_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 147def 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 155if __name__ == '__main__': 156 main() 157