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 41def _use_reclient(outdir): 42 args_gn = os.path.join(outdir, 'args.gn') 43 if not os.path.exists(args_gn): 44 return False 45 for line in _gn_lines(outdir, args_gn): 46 line_without_comment = line.split('#')[0] 47 if re.search(r"(^|\s)(use_remoteexec)\s*=\s*true($|\s)", 48 line_without_comment): 49 return True 50 return False 51 52def main(): 53 parser = argparse.ArgumentParser() 54 parser.add_argument('--source', action='append', 55 help=('The source file being analyzed.' 56 'Multiple --source arguments can be passed in order to batch ' 57 'process is desired. ')) 58 parser.add_argument('--perform-build', action='store_true', 59 help=('If specified, actually build the target, including any generated ' 60 'prerequisite files. ' 61 'If --perform-build is not passed, the contents of ' 62 'the GeneratedFile results will only be returned if a build has ' 63 'been previously completed, and may be stale.')) 64 parser.add_argument('--out-dir', 65 help=('Output directory, containing args.gn, which specifies the build ' 66 'configuration.')) 67 options = parser.parse_args() 68 69 this_dir = os.path.dirname(__file__) 70 repo_root = os.path.join(this_dir, '..', '..') 71 72 targets = [] 73 for source in options.source: 74 # source is repo root (cwd) relative, 75 # but siso uses out dir relative target. 76 target = os.path.relpath(source, start=options.out_dir) + "^" 77 targets.append(target) 78 79 if options.perform_build: 80 if _use_reclient(options.out_dir): 81 args = ['autoninja', '-C', options.out_dir] 82 else: 83 args = ['siso', 'ninja', '-C', options.out_dir] 84 args.extend(targets) 85 p = subprocess.run(args, cwd=repo_root, capture_output=True) 86 if p.returncode != 0: 87 # TODO: report error in IdeAnalysis.Status? 88 sys.stderr.write('build failed with %d\n%s\n%s' % ( 89 p.returncode, p.stdout, p.stderr)) 90 return 1 91 92 args = ['siso', 'query', 'ideanalysis', '-C', options.out_dir] 93 args.extend(targets) 94 subprocess.run(args, cwd=repo_root, check=True) 95 96if __name__ == '__main__': 97 sys.exit(main()) 98