blob: 0850196e31b1594e8923e263ae71468794bcba0b [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
wychenef74ec992017-04-27 06:28:2518from multiprocessing import Process, Queue
wychen037f6e9e2017-01-10 17:14:5619
20
wychenef74ec992017-04-27 06:28:2521def GetHeadersFromNinja(out_dir, q):
wychen037f6e9e2017-01-10 17:14:5622 """Return all the header files from ninja_deps"""
wychenef74ec992017-04-27 06:28:2523
24 def NinjaSource():
25 cmd = ['ninja', '-C', out_dir, '-t', 'deps']
26 # A negative bufsize means to use the system default, which usually
27 # means fully buffered.
28 popen = subprocess.Popen(cmd, stdout=subprocess.PIPE, bufsize=-1)
29 for line in iter(popen.stdout.readline, ''):
30 yield line.rstrip()
31
32 popen.stdout.close()
33 return_code = popen.wait()
34 if return_code:
35 raise subprocess.CalledProcessError(return_code, cmd)
36
37 ninja_out = NinjaSource()
38 q.put(ParseNinjaDepsOutput(ninja_out))
wychen037f6e9e2017-01-10 17:14:5639
40
41def ParseNinjaDepsOutput(ninja_out):
42 """Parse ninja output and get the header files"""
43 all_headers = set()
44
45 prefix = '..' + os.sep + '..' + os.sep
46
47 is_valid = False
wychenef74ec992017-04-27 06:28:2548 for line in ninja_out:
wychen037f6e9e2017-01-10 17:14:5649 if line.startswith(' '):
50 if not is_valid:
51 continue
52 if line.endswith('.h') or line.endswith('.hh'):
53 f = line.strip()
54 if f.startswith(prefix):
55 f = f[6:] # Remove the '../../' prefix
56 # build/ only contains build-specific files like build_config.h
57 # and buildflag.h, and system header files, so they should be
58 # skipped.
59 if not f.startswith('build'):
60 all_headers.add(f)
61 else:
62 is_valid = line.endswith('(VALID)')
63
64 return all_headers
65
66
wychenef74ec992017-04-27 06:28:2567def GetHeadersFromGN(out_dir, q):
wychen037f6e9e2017-01-10 17:14:5668 """Return all the header files from GN"""
69 subprocess.check_call(['gn', 'gen', out_dir, '--ide=json', '-q'])
70 gn_json = json.load(open(os.path.join(out_dir, 'project.json')))
wychenef74ec992017-04-27 06:28:2571 q.put(ParseGNProjectJSON(gn_json))
wychen037f6e9e2017-01-10 17:14:5672
73
74def ParseGNProjectJSON(gn):
75 """Parse GN output and get the header files"""
76 all_headers = set()
77
78 for _target, properties in gn['targets'].iteritems():
wychen55235782017-04-28 01:59:1579 sources = properties.get('sources', [])
80 public = properties.get('public', [])
81 # Exclude '"public": "*"'.
82 if type(public) is list:
83 sources += public
84 for f in sources:
wychen037f6e9e2017-01-10 17:14:5685 if f.endswith('.h') or f.endswith('.hh'):
86 if f.startswith('//'):
87 f = f[2:] # Strip the '//' prefix.
88 all_headers.add(f)
89
90 return all_headers
91
92
wychenef74ec992017-04-27 06:28:2593def GetDepsPrefixes(q):
wychen037f6e9e2017-01-10 17:14:5694 """Return all the folders controlled by DEPS file"""
95 gclient_out = subprocess.check_output(
96 ['gclient', 'recurse', '--no-progress', '-j1',
97 'python', '-c', 'import os;print os.environ["GCLIENT_DEP_PATH"]'])
98 prefixes = set()
99 for i in gclient_out.split('\n'):
100 if i.startswith('src/'):
101 i = i[4:]
102 prefixes.add(i)
wychenef74ec992017-04-27 06:28:25103 q.put(prefixes)
wychen037f6e9e2017-01-10 17:14:56104
105
106def ParseWhiteList(whitelist):
107 out = set()
108 for line in whitelist.split('\n'):
109 line = re.sub(r'#.*', '', line).strip()
110 if line:
111 out.add(line)
112 return out
113
114
wychene7a3d6482017-04-29 07:12:17115def FilterOutDepsedRepo(files, deps):
116 return {f for f in files if not any(f.startswith(d) for d in deps)}
117
118
119def GetNonExistingFiles(lst):
120 out = set()
121 for f in lst:
122 if not os.path.isfile(f):
123 out.add(f)
124 return out
125
126
wychen037f6e9e2017-01-10 17:14:56127def main():
128 parser = argparse.ArgumentParser()
129 parser.add_argument('--out-dir', default='out/Release')
130 parser.add_argument('--json')
131 parser.add_argument('--whitelist')
132 parser.add_argument('args', nargs=argparse.REMAINDER)
133
134 args, _extras = parser.parse_known_args()
135
wychenef74ec992017-04-27 06:28:25136 d_q = Queue()
137 d_p = Process(target=GetHeadersFromNinja, args=(args.out_dir, d_q,))
138 d_p.start()
139
140 gn_q = Queue()
141 gn_p = Process(target=GetHeadersFromGN, args=(args.out_dir, gn_q,))
142 gn_p.start()
143
144 deps_q = Queue()
145 deps_p = Process(target=GetDepsPrefixes, args=(deps_q,))
146 deps_p.start()
147
148 d = d_q.get()
wychene7a3d6482017-04-29 07:12:17149 assert len(GetNonExistingFiles(d)) == 0, \
150 'Found non-existing files in ninja deps'
wychenef74ec992017-04-27 06:28:25151 gn = gn_q.get()
wychen037f6e9e2017-01-10 17:14:56152 missing = d - gn
wychene7a3d6482017-04-29 07:12:17153 nonexisting = GetNonExistingFiles(gn)
wychen037f6e9e2017-01-10 17:14:56154
wychenef74ec992017-04-27 06:28:25155 deps = deps_q.get()
wychene7a3d6482017-04-29 07:12:17156 missing = FilterOutDepsedRepo(missing, deps)
157 nonexisting = FilterOutDepsedRepo(nonexisting, deps)
wychen037f6e9e2017-01-10 17:14:56158
wychenef74ec992017-04-27 06:28:25159 d_p.join()
160 gn_p.join()
161 deps_p.join()
162
wychen037f6e9e2017-01-10 17:14:56163 if args.whitelist:
164 whitelist = ParseWhiteList(open(args.whitelist).read())
165 missing -= whitelist
166
167 missing = sorted(missing)
wychene7a3d6482017-04-29 07:12:17168 nonexisting = sorted(nonexisting)
wychen037f6e9e2017-01-10 17:14:56169
170 if args.json:
171 with open(args.json, 'w') as f:
172 json.dump(missing, f)
173
wychene7a3d6482017-04-29 07:12:17174 if len(missing) == 0 and len(nonexisting) == 0:
wychen037f6e9e2017-01-10 17:14:56175 return 0
176
wychene7a3d6482017-04-29 07:12:17177 if len(missing) > 0:
178 print '\nThe following files should be included in gn files:'
179 for i in missing:
180 print i
181
182 if len(nonexisting) > 0:
183 print '\nThe following non-existing files should be removed from gn files:'
184 for i in nonexisting:
185 print i
186
wychen037f6e9e2017-01-10 17:14:56187 return 1
188
189
190if __name__ == '__main__':
191 sys.exit(main())