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"""
7Script 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
9and gn files. The latter inclusion is overapproximated.
10
11TODO(machenbach): If two source files with the same name exist, but only one
12is referenced from a gyp/gn file, we won't necessarily detect it.
13"""
14
15import itertools
16import re
17import os
18import subprocess
19import sys
20
21
22V8_BASE = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
23
24GYP_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
39ALL_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
54GYP_UNSUPPORTED_FEATURES = [
55  'gcmole',
56]
57
58GN_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
69GN_UNSUPPORTED_FEATURES = [
70  'aix',
71  'cygwin',
72  'freebsd',
73  'gcmole',
74  'openbsd',
75  'ppc',
76  'qnx',
77  'solaris',
78  'vtune',
79  'x87',
80]
81
82ALL_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
92def pathsplit(path):
93  return re.split('[/\\\\]', path)
94
95def 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
102def 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
110def 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
123def 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
132def 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
145def icheck_values(values, prefixes):
146  for source_file in isources(prefixes):
147    if source_file not in values:
148      yield source_file
149
150
151def 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
160def 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
170def 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
180if '__main__' == __name__:
181  sys.exit(main())
182