1*635a8641SAndroid Build Coastguard Worker#!/usr/bin/env python 2*635a8641SAndroid Build Coastguard Worker# Copyright 2017 The Chromium Authors. All rights reserved. 3*635a8641SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be 4*635a8641SAndroid Build Coastguard Worker# found in the LICENSE file. 5*635a8641SAndroid Build Coastguard Worker 6*635a8641SAndroid Build Coastguard Worker"""Find header files missing in GN. 7*635a8641SAndroid Build Coastguard Worker 8*635a8641SAndroid Build Coastguard WorkerThis script gets all the header files from ninja_deps, which is from the true 9*635a8641SAndroid Build Coastguard Workerdependency generated by the compiler, and report if they don't exist in GN. 10*635a8641SAndroid Build Coastguard Worker""" 11*635a8641SAndroid Build Coastguard Worker 12*635a8641SAndroid Build Coastguard Workerimport argparse 13*635a8641SAndroid Build Coastguard Workerimport json 14*635a8641SAndroid Build Coastguard Workerimport os 15*635a8641SAndroid Build Coastguard Workerimport re 16*635a8641SAndroid Build Coastguard Workerimport shutil 17*635a8641SAndroid Build Coastguard Workerimport subprocess 18*635a8641SAndroid Build Coastguard Workerimport sys 19*635a8641SAndroid Build Coastguard Workerimport tempfile 20*635a8641SAndroid Build Coastguard Workerfrom multiprocessing import Process, Queue 21*635a8641SAndroid Build Coastguard Worker 22*635a8641SAndroid Build Coastguard WorkerSRC_DIR = os.path.abspath( 23*635a8641SAndroid Build Coastguard Worker os.path.join(os.path.abspath(os.path.dirname(__file__)), os.path.pardir)) 24*635a8641SAndroid Build Coastguard WorkerDEPOT_TOOLS_DIR = os.path.join(SRC_DIR, 'third_party', 'depot_tools') 25*635a8641SAndroid Build Coastguard Worker 26*635a8641SAndroid Build Coastguard Worker 27*635a8641SAndroid Build Coastguard Workerdef GetHeadersFromNinja(out_dir, skip_obj, q): 28*635a8641SAndroid Build Coastguard Worker """Return all the header files from ninja_deps""" 29*635a8641SAndroid Build Coastguard Worker 30*635a8641SAndroid Build Coastguard Worker def NinjaSource(): 31*635a8641SAndroid Build Coastguard Worker cmd = [os.path.join(DEPOT_TOOLS_DIR, 'ninja'), '-C', out_dir, '-t', 'deps'] 32*635a8641SAndroid Build Coastguard Worker # A negative bufsize means to use the system default, which usually 33*635a8641SAndroid Build Coastguard Worker # means fully buffered. 34*635a8641SAndroid Build Coastguard Worker popen = subprocess.Popen(cmd, stdout=subprocess.PIPE, bufsize=-1) 35*635a8641SAndroid Build Coastguard Worker for line in iter(popen.stdout.readline, ''): 36*635a8641SAndroid Build Coastguard Worker yield line.rstrip() 37*635a8641SAndroid Build Coastguard Worker 38*635a8641SAndroid Build Coastguard Worker popen.stdout.close() 39*635a8641SAndroid Build Coastguard Worker return_code = popen.wait() 40*635a8641SAndroid Build Coastguard Worker if return_code: 41*635a8641SAndroid Build Coastguard Worker raise subprocess.CalledProcessError(return_code, cmd) 42*635a8641SAndroid Build Coastguard Worker 43*635a8641SAndroid Build Coastguard Worker ans, err = set(), None 44*635a8641SAndroid Build Coastguard Worker try: 45*635a8641SAndroid Build Coastguard Worker ans = ParseNinjaDepsOutput(NinjaSource(), out_dir, skip_obj) 46*635a8641SAndroid Build Coastguard Worker except Exception as e: 47*635a8641SAndroid Build Coastguard Worker err = str(e) 48*635a8641SAndroid Build Coastguard Worker q.put((ans, err)) 49*635a8641SAndroid Build Coastguard Worker 50*635a8641SAndroid Build Coastguard Worker 51*635a8641SAndroid Build Coastguard Workerdef ParseNinjaDepsOutput(ninja_out, out_dir, skip_obj): 52*635a8641SAndroid Build Coastguard Worker """Parse ninja output and get the header files""" 53*635a8641SAndroid Build Coastguard Worker all_headers = {} 54*635a8641SAndroid Build Coastguard Worker 55*635a8641SAndroid Build Coastguard Worker # Ninja always uses "/", even on Windows. 56*635a8641SAndroid Build Coastguard Worker prefix = '../../' 57*635a8641SAndroid Build Coastguard Worker 58*635a8641SAndroid Build Coastguard Worker is_valid = False 59*635a8641SAndroid Build Coastguard Worker obj_file = '' 60*635a8641SAndroid Build Coastguard Worker for line in ninja_out: 61*635a8641SAndroid Build Coastguard Worker if line.startswith(' '): 62*635a8641SAndroid Build Coastguard Worker if not is_valid: 63*635a8641SAndroid Build Coastguard Worker continue 64*635a8641SAndroid Build Coastguard Worker if line.endswith('.h') or line.endswith('.hh'): 65*635a8641SAndroid Build Coastguard Worker f = line.strip() 66*635a8641SAndroid Build Coastguard Worker if f.startswith(prefix): 67*635a8641SAndroid Build Coastguard Worker f = f[6:] # Remove the '../../' prefix 68*635a8641SAndroid Build Coastguard Worker # build/ only contains build-specific files like build_config.h 69*635a8641SAndroid Build Coastguard Worker # and buildflag.h, and system header files, so they should be 70*635a8641SAndroid Build Coastguard Worker # skipped. 71*635a8641SAndroid Build Coastguard Worker if f.startswith(out_dir) or f.startswith('out'): 72*635a8641SAndroid Build Coastguard Worker continue 73*635a8641SAndroid Build Coastguard Worker if not f.startswith('build'): 74*635a8641SAndroid Build Coastguard Worker all_headers.setdefault(f, []) 75*635a8641SAndroid Build Coastguard Worker if not skip_obj: 76*635a8641SAndroid Build Coastguard Worker all_headers[f].append(obj_file) 77*635a8641SAndroid Build Coastguard Worker else: 78*635a8641SAndroid Build Coastguard Worker is_valid = line.endswith('(VALID)') 79*635a8641SAndroid Build Coastguard Worker obj_file = line.split(':')[0] 80*635a8641SAndroid Build Coastguard Worker 81*635a8641SAndroid Build Coastguard Worker return all_headers 82*635a8641SAndroid Build Coastguard Worker 83*635a8641SAndroid Build Coastguard Worker 84*635a8641SAndroid Build Coastguard Workerdef GetHeadersFromGN(out_dir, q): 85*635a8641SAndroid Build Coastguard Worker """Return all the header files from GN""" 86*635a8641SAndroid Build Coastguard Worker 87*635a8641SAndroid Build Coastguard Worker tmp = None 88*635a8641SAndroid Build Coastguard Worker ans, err = set(), None 89*635a8641SAndroid Build Coastguard Worker try: 90*635a8641SAndroid Build Coastguard Worker # Argument |dir| is needed to make sure it's on the same drive on Windows. 91*635a8641SAndroid Build Coastguard Worker # dir='' means dir='.', but doesn't introduce an unneeded prefix. 92*635a8641SAndroid Build Coastguard Worker tmp = tempfile.mkdtemp(dir='') 93*635a8641SAndroid Build Coastguard Worker shutil.copy2(os.path.join(out_dir, 'args.gn'), 94*635a8641SAndroid Build Coastguard Worker os.path.join(tmp, 'args.gn')) 95*635a8641SAndroid Build Coastguard Worker # Do "gn gen" in a temp dir to prevent dirtying |out_dir|. 96*635a8641SAndroid Build Coastguard Worker gn_exe = 'gn.bat' if sys.platform == 'win32' else 'gn' 97*635a8641SAndroid Build Coastguard Worker subprocess.check_call([ 98*635a8641SAndroid Build Coastguard Worker os.path.join(DEPOT_TOOLS_DIR, gn_exe), 'gen', tmp, '--ide=json', '-q']) 99*635a8641SAndroid Build Coastguard Worker gn_json = json.load(open(os.path.join(tmp, 'project.json'))) 100*635a8641SAndroid Build Coastguard Worker ans = ParseGNProjectJSON(gn_json, out_dir, tmp) 101*635a8641SAndroid Build Coastguard Worker except Exception as e: 102*635a8641SAndroid Build Coastguard Worker err = str(e) 103*635a8641SAndroid Build Coastguard Worker finally: 104*635a8641SAndroid Build Coastguard Worker if tmp: 105*635a8641SAndroid Build Coastguard Worker shutil.rmtree(tmp) 106*635a8641SAndroid Build Coastguard Worker q.put((ans, err)) 107*635a8641SAndroid Build Coastguard Worker 108*635a8641SAndroid Build Coastguard Worker 109*635a8641SAndroid Build Coastguard Workerdef ParseGNProjectJSON(gn, out_dir, tmp_out): 110*635a8641SAndroid Build Coastguard Worker """Parse GN output and get the header files""" 111*635a8641SAndroid Build Coastguard Worker all_headers = set() 112*635a8641SAndroid Build Coastguard Worker 113*635a8641SAndroid Build Coastguard Worker for _target, properties in gn['targets'].items(): 114*635a8641SAndroid Build Coastguard Worker sources = properties.get('sources', []) 115*635a8641SAndroid Build Coastguard Worker public = properties.get('public', []) 116*635a8641SAndroid Build Coastguard Worker # Exclude '"public": "*"'. 117*635a8641SAndroid Build Coastguard Worker if type(public) is list: 118*635a8641SAndroid Build Coastguard Worker sources += public 119*635a8641SAndroid Build Coastguard Worker for f in sources: 120*635a8641SAndroid Build Coastguard Worker if f.endswith('.h') or f.endswith('.hh'): 121*635a8641SAndroid Build Coastguard Worker if f.startswith('//'): 122*635a8641SAndroid Build Coastguard Worker f = f[2:] # Strip the '//' prefix. 123*635a8641SAndroid Build Coastguard Worker if f.startswith(tmp_out): 124*635a8641SAndroid Build Coastguard Worker f = out_dir + f[len(tmp_out):] 125*635a8641SAndroid Build Coastguard Worker all_headers.add(f) 126*635a8641SAndroid Build Coastguard Worker 127*635a8641SAndroid Build Coastguard Worker return all_headers 128*635a8641SAndroid Build Coastguard Worker 129*635a8641SAndroid Build Coastguard Worker 130*635a8641SAndroid Build Coastguard Workerdef GetDepsPrefixes(q): 131*635a8641SAndroid Build Coastguard Worker """Return all the folders controlled by DEPS file""" 132*635a8641SAndroid Build Coastguard Worker prefixes, err = set(), None 133*635a8641SAndroid Build Coastguard Worker try: 134*635a8641SAndroid Build Coastguard Worker gclient_exe = 'gclient.bat' if sys.platform == 'win32' else 'gclient' 135*635a8641SAndroid Build Coastguard Worker gclient_out = subprocess.check_output([ 136*635a8641SAndroid Build Coastguard Worker os.path.join(DEPOT_TOOLS_DIR, gclient_exe), 137*635a8641SAndroid Build Coastguard Worker 'recurse', '--no-progress', '-j1', 138*635a8641SAndroid Build Coastguard Worker 'python', '-c', 'import os;print(os.environ["GCLIENT_DEP_PATH"])'], 139*635a8641SAndroid Build Coastguard Worker universal_newlines=True) 140*635a8641SAndroid Build Coastguard Worker for i in gclient_out.split('\n'): 141*635a8641SAndroid Build Coastguard Worker if i.startswith('src/'): 142*635a8641SAndroid Build Coastguard Worker i = i[4:] 143*635a8641SAndroid Build Coastguard Worker prefixes.add(i) 144*635a8641SAndroid Build Coastguard Worker except Exception as e: 145*635a8641SAndroid Build Coastguard Worker err = str(e) 146*635a8641SAndroid Build Coastguard Worker q.put((prefixes, err)) 147*635a8641SAndroid Build Coastguard Worker 148*635a8641SAndroid Build Coastguard Worker 149*635a8641SAndroid Build Coastguard Workerdef IsBuildClean(out_dir): 150*635a8641SAndroid Build Coastguard Worker cmd = [os.path.join(DEPOT_TOOLS_DIR, 'ninja'), '-C', out_dir, '-n'] 151*635a8641SAndroid Build Coastguard Worker try: 152*635a8641SAndroid Build Coastguard Worker out = subprocess.check_output(cmd) 153*635a8641SAndroid Build Coastguard Worker return 'no work to do.' in out 154*635a8641SAndroid Build Coastguard Worker except Exception as e: 155*635a8641SAndroid Build Coastguard Worker print(e) 156*635a8641SAndroid Build Coastguard Worker return False 157*635a8641SAndroid Build Coastguard Worker 158*635a8641SAndroid Build Coastguard Workerdef ParseWhiteList(whitelist): 159*635a8641SAndroid Build Coastguard Worker out = set() 160*635a8641SAndroid Build Coastguard Worker for line in whitelist.split('\n'): 161*635a8641SAndroid Build Coastguard Worker line = re.sub(r'#.*', '', line).strip() 162*635a8641SAndroid Build Coastguard Worker if line: 163*635a8641SAndroid Build Coastguard Worker out.add(line) 164*635a8641SAndroid Build Coastguard Worker return out 165*635a8641SAndroid Build Coastguard Worker 166*635a8641SAndroid Build Coastguard Worker 167*635a8641SAndroid Build Coastguard Workerdef FilterOutDepsedRepo(files, deps): 168*635a8641SAndroid Build Coastguard Worker return {f for f in files if not any(f.startswith(d) for d in deps)} 169*635a8641SAndroid Build Coastguard Worker 170*635a8641SAndroid Build Coastguard Worker 171*635a8641SAndroid Build Coastguard Workerdef GetNonExistingFiles(lst): 172*635a8641SAndroid Build Coastguard Worker out = set() 173*635a8641SAndroid Build Coastguard Worker for f in lst: 174*635a8641SAndroid Build Coastguard Worker if not os.path.isfile(f): 175*635a8641SAndroid Build Coastguard Worker out.add(f) 176*635a8641SAndroid Build Coastguard Worker return out 177*635a8641SAndroid Build Coastguard Worker 178*635a8641SAndroid Build Coastguard Worker 179*635a8641SAndroid Build Coastguard Workerdef main(): 180*635a8641SAndroid Build Coastguard Worker 181*635a8641SAndroid Build Coastguard Worker def DumpJson(data): 182*635a8641SAndroid Build Coastguard Worker if args.json: 183*635a8641SAndroid Build Coastguard Worker with open(args.json, 'w') as f: 184*635a8641SAndroid Build Coastguard Worker json.dump(data, f) 185*635a8641SAndroid Build Coastguard Worker 186*635a8641SAndroid Build Coastguard Worker def PrintError(msg): 187*635a8641SAndroid Build Coastguard Worker DumpJson([]) 188*635a8641SAndroid Build Coastguard Worker parser.error(msg) 189*635a8641SAndroid Build Coastguard Worker 190*635a8641SAndroid Build Coastguard Worker parser = argparse.ArgumentParser(description=''' 191*635a8641SAndroid Build Coastguard Worker NOTE: Use ninja to build all targets in OUT_DIR before running 192*635a8641SAndroid Build Coastguard Worker this script.''') 193*635a8641SAndroid Build Coastguard Worker parser.add_argument('--out-dir', metavar='OUT_DIR', default='out/Release', 194*635a8641SAndroid Build Coastguard Worker help='output directory of the build') 195*635a8641SAndroid Build Coastguard Worker parser.add_argument('--json', 196*635a8641SAndroid Build Coastguard Worker help='JSON output filename for missing headers') 197*635a8641SAndroid Build Coastguard Worker parser.add_argument('--whitelist', help='file containing whitelist') 198*635a8641SAndroid Build Coastguard Worker parser.add_argument('--skip-dirty-check', action='store_true', 199*635a8641SAndroid Build Coastguard Worker help='skip checking whether the build is dirty') 200*635a8641SAndroid Build Coastguard Worker parser.add_argument('--verbose', action='store_true', 201*635a8641SAndroid Build Coastguard Worker help='print more diagnostic info') 202*635a8641SAndroid Build Coastguard Worker 203*635a8641SAndroid Build Coastguard Worker args, _extras = parser.parse_known_args() 204*635a8641SAndroid Build Coastguard Worker 205*635a8641SAndroid Build Coastguard Worker if not os.path.isdir(args.out_dir): 206*635a8641SAndroid Build Coastguard Worker parser.error('OUT_DIR "%s" does not exist.' % args.out_dir) 207*635a8641SAndroid Build Coastguard Worker 208*635a8641SAndroid Build Coastguard Worker if not args.skip_dirty_check and not IsBuildClean(args.out_dir): 209*635a8641SAndroid Build Coastguard Worker dirty_msg = 'OUT_DIR looks dirty. You need to build all there.' 210*635a8641SAndroid Build Coastguard Worker if args.json: 211*635a8641SAndroid Build Coastguard Worker # Assume running on the bots. Silently skip this step. 212*635a8641SAndroid Build Coastguard Worker # This is possible because "analyze" step can be wrong due to 213*635a8641SAndroid Build Coastguard Worker # underspecified header files. See crbug.com/725877 214*635a8641SAndroid Build Coastguard Worker print(dirty_msg) 215*635a8641SAndroid Build Coastguard Worker DumpJson([]) 216*635a8641SAndroid Build Coastguard Worker return 0 217*635a8641SAndroid Build Coastguard Worker else: 218*635a8641SAndroid Build Coastguard Worker # Assume running interactively. 219*635a8641SAndroid Build Coastguard Worker parser.error(dirty_msg) 220*635a8641SAndroid Build Coastguard Worker 221*635a8641SAndroid Build Coastguard Worker d_q = Queue() 222*635a8641SAndroid Build Coastguard Worker d_p = Process(target=GetHeadersFromNinja, args=(args.out_dir, True, d_q,)) 223*635a8641SAndroid Build Coastguard Worker d_p.start() 224*635a8641SAndroid Build Coastguard Worker 225*635a8641SAndroid Build Coastguard Worker gn_q = Queue() 226*635a8641SAndroid Build Coastguard Worker gn_p = Process(target=GetHeadersFromGN, args=(args.out_dir, gn_q,)) 227*635a8641SAndroid Build Coastguard Worker gn_p.start() 228*635a8641SAndroid Build Coastguard Worker 229*635a8641SAndroid Build Coastguard Worker deps_q = Queue() 230*635a8641SAndroid Build Coastguard Worker deps_p = Process(target=GetDepsPrefixes, args=(deps_q,)) 231*635a8641SAndroid Build Coastguard Worker deps_p.start() 232*635a8641SAndroid Build Coastguard Worker 233*635a8641SAndroid Build Coastguard Worker d, d_err = d_q.get() 234*635a8641SAndroid Build Coastguard Worker gn, gn_err = gn_q.get() 235*635a8641SAndroid Build Coastguard Worker missing = set(d.keys()) - gn 236*635a8641SAndroid Build Coastguard Worker nonexisting = GetNonExistingFiles(gn) 237*635a8641SAndroid Build Coastguard Worker 238*635a8641SAndroid Build Coastguard Worker deps, deps_err = deps_q.get() 239*635a8641SAndroid Build Coastguard Worker missing = FilterOutDepsedRepo(missing, deps) 240*635a8641SAndroid Build Coastguard Worker nonexisting = FilterOutDepsedRepo(nonexisting, deps) 241*635a8641SAndroid Build Coastguard Worker 242*635a8641SAndroid Build Coastguard Worker d_p.join() 243*635a8641SAndroid Build Coastguard Worker gn_p.join() 244*635a8641SAndroid Build Coastguard Worker deps_p.join() 245*635a8641SAndroid Build Coastguard Worker 246*635a8641SAndroid Build Coastguard Worker if d_err: 247*635a8641SAndroid Build Coastguard Worker PrintError(d_err) 248*635a8641SAndroid Build Coastguard Worker if gn_err: 249*635a8641SAndroid Build Coastguard Worker PrintError(gn_err) 250*635a8641SAndroid Build Coastguard Worker if deps_err: 251*635a8641SAndroid Build Coastguard Worker PrintError(deps_err) 252*635a8641SAndroid Build Coastguard Worker if len(GetNonExistingFiles(d)) > 0: 253*635a8641SAndroid Build Coastguard Worker print('Non-existing files in ninja deps:', GetNonExistingFiles(d)) 254*635a8641SAndroid Build Coastguard Worker PrintError('Found non-existing files in ninja deps. You should ' + 255*635a8641SAndroid Build Coastguard Worker 'build all in OUT_DIR.') 256*635a8641SAndroid Build Coastguard Worker if len(d) == 0: 257*635a8641SAndroid Build Coastguard Worker PrintError('OUT_DIR looks empty. You should build all there.') 258*635a8641SAndroid Build Coastguard Worker if any((('/gen/' in i) for i in nonexisting)): 259*635a8641SAndroid Build Coastguard Worker PrintError('OUT_DIR looks wrong. You should build all there.') 260*635a8641SAndroid Build Coastguard Worker 261*635a8641SAndroid Build Coastguard Worker if args.whitelist: 262*635a8641SAndroid Build Coastguard Worker whitelist = ParseWhiteList(open(args.whitelist).read()) 263*635a8641SAndroid Build Coastguard Worker missing -= whitelist 264*635a8641SAndroid Build Coastguard Worker nonexisting -= whitelist 265*635a8641SAndroid Build Coastguard Worker 266*635a8641SAndroid Build Coastguard Worker missing = sorted(missing) 267*635a8641SAndroid Build Coastguard Worker nonexisting = sorted(nonexisting) 268*635a8641SAndroid Build Coastguard Worker 269*635a8641SAndroid Build Coastguard Worker DumpJson(sorted(missing + nonexisting)) 270*635a8641SAndroid Build Coastguard Worker 271*635a8641SAndroid Build Coastguard Worker if len(missing) == 0 and len(nonexisting) == 0: 272*635a8641SAndroid Build Coastguard Worker return 0 273*635a8641SAndroid Build Coastguard Worker 274*635a8641SAndroid Build Coastguard Worker if len(missing) > 0: 275*635a8641SAndroid Build Coastguard Worker print('\nThe following files should be included in gn files:') 276*635a8641SAndroid Build Coastguard Worker for i in missing: 277*635a8641SAndroid Build Coastguard Worker print(i) 278*635a8641SAndroid Build Coastguard Worker 279*635a8641SAndroid Build Coastguard Worker if len(nonexisting) > 0: 280*635a8641SAndroid Build Coastguard Worker print('\nThe following non-existing files should be removed from gn files:') 281*635a8641SAndroid Build Coastguard Worker for i in nonexisting: 282*635a8641SAndroid Build Coastguard Worker print(i) 283*635a8641SAndroid Build Coastguard Worker 284*635a8641SAndroid Build Coastguard Worker if args.verbose: 285*635a8641SAndroid Build Coastguard Worker # Only get detailed obj dependency here since it is slower. 286*635a8641SAndroid Build Coastguard Worker GetHeadersFromNinja(args.out_dir, False, d_q) 287*635a8641SAndroid Build Coastguard Worker d, d_err = d_q.get() 288*635a8641SAndroid Build Coastguard Worker print('\nDetailed dependency info:') 289*635a8641SAndroid Build Coastguard Worker for f in missing: 290*635a8641SAndroid Build Coastguard Worker print(f) 291*635a8641SAndroid Build Coastguard Worker for cc in d[f]: 292*635a8641SAndroid Build Coastguard Worker print(' ', cc) 293*635a8641SAndroid Build Coastguard Worker 294*635a8641SAndroid Build Coastguard Worker print('\nMissing headers sorted by number of affected object files:') 295*635a8641SAndroid Build Coastguard Worker count = {k: len(v) for (k, v) in d.items()} 296*635a8641SAndroid Build Coastguard Worker for f in sorted(count, key=count.get, reverse=True): 297*635a8641SAndroid Build Coastguard Worker if f in missing: 298*635a8641SAndroid Build Coastguard Worker print(count[f], f) 299*635a8641SAndroid Build Coastguard Worker 300*635a8641SAndroid Build Coastguard Worker return 1 301*635a8641SAndroid Build Coastguard Worker 302*635a8641SAndroid Build Coastguard Worker 303*635a8641SAndroid Build Coastguard Workerif __name__ == '__main__': 304*635a8641SAndroid Build Coastguard Worker sys.exit(main()) 305