aboutsummaryrefslogtreecommitdiff
path: root/extras/batched_cc.py
blob: aad2872298378fc70c1ecbf1f2fba33d855388a6 (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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
#!/usr/bin/env python
# encoding: utf-8
# Thomas Nagy, 2006-2015 (ita)

"""
Instead of compiling object files one by one, c/c++ compilers are often able to compile at once:
cc -c ../file1.c ../file2.c ../file3.c

Files are output on the directory where the compiler is called, and dependencies are more difficult
to track (do not run the command on all source files if only one file changes)
As such, we do as if the files were compiled one by one, but no command is actually run:
replace each cc/cpp Task by a TaskSlave. A new task called TaskMaster collects the
signatures from each slave and finds out the command-line to run.

Just import this module to start using it:
def build(bld):
	bld.load('batched_cc')

Note that this is provided as an example, unity builds are recommended
for best performance results (fewer tasks and fewer jobs to execute).
See waflib/extras/unity.py.
"""

from waflib import Task, Utils
from waflib.TaskGen import extension, feature, after_method
from waflib.Tools import c, cxx

MAX_BATCH = 50

c_str = '${CC} ${ARCH_ST:ARCH} ${CFLAGS} ${FRAMEWORKPATH_ST:FRAMEWORKPATH} ${tsk.batch_incpaths()} ${DEFINES_ST:DEFINES} -c ${SRCLST} ${CXX_TGT_F_BATCHED} ${CPPFLAGS}'
c_fun, _ = Task.compile_fun_noshell(c_str)

cxx_str = '${CXX} ${ARCH_ST:ARCH} ${CXXFLAGS} ${FRAMEWORKPATH_ST:FRAMEWORKPATH} ${tsk.batch_incpaths()} ${DEFINES_ST:DEFINES} -c ${SRCLST} ${CXX_TGT_F_BATCHED} ${CPPFLAGS}'
cxx_fun, _ = Task.compile_fun_noshell(cxx_str)

count = 70000
class batch(Task.Task):
	color = 'PINK'

	after = ['c', 'cxx']
	before = ['cprogram', 'cshlib', 'cstlib', 'cxxprogram', 'cxxshlib', 'cxxstlib']

	def uid(self):
		return Utils.h_list([Task.Task.uid(self), self.generator.idx, self.generator.path.abspath(), self.generator.target])

	def __str__(self):
		return 'Batch compilation for %d slaves' % len(self.slaves)

	def __init__(self, *k, **kw):
		Task.Task.__init__(self, *k, **kw)
		self.slaves = []
		self.inputs = []
		self.hasrun = 0

		global count
		count += 1
		self.idx = count

	def add_slave(self, slave):
		self.slaves.append(slave)
		self.set_run_after(slave)

	def runnable_status(self):
		for t in self.run_after:
			if not t.hasrun:
				return Task.ASK_LATER

		for t in self.slaves:
			#if t.executed:
			if t.hasrun != Task.SKIPPED:
				return Task.RUN_ME

		return Task.SKIP_ME

	def get_cwd(self):
		return self.slaves[0].outputs[0].parent

	def batch_incpaths(self):
		st = self.env.CPPPATH_ST
		return [st % node.abspath() for node in self.generator.includes_nodes]

	def run(self):
		self.outputs = []

		srclst = []
		slaves = []
		for t in self.slaves:
			if t.hasrun != Task.SKIPPED:
				slaves.append(t)
				srclst.append(t.inputs[0].abspath())

		self.env.SRCLST = srclst

		if self.slaves[0].__class__.__name__ == 'c':
			ret = c_fun(self)
		else:
			ret = cxx_fun(self)

		if ret:
			return ret

		for t in slaves:
			t.old_post_run()

def hook(cls_type):
	def n_hook(self, node):

		ext = '.obj' if self.env.CC_NAME == 'msvc' else '.o'
		name = node.name
		k = name.rfind('.')
		if k >= 0:
			basename = name[:k] + ext
		else:
			basename = name + ext

		outdir = node.parent.get_bld().make_node('%d' % self.idx)
		outdir.mkdir()
		out = outdir.find_or_declare(basename)

		task = self.create_task(cls_type, node, out)

		try:
			self.compiled_tasks.append(task)
		except AttributeError:
			self.compiled_tasks = [task]

		if not getattr(self, 'masters', None):
			self.masters = {}
			self.allmasters = []

		def fix_path(tsk):
			if self.env.CC_NAME == 'msvc':
				tsk.env.append_unique('CXX_TGT_F_BATCHED', '/Fo%s\\' % outdir.abspath())

		if not node.parent in self.masters:
			m = self.masters[node.parent] = self.master = self.create_task('batch')
			fix_path(m)
			self.allmasters.append(m)
		else:
			m = self.masters[node.parent]
			if len(m.slaves) > MAX_BATCH:
				m = self.masters[node.parent] = self.master = self.create_task('batch')
				fix_path(m)
				self.allmasters.append(m)
		m.add_slave(task)
		return task
	return n_hook

extension('.c')(hook('c'))
extension('.cpp','.cc','.cxx','.C','.c++')(hook('cxx'))

@feature('cprogram', 'cshlib', 'cstaticlib', 'cxxprogram', 'cxxshlib', 'cxxstlib')
@after_method('apply_link')
def link_after_masters(self):
	if getattr(self, 'allmasters', None):
		for m in self.allmasters:
			self.link_task.set_run_after(m)

# Modify the c and cxx task classes - in theory it would be best to
# create subclasses and to re-map the c/c++ extensions
for x in ('c', 'cxx'):
	t = Task.classes[x]
	def run(self):
		pass

	def post_run(self):
		pass

	setattr(t, 'oldrun', getattr(t, 'run', None))
	setattr(t, 'run', run)
	setattr(t, 'old_post_run', t.post_run)
	setattr(t, 'post_run', post_run)