Rubin Xu | 2894c6a | 2019-02-07 16:01:35 +0000 | [diff] [blame] | 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() |