• 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
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