blob: ae1ef49c179abd1faa254180d859042beebe773d [file] [log] [blame]
wychen037f6e9e2017-01-10 17:14:561#!/usr/bin/env python
2# Copyright 2017 The Chromium 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"""Find header files missing in GN.
7
8This script gets all the header files from ninja_deps, which is from the true
9dependency generated by the compiler, and report if they don't exist in GN.
10"""
11
12import argparse
13import json
14import os
15import re
16import subprocess
17import sys
18
19
20def GetHeadersFromNinja(out_dir):
21 """Return all the header files from ninja_deps"""
22 ninja_out = subprocess.check_output(['ninja', '-C', out_dir, '-t', 'deps'])
23 return ParseNinjaDepsOutput(ninja_out)
24
25
26def ParseNinjaDepsOutput(ninja_out):
27 """Parse ninja output and get the header files"""
28 all_headers = set()
29
30 prefix = '..' + os.sep + '..' + os.sep
31
32 is_valid = False
33 for line in ninja_out.split('\n'):
34 if line.startswith(' '):
35 if not is_valid:
36 continue
37 if line.endswith('.h') or line.endswith('.hh'):
38 f = line.strip()
39 if f.startswith(prefix):
40 f = f[6:] # Remove the '../../' prefix
41 # build/ only contains build-specific files like build_config.h
42 # and buildflag.h, and system header files, so they should be
43 # skipped.
44 if not f.startswith('build'):
45 all_headers.add(f)
46 else:
47 is_valid = line.endswith('(VALID)')
48
49 return all_headers
50
51
52def GetHeadersFromGN(out_dir):
53 """Return all the header files from GN"""
54 subprocess.check_call(['gn', 'gen', out_dir, '--ide=json', '-q'])
55 gn_json = json.load(open(os.path.join(out_dir, 'project.json')))
56 return ParseGNProjectJSON(gn_json)
57
58
59def ParseGNProjectJSON(gn):
60 """Parse GN output and get the header files"""
61 all_headers = set()
62
63 for _target, properties in gn['targets'].iteritems():
64 for f in properties.get('sources', []):
65 if f.endswith('.h') or f.endswith('.hh'):
66 if f.startswith('//'):
67 f = f[2:] # Strip the '//' prefix.
68 all_headers.add(f)
69
70 return all_headers
71
72
73def GetDepsPrefixes():
74 """Return all the folders controlled by DEPS file"""
75 gclient_out = subprocess.check_output(
76 ['gclient', 'recurse', '--no-progress', '-j1',
77 'python', '-c', 'import os;print os.environ["GCLIENT_DEP_PATH"]'])
78 prefixes = set()
79 for i in gclient_out.split('\n'):
80 if i.startswith('src/'):
81 i = i[4:]
82 prefixes.add(i)
83 return prefixes
84
85
86def ParseWhiteList(whitelist):
87 out = set()
88 for line in whitelist.split('\n'):
89 line = re.sub(r'#.*', '', line).strip()
90 if line:
91 out.add(line)
92 return out
93
94
95def main():
96 parser = argparse.ArgumentParser()
97 parser.add_argument('--out-dir', default='out/Release')
98 parser.add_argument('--json')
99 parser.add_argument('--whitelist')
100 parser.add_argument('args', nargs=argparse.REMAINDER)
101
102 args, _extras = parser.parse_known_args()
103
104 d = GetHeadersFromNinja(args.out_dir)
105 gn = GetHeadersFromGN(args.out_dir)
106 missing = d - gn
107
108 deps = GetDepsPrefixes()
109 missing = {m for m in missing if not any(m.startswith(d) for d in deps)}
110
111 if args.whitelist:
112 whitelist = ParseWhiteList(open(args.whitelist).read())
113 missing -= whitelist
114
115 missing = sorted(missing)
116
117 if args.json:
118 with open(args.json, 'w') as f:
119 json.dump(missing, f)
120
121 if len(missing) == 0:
122 return 0
123
124 print 'The following files should be included in gn files:'
125 for i in missing:
126 print i
127 return 1
128
129
130if __name__ == '__main__':
131 sys.exit(main())