1 #!/usr/bin/env python 2 # Copyright 2015 the V8 project authors. All rights reserved. 3 # Use of this source code is governed by a BSD-style license that can be 4 # found in the LICENSE file. 5 6 """ 7 Script to print potentially missing source dependencies based on the actual 8 .h and .cc files in the source tree and which files are included in the gyp 9 and gn files. The latter inclusion is overapproximated. 10 11 TODO(machenbach): If two source files with the same name exist, but only one 12 is referenced from a gyp/gn file, we won't necessarily detect it. 13 """ 14 15 import itertools 16 import re 17 import os 18 import subprocess 19 import sys 20 21 22 V8_BASE = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) 23 24 GYP_FILES = [ 25 os.path.join(V8_BASE, 'src', 'd8.gyp'), 26 os.path.join(V8_BASE, 'src', 'v8.gyp'), 27 os.path.join(V8_BASE, 'src', 'inspector', 'inspector.gypi'), 28 os.path.join(V8_BASE, 'src', 'third_party', 'vtune', 'v8vtune.gyp'), 29 os.path.join(V8_BASE, 'samples', 'samples.gyp'), 30 os.path.join(V8_BASE, 'test', 'cctest', 'cctest.gyp'), 31 os.path.join(V8_BASE, 'test', 'fuzzer', 'fuzzer.gyp'), 32 os.path.join(V8_BASE, 'test', 'unittests', 'unittests.gyp'), 33 os.path.join(V8_BASE, 'test', 'inspector', 'inspector.gyp'), 34 os.path.join(V8_BASE, 'testing', 'gmock.gyp'), 35 os.path.join(V8_BASE, 'testing', 'gtest.gyp'), 36 os.path.join(V8_BASE, 'tools', 'parser-shell.gyp'), 37 ] 38 39 ALL_GYP_PREFIXES = [ 40 '..', 41 'common', 42 os.path.join('src', 'third_party', 'vtune'), 43 'src', 44 'samples', 45 'testing', 46 'tools', 47 os.path.join('test', 'cctest'), 48 os.path.join('test', 'common'), 49 os.path.join('test', 'fuzzer'), 50 os.path.join('test', 'unittests'), 51 os.path.join('test', 'inspector'), 52 ] 53 54 GYP_UNSUPPORTED_FEATURES = [ 55 'gcmole', 56 ] 57 58 GN_FILES = [ 59 os.path.join(V8_BASE, 'BUILD.gn'), 60 os.path.join(V8_BASE, 'build', 'secondary', 'testing', 'gmock', 'BUILD.gn'), 61 os.path.join(V8_BASE, 'build', 'secondary', 'testing', 'gtest', 'BUILD.gn'), 62 os.path.join(V8_BASE, 'src', 'inspector', 'BUILD.gn'), 63 os.path.join(V8_BASE, 'test', 'cctest', 'BUILD.gn'), 64 os.path.join(V8_BASE, 'test', 'unittests', 'BUILD.gn'), 65 os.path.join(V8_BASE, 'test', 'inspector', 'BUILD.gn'), 66 os.path.join(V8_BASE, 'tools', 'BUILD.gn'), 67 ] 68 69 GN_UNSUPPORTED_FEATURES = [ 70 'aix', 71 'cygwin', 72 'freebsd', 73 'gcmole', 74 'openbsd', 75 'ppc', 76 'qnx', 77 'solaris', 78 'vtune', 79 'x87', 80 ] 81 82 ALL_GN_PREFIXES = [ 83 '..', 84 os.path.join('src', 'inspector'), 85 'src', 86 'testing', 87 os.path.join('test', 'cctest'), 88 os.path.join('test', 'unittests'), 89 os.path.join('test', 'inspector'), 90 ] 91 92 def pathsplit(path): 93 return re.split('[/\\\\]', path) 94 95 def path_no_prefix(path, prefixes): 96 for prefix in prefixes: 97 if path.startswith(prefix + os.sep): 98 return path_no_prefix(path[len(prefix) + 1:], prefixes) 99 return path 100 101 102 def isources(prefixes): 103 cmd = ['git', 'ls-tree', '-r', 'HEAD', '--full-name', '--name-only'] 104 for f in subprocess.check_output(cmd, universal_newlines=True).split('\n'): 105 if not (f.endswith('.h') or f.endswith('.cc')): 106 continue 107 yield path_no_prefix(os.path.join(*pathsplit(f)), prefixes) 108 109 110 def iflatten(obj): 111 if isinstance(obj, dict): 112 for value in obj.values(): 113 for i in iflatten(value): 114 yield i 115 elif isinstance(obj, list): 116 for value in obj: 117 for i in iflatten(value): 118 yield i 119 elif isinstance(obj, basestring): 120 yield path_no_prefix(os.path.join(*pathsplit(obj)), ALL_GYP_PREFIXES) 121 122 123 def iflatten_gyp_file(gyp_file): 124 """Overaproximates all values in the gyp file. 125 126 Iterates over all string values recursively. Removes '../' path prefixes. 127 """ 128 with open(gyp_file) as f: 129 return iflatten(eval(f.read())) 130 131 132 def iflatten_gn_file(gn_file): 133 """Overaproximates all values in the gn file. 134 135 Iterates over all double quoted strings. 136 """ 137 with open(gn_file) as f: 138 for line in f.read().splitlines(): 139 match = re.match(r'.*"([^"]*)".*', line) 140 if match: 141 yield path_no_prefix( 142 os.path.join(*pathsplit(match.group(1))), ALL_GN_PREFIXES) 143 144 145 def icheck_values(values, prefixes): 146 for source_file in isources(prefixes): 147 if source_file not in values: 148 yield source_file 149 150 151 def missing_gyp_files(): 152 gyp_values = set(itertools.chain( 153 *[iflatten_gyp_file(gyp_file) for gyp_file in GYP_FILES] 154 )) 155 gyp_files = sorted(icheck_values(gyp_values, ALL_GYP_PREFIXES)) 156 return filter( 157 lambda x: not any(i in x for i in GYP_UNSUPPORTED_FEATURES), gyp_files) 158 159 160 def missing_gn_files(): 161 gn_values = set(itertools.chain( 162 *[iflatten_gn_file(gn_file) for gn_file in GN_FILES] 163 )) 164 165 gn_files = sorted(icheck_values(gn_values, ALL_GN_PREFIXES)) 166 return filter( 167 lambda x: not any(i in x for i in GN_UNSUPPORTED_FEATURES), gn_files) 168 169 170 def main(): 171 print "----------- Files not in gyp: ------------" 172 for i in missing_gyp_files(): 173 print i 174 175 print "\n----------- Files not in gn: -------------" 176 for i in missing_gn_files(): 177 print i 178 return 0 179 180 if '__main__' == __name__: 181 sys.exit(main()) 182