1#!/usr/bin/env python
2#
3#=- run-find-all-symbols.py - Parallel find-all-symbols runner -*- python  -*-=#
4#
5# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
6# See https://llvm.org/LICENSE.txt for license information.
7# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
8#
9#===------------------------------------------------------------------------===#
10
11"""
12Parallel find-all-symbols runner
13================================
14
15Runs find-all-symbols over all files in a compilation database.
16
17Example invocations.
18- Run find-all-symbols on all files in the current working directory.
19    run-find-all-symbols.py <source-file>
20
21Compilation database setup:
22http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html
23"""
24
25import argparse
26import json
27import multiprocessing
28import os
29import Queue
30import shutil
31import subprocess
32import sys
33import tempfile
34import threading
35
36
37def find_compilation_database(path):
38  """Adjusts the directory until a compilation database is found."""
39  result = './'
40  while not os.path.isfile(os.path.join(result, path)):
41    if os.path.realpath(result) == '/':
42      print 'Error: could not find compilation database.'
43      sys.exit(1)
44    result += '../'
45  return os.path.realpath(result)
46
47
48def MergeSymbols(directory, args):
49  """Merge all symbol files (yaml) in a given directory into a single file."""
50  invocation = [args.binary, '-merge-dir='+directory, args.saving_path]
51  subprocess.call(invocation)
52  print 'Merge is finished. Saving results in ' + args.saving_path
53
54
55def run_find_all_symbols(args, tmpdir, build_path, queue):
56  """Takes filenames out of queue and runs find-all-symbols on them."""
57  while True:
58    name = queue.get()
59    invocation = [args.binary, name, '-output-dir='+tmpdir, '-p='+build_path]
60    sys.stdout.write(' '.join(invocation) + '\n')
61    subprocess.call(invocation)
62    queue.task_done()
63
64
65def main():
66  parser = argparse.ArgumentParser(description='Runs find-all-symbols over all'
67                                   'files in a compilation database.')
68  parser.add_argument('-binary', metavar='PATH',
69                      default='./bin/find-all-symbols',
70                      help='path to find-all-symbols binary')
71  parser.add_argument('-j', type=int, default=0,
72                      help='number of instances to be run in parallel.')
73  parser.add_argument('-p', dest='build_path',
74                      help='path used to read a compilation database.')
75  parser.add_argument('-saving-path', default='./find_all_symbols_db.yaml',
76                      help='result saving path')
77  args = parser.parse_args()
78
79  db_path = 'compile_commands.json'
80
81  if args.build_path is not None:
82    build_path = args.build_path
83  else:
84    build_path = find_compilation_database(db_path)
85
86  tmpdir = tempfile.mkdtemp()
87
88  # Load the database and extract all files.
89  database = json.load(open(os.path.join(build_path, db_path)))
90  files = [entry['file'] for entry in database]
91
92  # Filter out .rc files on Windows. CMake includes them for some reason.
93  files = [f for f in files if not f.endswith('.rc')]
94
95  max_task = args.j
96  if max_task == 0:
97    max_task = multiprocessing.cpu_count()
98
99  try:
100    # Spin up a bunch of tidy-launching threads.
101    queue = Queue.Queue(max_task)
102    for _ in range(max_task):
103      t = threading.Thread(target=run_find_all_symbols,
104                           args=(args, tmpdir, build_path, queue))
105      t.daemon = True
106      t.start()
107
108    # Fill the queue with files.
109    for name in files:
110      queue.put(name)
111
112    # Wait for all threads to be done.
113    queue.join()
114
115    MergeSymbols(tmpdir, args)
116
117
118  except KeyboardInterrupt:
119    # This is a sad hack. Unfortunately subprocess goes
120    # bonkers with ctrl-c and we start forking merrily.
121    print '\nCtrl-C detected, goodbye.'
122    os.kill(0, 9)
123
124
125if __name__ == '__main__':
126  main()
127