summaryrefslogtreecommitdiffstats
path: root/third_party/waf/waflib/extras/clang_compilation_database.py
blob: bd29db93fd502e31b805ea635c461711219f403d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#!/usr/bin/env python
# encoding: utf-8
# Christoph Koke, 2013
# Alibek Omarov, 2019

"""
Writes the c and cpp compile commands into build/compile_commands.json
see http://clang.llvm.org/docs/JSONCompilationDatabase.html

Usage:

	Load this tool in `options` to be able to generate database
	by request in command-line and before build:

	$ waf clangdb

	def options(opt):
		opt.load('clang_compilation_database')

	Otherwise, load only in `configure` to generate it always before build.

	def configure(conf):
		conf.load('compiler_cxx')
		...
		conf.load('clang_compilation_database')
"""

from waflib import Logs, TaskGen, Task, Build, Scripting

Task.Task.keep_last_cmd = True

class ClangDbContext(Build.BuildContext):
	'''generates compile_commands.json by request'''
	cmd = 'clangdb'

	def write_compilation_database(self):
		"""
		Write the clang compilation database as JSON
		"""
		database_file = self.bldnode.make_node('compile_commands.json')
		Logs.info('Build commands will be stored in %s', database_file.path_from(self.path))
		try:
			root = database_file.read_json()
		except IOError:
			root = []
		clang_db = dict((x['file'], x) for x in root)
		for task in self.clang_compilation_database_tasks:
			try:
				cmd = task.last_cmd
			except AttributeError:
				continue
			f_node = task.inputs[0]
			filename = f_node.path_from(task.get_cwd())
			entry = {
				"directory": task.get_cwd().abspath(),
				"arguments": cmd,
				"file": filename,
			}
			clang_db[filename] = entry
		root = list(clang_db.values())
		database_file.write_json(root)

	def execute(self):
		"""
		Build dry run
		"""
		self.restore()
		self.cur_tasks = []
		self.clang_compilation_database_tasks = []

		if not self.all_envs:
			self.load_envs()

		self.recurse([self.run_dir])
		self.pre_build()

		# we need only to generate last_cmd, so override
		# exec_command temporarily
		def exec_command(self, *k, **kw):
			return 0

		for g in self.groups:
			for tg in g:
				try:
					f = tg.post
				except AttributeError:
					pass
				else:
					f()

				if isinstance(tg, Task.Task):
					lst = [tg]
				else: lst = tg.tasks
				for tsk in lst:
					if tsk.__class__.__name__ == "swig":
						tsk.runnable_status()
						if hasattr(tsk, 'more_tasks'):
							lst.extend(tsk.more_tasks)
					# Not all dynamic tasks can be processed, in some cases
					# one may have to call the method "run()" like this:
					#elif tsk.__class__.__name__ == 'src2c':
					#	tsk.run()
					#	if hasattr(tsk, 'more_tasks'):
					#		lst.extend(tsk.more_tasks)

					tup = tuple(y for y in [Task.classes.get(x) for x in ('c', 'cxx')] if y)
					if isinstance(tsk, tup):
						self.clang_compilation_database_tasks.append(tsk)
						tsk.nocache = True
						old_exec = tsk.exec_command
						tsk.exec_command = exec_command
						tsk.run()
						tsk.exec_command = old_exec

		self.write_compilation_database()

EXECUTE_PATCHED = False
def patch_execute():
	global EXECUTE_PATCHED

	if EXECUTE_PATCHED:
		return

	def new_execute_build(self):
		"""
		Invoke clangdb command before build
		"""
		if self.cmd.startswith('build'):
			Scripting.run_command(self.cmd.replace('build','clangdb'))

		old_execute_build(self)

	old_execute_build = getattr(Build.BuildContext, 'execute_build', None)
	setattr(Build.BuildContext, 'execute_build', new_execute_build)
	EXECUTE_PATCHED = True

patch_execute()