blob: ca3b0079ca06b7ca7b7ae34c415f3a3cd9fdd65a [file] [log] [blame]
Rubin Xu2894c6a2019-02-07 16:01:35 +00001#!/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()