clang_compilation_database.py (3412B)
1 #!/usr/bin/env python 2 # encoding: utf-8 3 # Christoph Koke, 2013 4 # Alibek Omarov, 2019 5 6 """ 7 Writes the c and cpp compile commands into build/compile_commands.json 8 see http://clang.llvm.org/docs/JSONCompilationDatabase.html 9 10 Usage: 11 12 Load this tool in `options` to be able to generate database 13 by request in command-line and before build: 14 15 $ waf clangdb 16 17 def options(opt): 18 opt.load('clang_compilation_database') 19 20 Otherwise, load only in `configure` to generate it always before build. 21 22 def configure(conf): 23 conf.load('compiler_cxx') 24 ... 25 conf.load('clang_compilation_database') 26 """ 27 28 from waflib import Logs, TaskGen, Task, Build, Scripting 29 30 Task.Task.keep_last_cmd = True 31 32 class ClangDbContext(Build.BuildContext): 33 '''generates compile_commands.json by request''' 34 cmd = 'clangdb' 35 36 def write_compilation_database(self): 37 """ 38 Write the clang compilation database as JSON 39 """ 40 database_file = self.bldnode.make_node('compile_commands.json') 41 Logs.info('Build commands will be stored in %s', database_file.path_from(self.path)) 42 try: 43 root = database_file.read_json() 44 except IOError: 45 root = [] 46 clang_db = dict((x['file'], x) for x in root) 47 for task in self.clang_compilation_database_tasks: 48 try: 49 cmd = task.last_cmd 50 except AttributeError: 51 continue 52 f_node = task.inputs[0] 53 filename = f_node.path_from(task.get_cwd()) 54 entry = { 55 "directory": task.get_cwd().abspath(), 56 "arguments": cmd, 57 "file": filename, 58 } 59 clang_db[filename] = entry 60 root = list(clang_db.values()) 61 database_file.write_json(root) 62 63 def execute(self): 64 """ 65 Build dry run 66 """ 67 self.restore() 68 self.cur_tasks = [] 69 self.clang_compilation_database_tasks = [] 70 71 if not self.all_envs: 72 self.load_envs() 73 74 self.recurse([self.run_dir]) 75 self.pre_build() 76 77 # we need only to generate last_cmd, so override 78 # exec_command temporarily 79 def exec_command(self, *k, **kw): 80 return 0 81 82 for g in self.groups: 83 for tg in g: 84 try: 85 f = tg.post 86 except AttributeError: 87 pass 88 else: 89 f() 90 91 if isinstance(tg, Task.Task): 92 lst = [tg] 93 else: lst = tg.tasks 94 for tsk in lst: 95 if tsk.__class__.__name__ == "swig": 96 tsk.runnable_status() 97 if hasattr(tsk, 'more_tasks'): 98 lst.extend(tsk.more_tasks) 99 # Not all dynamic tasks can be processed, in some cases 100 # one may have to call the method "run()" like this: 101 #elif tsk.__class__.__name__ == 'src2c': 102 # tsk.run() 103 # if hasattr(tsk, 'more_tasks'): 104 # lst.extend(tsk.more_tasks) 105 106 tup = tuple(y for y in [Task.classes.get(x) for x in ('c', 'cxx')] if y) 107 if isinstance(tsk, tup): 108 self.clang_compilation_database_tasks.append(tsk) 109 tsk.nocache = True 110 old_exec = tsk.exec_command 111 tsk.exec_command = exec_command 112 tsk.run() 113 tsk.exec_command = old_exec 114 115 self.write_compilation_database() 116 117 EXECUTE_PATCHED = False 118 def patch_execute(): 119 global EXECUTE_PATCHED 120 121 if EXECUTE_PATCHED: 122 return 123 124 def new_execute_build(self): 125 """ 126 Invoke clangdb command before build 127 """ 128 if self.cmd.startswith('build'): 129 ClangDbContext.variant = self.variant 130 Scripting.run_command(self.cmd.replace('build','clangdb')) 131 132 old_execute_build(self) 133 134 old_execute_build = getattr(Build.BuildContext, 'execute_build', None) 135 setattr(Build.BuildContext, 'execute_build', new_execute_build) 136 EXECUTE_PATCHED = True 137 138 patch_execute()