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