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