1#!/usr/bin/env python3 2# Copyright 2024 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"""A script gets the information needed by lDE language services. 6 7Expected to run it at repository root, where top DEP, .gn etc exists. 8Not intended to run by user. 9See go/reqs-for-peep 10""" 11 12import argparse 13import os 14import re 15import subprocess 16import sys 17 18def _gn_lines(output_dir, path): 19 """ 20 Generator function that returns args.gn lines one at a time, following 21 import directives as needed. 22 """ 23 import_re = re.compile(r'\s*import\("(.*)"\)') 24 with open(path, encoding="utf-8") as f: 25 for line in f: 26 match = import_re.match(line) 27 if match: 28 raw_import_path = match.groups()[0] 29 if raw_import_path[:2] == "//": 30 import_path = os.path.normpath( 31 os.path.join(output_dir, "..", "..", 32 raw_import_path[2:])) 33 else: 34 import_path = os.path.normpath( 35 os.path.join(os.path.dirname(path), raw_import_path)) 36 for import_line in _gn_lines(output_dir, import_path): 37 yield import_line 38 else: 39 yield line 40 41 42def _use_reclient(outdir): 43 use_remoteexec = False 44 use_reclient = None 45 args_gn = os.path.join(outdir, 'args.gn') 46 if not os.path.exists(args_gn): 47 return False 48 for line in _gn_lines(outdir, args_gn): 49 line_without_comment = line.split('#')[0] 50 m = re.match(r"(^|\s*)use_remoteexec\s*=\s*(true|false)\s*$", 51 line_without_comment) 52 if m: 53 use_remoteexec = m.group(2) == 'true' 54 continue 55 m = re.match(r"(^|\s*)use_reclient\s*=\s*(true|false)\s*$", 56 line_without_comment) 57 if m: 58 use_reclient = m.group(2) == 'true' 59 if use_reclient == None: 60 use_reclient = use_remoteexec 61 return use_reclient 62 63 64def main(): 65 parser = argparse.ArgumentParser() 66 parser.add_argument('source', nargs='+', 67 help=('The source file being analyzed.' 68 'Multiple source arguments can be passed in order to batch ' 69 'process if desired.')) 70 parser.add_argument('--perform-build', action='store_true', 71 help=('If specified, actually build the target, including any generated ' 72 'prerequisite files. ' 73 'If --perform-build is not passed, the contents of ' 74 'the GeneratedFile results will only be returned if a build has ' 75 'been previously completed, and may be stale.')) 76 parser.add_argument('--out-dir', 77 help=('Output directory, containing args.gn, which specifies the build ' 78 'configuration.')) 79 options = parser.parse_args() 80 81 this_dir = os.path.dirname(__file__) 82 repo_root = os.path.join(this_dir, '..', '..') 83 84 targets = [] 85 for source in options.source: 86 # source is repo root (cwd) relative, 87 # but siso uses out dir relative target. 88 target = os.path.relpath(source, start=options.out_dir) + "^" 89 targets.append(target) 90 91 if _use_reclient(options.out_dir): 92 # b/335795623 ide_query compiler_arguments contain non-compiler arguments 93 sys.stderr.write( 94 'ide_query won\'t work well with "use_reclient=true"\n' 95 'Set "use_reclient=false" in args.gn.\n') 96 sys.exit(1) 97 if options.perform_build: 98 args = ['siso', 'ninja'] 99 # use `-k=0` to build generated files as much as possible. 100 args.extend([ 101 '-k=0', 102 '--prepare', 103 '-C', 104 options.out_dir, 105 ]) 106 args.extend(targets) 107 env = os.environ.copy() 108 env['SISO_EXPERIMENTS'] = 'no-fast-deps,prepare-header-only' 109 with subprocess.Popen( 110 args, 111 cwd=repo_root, 112 env=env, 113 stderr=subprocess.STDOUT, 114 stdout=subprocess.PIPE, 115 universal_newlines=True 116 ) as p: 117 for line in p.stdout: 118 print(line, end='', file=sys.stderr) 119 # loop ends when program finishes, but must wait else returncode is None. 120 p.wait() 121 if p.returncode != 0: 122 # TODO: report error in IdeAnalysis.Status? 123 sys.stderr.write('build failed with %d\n' % p.returncode) 124 # even if build fails, it should report ideanalysis back. 125 126 args = ['siso', 'query', 'ideanalysis', '-C', options.out_dir] 127 args.extend(targets) 128 subprocess.run(args, cwd=repo_root, check=True) 129 130if __name__ == '__main__': 131 sys.exit(main()) 132