1#! /usr/bin/env python
2# encoding: utf-8
3
4"""
5Compile whole groups of C/C++ files at once
6(C and C++ files are processed independently though).
7
8To enable globally::
9
10	def options(opt):
11		opt.load('compiler_cxx')
12	def build(bld):
13		bld.load('compiler_cxx unity')
14
15To enable for specific task generators only::
16
17	def build(bld):
18		bld(features='c cprogram unity', source='main.c', ...)
19
20The file order is often significant in such builds, so it can be
21necessary to adjust the order of source files and the batch sizes.
22To control the amount of files processed in a batch per target
23(the default is 50)::
24
25	def build(bld):
26		bld(features='c cprogram', unity_size=20)
27
28"""
29
30from waflib import Task, Options
31from waflib.Tools import c_preproc
32from waflib import TaskGen
33
34MAX_BATCH = 50
35
36EXTS_C = ('.c',)
37EXTS_CXX = ('.cpp','.cc','.cxx','.C','.c++')
38
39def options(opt):
40	global MAX_BATCH
41	opt.add_option('--batchsize', action='store', dest='batchsize', type='int', default=MAX_BATCH,
42		help='default unity batch size (0 disables unity builds)')
43
44@TaskGen.taskgen_method
45def batch_size(self):
46	default = getattr(Options.options, 'batchsize', MAX_BATCH)
47	if default < 1:
48		return 0
49	return getattr(self, 'unity_size', default)
50
51
52class unity(Task.Task):
53	color = 'BLUE'
54	scan = c_preproc.scan
55	def to_include(self, node):
56		ret = node.path_from(self.outputs[0].parent)
57		ret = ret.replace('\\', '\\\\').replace('"', '\\"')
58		return ret
59	def run(self):
60		lst = ['#include "%s"\n' % self.to_include(node) for node in self.inputs]
61		txt = ''.join(lst)
62		self.outputs[0].write(txt)
63	def __str__(self):
64		node = self.outputs[0]
65		return node.path_from(node.ctx.launch_node())
66
67def bind_unity(obj, cls_name, exts):
68	if not 'mappings' in obj.__dict__:
69		obj.mappings = dict(obj.mappings)
70
71	for j in exts:
72		fun = obj.mappings[j]
73		if fun.__name__ == 'unity_fun':
74			raise ValueError('Attempt to bind unity mappings multiple times %r' % j)
75
76		def unity_fun(self, node):
77			cnt = self.batch_size()
78			if cnt <= 1:
79				return fun(self, node)
80			x = getattr(self, 'master_%s' % cls_name, None)
81			if not x or len(x.inputs) >= cnt:
82				x = self.create_task('unity')
83				setattr(self, 'master_%s' % cls_name, x)
84
85				cnt_cur = getattr(self, 'cnt_%s' % cls_name, 0)
86				c_node = node.parent.find_or_declare('unity_%s_%d_%d.%s' % (self.idx, cnt_cur, cnt, cls_name))
87				x.outputs = [c_node]
88				setattr(self, 'cnt_%s' % cls_name, cnt_cur + 1)
89				fun(self, c_node)
90			x.inputs.append(node)
91
92		obj.mappings[j] = unity_fun
93
94@TaskGen.feature('unity')
95@TaskGen.before('process_source')
96def single_unity(self):
97	lst = self.to_list(self.features)
98	if 'c' in lst:
99		bind_unity(self, 'c', EXTS_C)
100	if 'cxx' in lst:
101		bind_unity(self, 'cxx', EXTS_CXX)
102
103def build(bld):
104	if bld.env.CC_NAME:
105		bind_unity(TaskGen.task_gen, 'c', EXTS_C)
106	if bld.env.CXX_NAME:
107		bind_unity(TaskGen.task_gen, 'cxx', EXTS_CXX)
108
109