Home | History | Annotate | Download | only in tools
      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()
    157