1#! /usr/bin/env python
2# encoding: UTF-8
3# Petar Forai
4# Thomas Nagy 2008-2010 (ita)
5
6import re
7from waflib import Task, Logs
8from waflib.TaskGen import extension, feature, after_method
9from waflib.Configure import conf
10from waflib.Tools import c_preproc
11
12"""
13tasks have to be added dynamically:
14- swig interface files may be created at runtime
15- the module name may be unknown in advance
16"""
17
18SWIG_EXTS = ['.swig', '.i']
19
20re_module = re.compile(r'%module(?:\s*\(.*\))?\s+(.+)', re.M)
21
22re_1 = re.compile(r'^%module.*?\s+([\w]+)\s*?$', re.M)
23re_2 = re.compile(r'[#%](?:include|import(?:\(module=".*"\))+|python(?:begin|code)) [<"](.*)[">]', re.M)
24
25class swig(Task.Task):
26	color   = 'BLUE'
27	run_str = '${SWIG} ${SWIGFLAGS} ${SWIGPATH_ST:INCPATHS} ${SWIGDEF_ST:DEFINES} ${SRC}'
28	ext_out = ['.h'] # might produce .h files although it is not mandatory
29	vars = ['SWIG_VERSION', 'SWIGDEPS']
30
31	def runnable_status(self):
32		for t in self.run_after:
33			if not t.hasrun:
34				return Task.ASK_LATER
35
36		if not getattr(self, 'init_outputs', None):
37			self.init_outputs = True
38			if not getattr(self, 'module', None):
39				# search the module name
40				txt = self.inputs[0].read()
41				m = re_module.search(txt)
42				if not m:
43					raise ValueError("could not find the swig module name")
44				self.module = m.group(1)
45
46			swig_c(self)
47
48			# add the language-specific output files as nodes
49			# call funs in the dict swig_langs
50			for x in self.env['SWIGFLAGS']:
51				# obtain the language
52				x = x[1:]
53				try:
54					fun = swig_langs[x]
55				except KeyError:
56					pass
57				else:
58					fun(self)
59
60		return super(swig, self).runnable_status()
61
62	def scan(self):
63		"scan for swig dependencies, climb the .i files"
64		lst_src = []
65
66		seen = []
67		missing = []
68		to_see = [self.inputs[0]]
69
70		while to_see:
71			node = to_see.pop(0)
72			if node in seen:
73				continue
74			seen.append(node)
75			lst_src.append(node)
76
77			# read the file
78			code = node.read()
79			code = c_preproc.re_nl.sub('', code)
80			code = c_preproc.re_cpp.sub(c_preproc.repl, code)
81
82			# find .i files and project headers
83			names = re_2.findall(code)
84			for n in names:
85				for d in self.generator.includes_nodes + [node.parent]:
86					u = d.find_resource(n)
87					if u:
88						to_see.append(u)
89						break
90				else:
91					missing.append(n)
92		return (lst_src, missing)
93
94# provide additional language processing
95swig_langs = {}
96def swigf(fun):
97	swig_langs[fun.__name__.replace('swig_', '')] = fun
98	return fun
99swig.swigf = swigf
100
101def swig_c(self):
102	ext = '.swigwrap_%d.c' % self.generator.idx
103	flags = self.env['SWIGFLAGS']
104	if '-c++' in flags:
105		ext += 'xx'
106	out_node = self.inputs[0].parent.find_or_declare(self.module + ext)
107
108	if '-c++' in flags:
109		c_tsk = self.generator.cxx_hook(out_node)
110	else:
111		c_tsk = self.generator.c_hook(out_node)
112
113	c_tsk.set_run_after(self)
114
115	# transfer weights from swig task to c task
116	if getattr(self, 'weight', None):
117		c_tsk.weight = self.weight
118	if getattr(self, 'tree_weight', None):
119		c_tsk.tree_weight = self.tree_weight
120
121	try:
122		self.more_tasks.append(c_tsk)
123	except AttributeError:
124		self.more_tasks = [c_tsk]
125
126	try:
127		ltask = self.generator.link_task
128	except AttributeError:
129		pass
130	else:
131		ltask.set_run_after(c_tsk)
132		# setting input nodes does not declare the build order
133		# because the build already started, but it sets
134		# the dependency to enable rebuilds
135		ltask.inputs.append(c_tsk.outputs[0])
136
137	self.outputs.append(out_node)
138
139	if not '-o' in self.env['SWIGFLAGS']:
140		self.env.append_value('SWIGFLAGS', ['-o', self.outputs[0].abspath()])
141
142@swigf
143def swig_python(tsk):
144	node = tsk.inputs[0].parent
145	if tsk.outdir:
146		node = tsk.outdir
147	tsk.set_outputs(node.find_or_declare(tsk.module+'.py'))
148
149@swigf
150def swig_ocaml(tsk):
151	node = tsk.inputs[0].parent
152	if tsk.outdir:
153		node = tsk.outdir
154	tsk.set_outputs(node.find_or_declare(tsk.module+'.ml'))
155	tsk.set_outputs(node.find_or_declare(tsk.module+'.mli'))
156
157@extension(*SWIG_EXTS)
158def i_file(self, node):
159	# the task instance
160	tsk = self.create_task('swig')
161	tsk.set_inputs(node)
162	tsk.module = getattr(self, 'swig_module', None)
163
164	flags = self.to_list(getattr(self, 'swig_flags', []))
165	tsk.env.append_value('SWIGFLAGS', flags)
166
167	tsk.outdir = None
168	if '-outdir' in flags:
169		outdir = flags[flags.index('-outdir')+1]
170		outdir = tsk.generator.bld.bldnode.make_node(outdir)
171		outdir.mkdir()
172		tsk.outdir = outdir
173
174@feature('c', 'cxx', 'd', 'fc', 'asm')
175@after_method('apply_link', 'process_source')
176def enforce_swig_before_link(self):
177	try:
178		link_task = self.link_task
179	except AttributeError:
180		pass
181	else:
182		for x in self.tasks:
183			if x.__class__.__name__ == 'swig':
184				link_task.run_after.add(x)
185
186@conf
187def check_swig_version(conf, minver=None):
188	"""
189	Check if the swig tool is found matching a given minimum version.
190	minver should be a tuple, eg. to check for swig >= 1.3.28 pass (1,3,28) as minver.
191
192	If successful, SWIG_VERSION is defined as 'MAJOR.MINOR'
193	(eg. '1.3') of the actual swig version found.
194
195	:param minver: minimum version
196	:type minver: tuple of int
197	:return: swig version
198	:rtype: tuple of int
199	"""
200	assert minver is None or isinstance(minver, tuple)
201	swigbin = conf.env['SWIG']
202	if not swigbin:
203		conf.fatal('could not find the swig executable')
204
205	# Get swig version string
206	cmd = swigbin + ['-version']
207	Logs.debug('swig: Running swig command %r', cmd)
208	reg_swig = re.compile(r'SWIG Version\s(.*)', re.M)
209	swig_out = conf.cmd_and_log(cmd)
210	swigver_tuple = tuple([int(s) for s in reg_swig.findall(swig_out)[0].split('.')])
211
212	# Compare swig version with the minimum required
213	result = (minver is None) or (swigver_tuple >= minver)
214
215	if result:
216		# Define useful environment variables
217		swigver = '.'.join([str(x) for x in swigver_tuple[:2]])
218		conf.env['SWIG_VERSION'] = swigver
219
220	# Feedback
221	swigver_full = '.'.join(map(str, swigver_tuple[:3]))
222	if minver is None:
223		conf.msg('Checking for swig version', swigver_full)
224	else:
225		minver_str = '.'.join(map(str, minver))
226		conf.msg('Checking for swig version >= %s' % (minver_str,), swigver_full, color=result and 'GREEN' or 'YELLOW')
227
228	if not result:
229		conf.fatal('The swig version is too old, expecting %r' % (minver,))
230
231	return swigver_tuple
232
233def configure(conf):
234	conf.find_program('swig', var='SWIG')
235	conf.env.SWIGPATH_ST = '-I%s'
236	conf.env.SWIGDEF_ST = '-D%s'
237
238