xref: /aosp_15_r20/external/cronet/build/util/ide_query (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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