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