• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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