Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(830)

Side by Side Diff: build/check_gn_headers.py

Issue 2911543002: Make check_gn_headers.py more robust on the bots (Closed)
Patch Set: filter out* as well Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | build/check_gn_headers_unittest.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright 2017 The Chromium Authors. All rights reserved. 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 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 """Find header files missing in GN. 6 """Find header files missing in GN.
7 7
8 This script gets all the header files from ninja_deps, which is from the true 8 This script gets all the header files from ninja_deps, which is from the true
9 dependency generated by the compiler, and report if they don't exist in GN. 9 dependency generated by the compiler, and report if they don't exist in GN.
10 """ 10 """
(...skipping 20 matching lines...) Expand all
31 for line in iter(popen.stdout.readline, ''): 31 for line in iter(popen.stdout.readline, ''):
32 yield line.rstrip() 32 yield line.rstrip()
33 33
34 popen.stdout.close() 34 popen.stdout.close()
35 return_code = popen.wait() 35 return_code = popen.wait()
36 if return_code: 36 if return_code:
37 raise subprocess.CalledProcessError(return_code, cmd) 37 raise subprocess.CalledProcessError(return_code, cmd)
38 38
39 ans, err = set(), None 39 ans, err = set(), None
40 try: 40 try:
41 ans = ParseNinjaDepsOutput(NinjaSource()) 41 ans = ParseNinjaDepsOutput(NinjaSource(), out_dir)
42 except Exception as e: 42 except Exception as e:
43 err = str(e) 43 err = str(e)
44 q.put((ans, err)) 44 q.put((ans, err))
45 45
46 46
47 def ParseNinjaDepsOutput(ninja_out): 47 def ParseNinjaDepsOutput(ninja_out, out_dir):
48 """Parse ninja output and get the header files""" 48 """Parse ninja output and get the header files"""
49 all_headers = set() 49 all_headers = set()
50 50
51 prefix = '..' + os.sep + '..' + os.sep 51 prefix = '..' + os.sep + '..' + os.sep
52 52
53 is_valid = False 53 is_valid = False
54 for line in ninja_out: 54 for line in ninja_out:
55 if line.startswith(' '): 55 if line.startswith(' '):
56 if not is_valid: 56 if not is_valid:
57 continue 57 continue
58 if line.endswith('.h') or line.endswith('.hh'): 58 if line.endswith('.h') or line.endswith('.hh'):
59 f = line.strip() 59 f = line.strip()
60 if f.startswith(prefix): 60 if f.startswith(prefix):
61 f = f[6:] # Remove the '../../' prefix 61 f = f[6:] # Remove the '../../' prefix
62 # build/ only contains build-specific files like build_config.h 62 # build/ only contains build-specific files like build_config.h
63 # and buildflag.h, and system header files, so they should be 63 # and buildflag.h, and system header files, so they should be
64 # skipped. 64 # skipped.
65 if f.startswith(out_dir) or f.startswith('out'):
66 continue
65 if not f.startswith('build'): 67 if not f.startswith('build'):
66 all_headers.add(f) 68 all_headers.add(f)
67 else: 69 else:
68 is_valid = line.endswith('(VALID)') 70 is_valid = line.endswith('(VALID)')
69 71
70 return all_headers 72 return all_headers
71 73
72 74
73 def GetHeadersFromGN(out_dir, q): 75 def GetHeadersFromGN(out_dir, q):
74 """Return all the header files from GN""" 76 """Return all the header files from GN"""
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
121 'python', '-c', 'import os;print os.environ["GCLIENT_DEP_PATH"]']) 123 'python', '-c', 'import os;print os.environ["GCLIENT_DEP_PATH"]'])
122 for i in gclient_out.split('\n'): 124 for i in gclient_out.split('\n'):
123 if i.startswith('src/'): 125 if i.startswith('src/'):
124 i = i[4:] 126 i = i[4:]
125 prefixes.add(i) 127 prefixes.add(i)
126 except Exception as e: 128 except Exception as e:
127 err = str(e) 129 err = str(e)
128 q.put((prefixes, err)) 130 q.put((prefixes, err))
129 131
130 132
133 def IsBuildClean(out_dir):
134 cmd = ['ninja', '-C', out_dir, '-n']
135 out = subprocess.check_output(cmd)
136 return 'no work to do.' in out
137
138
131 def ParseWhiteList(whitelist): 139 def ParseWhiteList(whitelist):
132 out = set() 140 out = set()
133 for line in whitelist.split('\n'): 141 for line in whitelist.split('\n'):
134 line = re.sub(r'#.*', '', line).strip() 142 line = re.sub(r'#.*', '', line).strip()
135 if line: 143 if line:
136 out.add(line) 144 out.add(line)
137 return out 145 return out
138 146
139 147
140 def FilterOutDepsedRepo(files, deps): 148 def FilterOutDepsedRepo(files, deps):
141 return {f for f in files if not any(f.startswith(d) for d in deps)} 149 return {f for f in files if not any(f.startswith(d) for d in deps)}
142 150
143 151
144 def GetNonExistingFiles(lst): 152 def GetNonExistingFiles(lst):
145 out = set() 153 out = set()
146 for f in lst: 154 for f in lst:
147 if not os.path.isfile(f): 155 if not os.path.isfile(f):
148 out.add(f) 156 out.add(f)
149 return out 157 return out
150 158
151 159
152 def main(): 160 def main():
161
162 def DumpJson(data):
163 if args.json:
164 with open(args.json, 'w') as f:
165 json.dump(data, f)
166
167 def PrintError(msg):
168 DumpJson([])
169 parser.error(msg)
170
153 parser = argparse.ArgumentParser(description=''' 171 parser = argparse.ArgumentParser(description='''
154 NOTE: Use ninja to build all targets in OUT_DIR before running 172 NOTE: Use ninja to build all targets in OUT_DIR before running
155 this script.''') 173 this script.''')
156 parser.add_argument('--out-dir', metavar='OUT_DIR', default='out/Release', 174 parser.add_argument('--out-dir', metavar='OUT_DIR', default='out/Release',
157 help='output directory of the build') 175 help='output directory of the build')
158 parser.add_argument('--json', 176 parser.add_argument('--json',
159 help='JSON output filename for missing headers') 177 help='JSON output filename for missing headers')
160 parser.add_argument('--whitelist', help='file containing whitelist') 178 parser.add_argument('--whitelist', help='file containing whitelist')
179 parser.add_argument('--skip-dirty-check', action='store_true',
180 help='skip checking whether the build is dirty')
161 181
162 args, _extras = parser.parse_known_args() 182 args, _extras = parser.parse_known_args()
163 183
164 if not os.path.isdir(args.out_dir): 184 if not os.path.isdir(args.out_dir):
165 parser.error('OUT_DIR "%s" does not exist.' % args.out_dir) 185 parser.error('OUT_DIR "%s" does not exist.' % args.out_dir)
166 186
187 if not args.skip_dirty_check and not IsBuildClean(args.out_dir):
188 dirty_msg = 'OUT_DIR looks dirty. You need to build all there.'
189 if args.json:
190 # Assume running on the bots. Silently skip this step.
191 # This is possible because "analyze" step can be wrong due to
192 # underspecified header files. See crbug.com/725877
193 print dirty_msg
194 DumpJson([])
195 return 0
196 else:
197 # Assume running interactively.
198 parser.error(dirty_msg)
199
167 d_q = Queue() 200 d_q = Queue()
168 d_p = Process(target=GetHeadersFromNinja, args=(args.out_dir, d_q,)) 201 d_p = Process(target=GetHeadersFromNinja, args=(args.out_dir, d_q,))
169 d_p.start() 202 d_p.start()
170 203
171 gn_q = Queue() 204 gn_q = Queue()
172 gn_p = Process(target=GetHeadersFromGN, args=(args.out_dir, gn_q,)) 205 gn_p = Process(target=GetHeadersFromGN, args=(args.out_dir, gn_q,))
173 gn_p.start() 206 gn_p.start()
174 207
175 deps_q = Queue() 208 deps_q = Queue()
176 deps_p = Process(target=GetDepsPrefixes, args=(deps_q,)) 209 deps_p = Process(target=GetDepsPrefixes, args=(deps_q,))
177 deps_p.start() 210 deps_p.start()
178 211
179 d, d_err = d_q.get() 212 d, d_err = d_q.get()
180 gn, gn_err = gn_q.get() 213 gn, gn_err = gn_q.get()
181 missing = d - gn 214 missing = d - gn
182 nonexisting = GetNonExistingFiles(gn) 215 nonexisting = GetNonExistingFiles(gn)
183 216
184 deps, deps_err = deps_q.get() 217 deps, deps_err = deps_q.get()
185 missing = FilterOutDepsedRepo(missing, deps) 218 missing = FilterOutDepsedRepo(missing, deps)
186 nonexisting = FilterOutDepsedRepo(nonexisting, deps) 219 nonexisting = FilterOutDepsedRepo(nonexisting, deps)
187 220
188 d_p.join() 221 d_p.join()
189 gn_p.join() 222 gn_p.join()
190 deps_p.join() 223 deps_p.join()
191 224
192 if d_err: 225 if d_err:
193 parser.error(d_err) 226 PrintError(d_err)
Dirk Pranke 2017/06/03 01:16:37 I'd pass the parser into PrintError; relying on th
wychen 2017/06/03 07:37:37 It requires more than just |parser|. It also needs
194 if gn_err: 227 if gn_err:
195 parser.error(gn_err) 228 PrintError(gn_err)
196 if deps_err: 229 if deps_err:
197 parser.error(deps_err) 230 PrintError(deps_err)
198 if len(GetNonExistingFiles(d)) > 0: 231 if len(GetNonExistingFiles(d)) > 0:
199 parser.error('''Found non-existing files in ninja deps. You should 232 print 'Non-existing files in ninja deps:', GetNonExistingFiles(d)
200 build all in OUT_DIR.''') 233 PrintError('Found non-existing files in ninja deps. You should ' +
234 'build all in OUT_DIR.')
201 if len(d) == 0: 235 if len(d) == 0:
202 parser.error('OUT_DIR looks empty. You should build all there.') 236 PrintError('OUT_DIR looks empty. You should build all there.')
203 if any((('/gen/' in i) for i in nonexisting)): 237 if any((('/gen/' in i) for i in nonexisting)):
204 parser.error('OUT_DIR looks wrong. You should build all there.') 238 PrintError('OUT_DIR looks wrong. You should build all there.')
205 239
206 if args.whitelist: 240 if args.whitelist:
207 whitelist = ParseWhiteList(open(args.whitelist).read()) 241 whitelist = ParseWhiteList(open(args.whitelist).read())
208 missing -= whitelist 242 missing -= whitelist
243 nonexisting -= whitelist
209 244
210 missing = sorted(missing) 245 missing = sorted(missing)
211 nonexisting = sorted(nonexisting) 246 nonexisting = sorted(nonexisting)
212 247
213 if args.json: 248 DumpJson(sorted(missing + nonexisting))
214 with open(args.json, 'w') as f:
215 json.dump(missing, f)
216 249
217 if len(missing) == 0 and len(nonexisting) == 0: 250 if len(missing) == 0 and len(nonexisting) == 0:
218 return 0 251 return 0
219 252
220 if len(missing) > 0: 253 if len(missing) > 0:
221 print '\nThe following files should be included in gn files:' 254 print '\nThe following files should be included in gn files:'
222 for i in missing: 255 for i in missing:
223 print i 256 print i
224 257
225 if len(nonexisting) > 0: 258 if len(nonexisting) > 0:
226 print '\nThe following non-existing files should be removed from gn files:' 259 print '\nThe following non-existing files should be removed from gn files:'
227 for i in nonexisting: 260 for i in nonexisting:
228 print i 261 print i
229 262
230 return 1 263 return 1
231 264
232 265
233 if __name__ == '__main__': 266 if __name__ == '__main__':
234 sys.exit(main()) 267 sys.exit(main())
OLDNEW
« no previous file with comments | « no previous file | build/check_gn_headers_unittest.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698