1#!/usr/bin/env python
2# Copyright 2016 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
6from __future__ import print_function
7
8import argparse
9import json
10import os
11import re
12import shlex
13import sys
14
15script_dir = os.path.dirname(os.path.realpath(__file__))
16tool_dir = os.path.abspath(os.path.join(script_dir, '../pylib'))
17sys.path.insert(0, tool_dir)
18
19from clang import compile_db
20
21_PROBABLY_CLANG_RE = re.compile(r'clang(?:\+\+)?$')
22
23
24def ParseArgs():
25  parser = argparse.ArgumentParser(
26      description='Utility to build one Chromium file for debugging clang')
27  parser.add_argument('-p', required=True, help='path to the compile database')
28  parser.add_argument('--generate-compdb',
29                      action='store_true',
30                      help='regenerate the compile database')
31  parser.add_argument('--prefix',
32                      help='optional prefix to prepend, e.g. --prefix=lldb')
33  parser.add_argument(
34      '--compiler',
35      help='compiler to override the compiler specied in the compile db')
36  parser.add_argument('--suffix',
37                      help='optional suffix to append, e.g.' +
38                      ' --suffix="-Xclang -ast-dump -fsyntax-only"')
39  parser.add_argument('target_file', help='file to build')
40  return parser.parse_args()
41
42
43def BuildIt(record, prefix, compiler, suffix):
44  """Builds the file in the provided compile DB record.
45
46  Args:
47    prefix: Optional prefix to prepend to the build command.
48    compiler: Optional compiler to override the compiler specified the record.
49    suffix: Optional suffix to append to the build command.
50  """
51  raw_args = shlex.split(record['command'])
52  # The compile command might have some goop in front of it, e.g. if the build
53  # is using goma, so shift arguments off the front until raw_args[0] looks like
54  # a clang invocation.
55  while raw_args:
56    if _PROBABLY_CLANG_RE.search(raw_args[0]):
57      break
58    raw_args = raw_args[1:]
59  if not raw_args:
60    print('error: command %s does not appear to invoke clang!' %
61          record['command'])
62    return 2
63  args = []
64  if prefix:
65    args.extend(shlex.split(prefix))
66  if compiler:
67    raw_args[0] = compiler
68  args.extend(raw_args)
69  if suffix:
70    args.extend(shlex.split(suffix))
71  print('Running %s' % ' '.join(args))
72  os.execv(args[0], args)
73
74
75def main():
76  args = ParseArgs()
77  os.chdir(args.p)
78  if args.generate_compdb:
79    with open('compile_commands.json', 'w') as f:
80      f.write(compile_db.GenerateWithNinja('.'))
81  db = compile_db.Read('.')
82  for record in db:
83    if os.path.normpath(os.path.join(args.p, record[
84        'file'])) == args.target_file:
85      return BuildIt(record, args.prefix, args.compiler, args.suffix)
86  print('error: could not find %s in compile DB!' % args.target_file)
87  return 1
88
89
90if __name__ == '__main__':
91  sys.exit(main())
92